feat(wordpress): adiciona plugin wordpress (agents, skills, knowledge e metadados do projeto)

This commit is contained in:
2026-10-28 16:09:51 +00:00
parent a32752df8a
commit 63b1d79095
58 changed files with 7380 additions and 0 deletions
+17
View File
@@ -0,0 +1,17 @@
# AGENTS.md — skills
Directrizes específicas deste projecto para agentes IA.
## Referências obrigatórias
- `ECOSYSTEM.md` — Hub/06-Operacoes/Documentacao/ECOSYSTEM.md
- `PROC-DEV-STANDARD` — D7-SIS-006
- `PROC-AUDIT-STANDARD` — D7-SIS-007
## Protocolo de sessão
Início: ler MEMORY.md + STATUS.md. Fim: actualizar ambos.
## Convenções específicas
- (a definir)
+5
View File
@@ -0,0 +1,5 @@
# CHANGELOG — skills
## 0.1.0 — 2026-06-30
### Adicionado
- Estrutura padrão (PROC-DEV-STANDARD)
+4
View File
@@ -0,0 +1,4 @@
# MEMORY.md — skills
## 2026-06-30
- Estrutura padrão aplicada via propagate-structure.sh
+3
View File
@@ -0,0 +1,3 @@
# skills
Subprojecto skills do ecossistema Descomplicar.
+8
View File
@@ -0,0 +1,8 @@
# STATUS.md — skills
**Versão:** 0.1 | **Data:** 2026-06-30 | **Agente:** —
## Estado actual
- Em curso: —
- Bloqueios: —
- Próximos passos: —
+133
View File
@@ -0,0 +1,133 @@
---
name: branda-menu
description: Organizacao do menu admin WordPress via Branda (branda-white-labeling) em seccoes Descomplicar standard. Computa Branda IDs a partir de slugs WP, constroi menu custom e guarda programaticamente via WP-CLI.
layer: wiki
---
# /branda-menu - Organizar Menu Admin WordPress
Skill para organizar o menu admin sidebar de qualquer site WordPress WiP usando o Branda (branda-white-labeling).
---
## Arquitectura Branda (Referencia Interna)
### Como o Branda gere menus
O Branda usa um modulo `admin/menu.php` que intercepta o render do menu admin WordPress e substitui pela configuracao custom.
**Options `wp_options` envolvidas:**
| Option | Funcao |
|--------|--------|
| `ultimatebranding_activated_modules` | Modulos activos (precisa ter `admin/menu.php`) |
| `ub_custom_admin_menu` | Configuracao do menu custom por role |
| `ub_saved_admin_menus` | Captura do menu nativo WP (so para UI Branda) |
### Algoritmo de ID (critico)
```php
function branda_id($slug) {
return "menu_item_" . substr(base_convert(md5($slug), 16, 32), 0, 12);
}
```
**Exemplo:** `index.php` -> `menu_item_42ho017e7jo0`
### Tipos de itens no menu
| Tipo | Campos chave | Uso |
|------|-------------|-----|
| **Section header** | `link_type: "none"`, `css_classes: "menu-highlight"` | Separador visual com titulo |
| **Custom link** | `link_type: "custom"`, `custom_url: "..."` | Link manual |
| **Native item** | `was_native: 1`, campos vazios | Item WP real |
| **Hidden item** | `is_hidden: "1"`, `was_native: 1` | Item WP escondido |
### Mecanismo de merge (parse_args_deep)
Na renderizacao, o Branda faz merge do menu configurado com o menu WP real. **Regra critica:** Incluir **todos** os itens nativos no config (visiveis ou hidden). Se faltar algum, aparece na posicao default e quebra a organizacao.
---
## Seccoes Standard Descomplicar (9)
| # | Seccao | Dashicon | Conteudo tipico |
|---|--------|----------|----------------|
| 1 | **Suporte** | dashicons-admin-tools | Link Descomplicar |
| 2 | **Admin** | dashicons-admin-generic | Painel, Utilizadores, Opcoes, Plugins, Ferramentas |
| 3 | **Conteudo** | dashicons-admin-page | Paginas, CPTs, Artigos, Multimedia, ACF |
| 4 | **Design** | dashicons-admin-appearance | Elementor, Modelos, Jeg Kit, Apresentacao |
| 5 | **Marketing** | dashicons-megaphone | FluentCRM, Fluent Forms, Rank Math, WhatsApp |
| 6 | **Idiomas** | dashicons-translation | Polylang/WPML, Loco Translate (se multilingue) |
| 7 | **Performance** | dashicons-performance | WP Fastest Cache, WebP Express |
| 8 | **Seguranca** | dashicons-shield | Wordfence, Complianz |
| 9 | **WebMaster** | dashicons-admin-settings | Branda, Code Snippets, Activity Log |
---
## Procedimento
### Passo 1: Verificar pre-requisitos
```bash
mcp__ssh-unified__ssh_execute server:"server" command:"wp plugin is-active branda-white-labeling --allow-root --path=/home/USER/SITE && echo ACTIVE || echo INACTIVE"
# Verificar modulo menu activo
mcp__ssh-unified__ssh_execute server:"server" command:"wp eval '
\$m = get_option(\"ultimatebranding_activated_modules\", array());
echo isset(\$m[\"admin/menu.php\"]) ? \"MENU MODULE ACTIVE\" : \"MENU MODULE INACTIVE\";
' --allow-root --path=/home/USER/SITE"
```
Se modulo inactivo, activar:
```bash
mcp__ssh-unified__ssh_execute server:"server" command:"wp eval '
\$m = get_option(\"ultimatebranding_activated_modules\", array());
\$m[\"admin/menu.php\"] = \"yes\";
update_option(\"ultimatebranding_activated_modules\", \$m);
echo \"Activado\";
' --allow-root --path=/home/USER/SITE"
```
### Passo 2: Extrair menu nativo
**Opcao A (preferida): mu-plugin temporario** - criar mu-plugin que captura menu no proximo page load, depois ler a captura via `wp eval`.
**Opcao B (alternativa): Chrome DevTools** - navegar ao wp-admin e executar JS para listar itens.
### Passo 3: Mapear itens para seccoes
Com a lista de itens nativos, mapear cada um para a seccao correcta.
Mapeamento completo de slugs e IDs em: `references/mapeamento-slugs.md`
### Passo 4: Construir e guardar o menu
Script PHP template completo em: `references/script-template.md`
Executar com `wp eval-file` via SSH MCP.
### Passo 5: Verificar
```bash
mcp__ssh-unified__ssh_execute server:"server" command:"wp eval '
\$m = get_option(\"ub_custom_admin_menu\");
\$items = \$m[\"administrator\"];
echo \"Total: \" . count(\$items) . \"\n\";
foreach (\$items as \$key => \$item) {
\$hidden = !empty(\$item[\"is_hidden\"]) ? \" [HIDDEN]\" : \"\";
\$type = !empty(\$item[\"was_native\"]) ? \"native\" : (!empty(\$item[\"link_type\"]) && \$item[\"link_type\"] == \"none\" ? \"header\" : \"custom\");
\$title = !empty(\$item[\"title\"]) ? \$item[\"title\"] : \"(native)\";
echo \"\$key => \$type: \$title\$hidden\n\";
}
' --allow-root --path=/home/USER/SITE"
```
Verificar visualmente no browser.
### Passo 6: Limpar
```bash
mcp__ssh-unified__ssh_execute server:"server" command:"rm -f /tmp/branda-menu-SITE.php"
mcp__ssh-unified__ssh_e
@@ -0,0 +1,100 @@
# Mapeamento de Slugs para Seccoes Branda
Referencia completa de mapeamento slug -> seccao e tabela de Branda IDs.
---
## Mapeamento Standard por Slug
```
# Admin
index.php -> Admin
users.php -> Admin
options-general.php -> Admin
plugins.php -> Admin
tools.php -> Admin
# Conteudo
edit.php -> Conteudo (Artigos)
edit.php?post_type=page -> Conteudo (Paginas)
upload.php -> Conteudo (Multimedia)
edit.php?post_type=acf-field-group -> Conteudo (ACF)
edit.php?post_type=* -> Conteudo (CPTs)
# Design
elementor-home -> Design (Elementor)
edit.php?post_type=elementor_library -> Design (Modelos)
jkit -> Design (Jeg Kit)
themes.php -> Design (Apresentacao)
# Marketing
fluentcrm-admin -> Marketing
fluent_forms -> Marketing
rank-math -> Marketing
click-to-chat -> Marketing
# Idiomas (se multilingue)
mlang -> Idiomas (Polylang)
loco -> Idiomas (Loco Translate)
# Performance
wpfastestcacheoptions -> Performance
# Seguranca
Wordfence -> Seguranca
complianz -> Seguranca
# WebMaster
branding -> WebMaster (Branda)
wpcode -> WebMaster (Code Snippets)
# Hidden
separator1, separator2, separator-last -> Hidden
edit-comments.php -> Hidden
hello-elementor -> Hidden
elementor -> Hidden (duplicado de elementor-home)
```
**Itens que nao encaixam no standard:** Perguntar ao utilizador em que seccao colocar. Exemplos: WooCommerce, KiviCare, FareHarbor.
---
## IDs de Slugs Comuns (referencia rapida)
| Slug | Branda ID |
|------|-----------|
| `index.php` | `menu_item_42ho017e7jo0` |
| `edit.php` | `menu_item_6lk5pbiakha0` |
| `upload.php` | `menu_item_1jkdde99dfd0` |
| `edit.php?post_type=page` | `menu_item_774p5endtlu0` |
| `themes.php` | `menu_item_7jgmlsspgv60` |
| `plugins.php` | `menu_item_5g2kqk93qi30` |
| `users.php` | `menu_item_gajld83c8es0` |
| `tools.php` | `menu_item_3t0no8pv5bfg` |
| `options-general.php` | `menu_item_d1a8rsor9700` |
| `edit-comments.php` | `menu_item_252pn6seih20` |
| `separator1` | `menu_item_3u7nva84d1i0` |
| `separator2` | `menu_item_6lm7mo14a4r0` |
| `separator-last` | `menu_item_74g99t5jejn0` |
| `elementor-home` | `menu_item_2kehh8g6nop0` |
| `elementor` | `menu_item_27qkhd7iqao0` |
| `edit.php?post_type=elementor_library` | `menu_item_3rubghs8krfg` |
| `hello-elementor` | `menu_item_3q7v6ask7gpg` |
| `fluentcrm-admin` | `menu_item_4a7t8bi9mt30` |
| `fluent_forms` | `menu_item_ebai6etubd00` |
| `rank-math` | `menu_item_148bl1t91os0` |
| `click-to-chat` | `menu_item_1b65lubbpnd8` |
| `Wordfence` | `menu_item_1077vi8mf9b0` |
| `complianz` | `menu_item_5etmjgu9lnk0` |
| `wpfastestcacheoptions` | `menu_item_4hqsn1kbum10` |
| `branding` | `menu_item_7qde2b2f7670` |
| `wpcode` | `menu_item_69igp9fj4tl0` |
| `loco` | `menu_item_2c34vb7r1csg` |
| `mlang` | `menu_item_24um676vv08g` |
| `woocommerce` | Computar: `branda_id('woocommerce')` |
| `edit.php?post_type=acf-field-group` | `menu_item_70ga2p32mc70` |
Para slugs nao listados, computar com:
```bash
wp eval 'echo "menu_item_" . substr(base_convert(md5("SLUG"), 16, 32), 0, 12);' --allow-root --path=/home/USER/SITE
```
@@ -0,0 +1,174 @@
# Script Template Branda Menu Builder
Script PHP completo para construir e guardar menu Branda via WP-CLI.
---
## Template Base
```bash
mcp__ssh-unified__ssh_execute server:"server" command:"cat > /tmp/branda-menu-SITE.php << 'PHPEOF'
<?php
/**
* Branda menu builder for SITENAME
* Generated by /branda-menu skill
*/
function branda_id(\$slug) {
return 'menu_item_' . substr(base_convert(md5(\$slug), 16, 32), 0, 12);
}
function native_item(\$subs = array()) {
return array(
'title' => '', 'id_attribute' => '', 'css_classes' => '',
'icon_svg' => '', 'icon_url' => '', 'icon_image_id' => '',
'dashicon' => '', 'icon_type' => '', 'custom_url' => '',
'link_type' => '', 'link_target' => '', 'is_invisible' => '',
'is_hidden' => '', 'was_native' => 1, 'submenu' => \$subs
);
}
function hidden_item() {
\$item = native_item();
\$item['is_hidden'] = '1';
return \$item;
}
function section_header(\$title, \$dashicon) {
return array(
'icon_type' => 'dashicon', 'link_type' => 'none',
'submenu' => array(), 'title' => \$title,
'css_classes' => 'menu-highlight', 'dashicon' => \$dashicon
);
}
function custom_link(\$title, \$url, \$dashicon, \$target = '') {
return array(
'icon_type' => 'dashicon', 'link_type' => 'custom',
'submenu' => array(), 'title' => \$title,
'dashicon' => \$dashicon, 'link_target' => \$target,
'custom_url' => \$url
);
}
// === BUILD MENU ===
\$menu = array();
// 1. SUPORTE
\$menu['menu_item_sec_suporte'] = section_header('Suporte', 'dashicons-admin-tools');
\$menu['menu_item_desk_link'] = custom_link('Descomplicar', 'https://desk.descomplicar.pt/', 'dashicons-admin-comments', '_blank');
// 2. ADMIN
\$menu['menu_item_sec_admin'] = section_header('Admin', 'dashicons-admin-generic');
\$menu[branda_id('index.php')] = native_item();
\$menu[branda_id('users.php')] = native_item();
\$menu[branda_id('options-general.php')] = native_item();
\$menu[branda_id('plugins.php')] = native_item();
\$menu[branda_id('tools.php')] = native_item();
// 3. CONTEUDO
\$menu['menu_item_sec_conteudo'] = section_header('Conteudo', 'dashicons-admin-page');
\$menu[branda_id('edit.php?post_type=page')] = native_item();
// [INSERIR CPTs DO PROJECTO AQUI]
\$menu[branda_id('edit.php')] = native_item();
\$menu[branda_id('upload.php')] = native_item();
\$menu[branda_id('edit.php?post_type=acf-field-group')] = native_item();
// 4. DESIGN
\$menu['menu_item_sec_design'] = section_header('Design', 'dashicons-admin-appearance');
\$menu[branda_id('elementor-home')] = native_item();
\$menu[branda_id('edit.php?post_type=elementor_library')] = native_item();
// \$menu[branda_id('jkit')] = native_item(); // Se Jeg Kit activo
\$menu[branda_id('themes.php')] = native_item();
// 5. MARKETING
\$menu['menu_item_sec_marketing'] = section_header('Marketing', 'dashicons-megaphone');
\$menu[branda_id('fluentcrm-admin')] = native_item();
\$menu[branda_id('fluent_forms')] = native_item();
\$menu[branda_id('rank-math')] = native_item();
\$menu[branda_id('click-to-chat')] = native_item();
// 6. IDIOMAS (se multilingue)
// \$menu['menu_item_sec_idiomas'] = section_header('Idiomas', 'dashicons-translation');
// \$menu[branda_id('mlang')] = native_item();
// \$menu[branda_id('loco')] = native_item();
// 7. PERFORMANCE
\$menu['menu_item_sec_performance'] = section_header('Performance', 'dashicons-performance');
\$menu[branda_id('wpfastestcacheoptions')] = native_item();
// 8. SEGURANCA
\$menu['menu_item_sec_seguranca'] = section_header('Seguranca', 'dashicons-shield');
\$menu[branda_id('Wordfence')] = native_item();
\$menu[branda_id('complianz')] = native_item();
// 9. WEBMASTER
\$menu['menu_item_sec_webmaster'] = section_header('WebMaster', 'dashicons-admin-settings');
\$menu[branda_id('branding')] = native_item();
\$menu[branda_id('wpcode')] = native_item();
// HIDDEN
\$menu[branda_id('separator1')] = hidden_item();
\$menu[branda_id('separator2')] = hidden_item();
\$menu[branda_id('separator-last')] = hidden_item();
\$menu[branda_id('edit-comments.php')] = hidden_item();
\$menu[branda_id('hello-elementor')] = hidden_item();
\$menu[branda_id('elementor')] = hidden_item();
// SAVE
update_option('ub_custom_admin_menu', array('administrator' => \$menu));
wp_cache_flush();
\$visible = count(array_filter(\$menu, function(\$i) { return empty(\$i['is_hidden']); }));
\$hidden = count(\$menu) - \$visible;
echo "Menu guardado: \$visible visiveis + \$hidden hidden = " . count(\$menu) . " total\n";
PHPEOF
echo 'Script criado'
"
# Executar
mcp__ssh-unified__ssh_execute server:"server" command:"wp eval-file /tmp/branda-menu-SITE.php --allow-root --path=/home/USER/SITE"
```
---
## Adaptacoes por Tipo de Site
### Sites multilingue (Polylang/WPML)
Descomentar seccao Idiomas no script. Adicionar:
```php
$menu['menu_item_sec_idiomas'] = section_header('Idiomas', 'dashicons-translation');
$menu[branda_id('mlang')] = native_item(); // Polylang
$menu[branda_id('loco')] = native_item(); // Loco Translate
```
### Sites WooCommerce
Adicionar seccao e-Commerce entre Marketing e Idiomas:
```php
$menu['menu_item_sec_ecommerce'] = section_header('e-Commerce', 'dashicons-cart');
$menu[branda_id('woocommerce')] = native_item();
```
### Sites Care (KiviCare)
Adicionar seccao Clinica entre Marketing e Idiomas:
```php
$menu['menu_item_sec_clinica'] = section_header('Clinica', 'dashicons-heart');
$menu[branda_id('kivicare-...')] = native_item(); // Verificar slug exacto
```
### CPTs customizados
Adicionar na seccao Conteudo, antes de Artigos:
```php
$menu[branda_id('edit.php?post_type=SLUG_CPT')] = native_item();
```
### Itens desconhecidos
Se o menu nativo tiver itens que nao encaixam no mapeamento standard:
1. Perguntar ao utilizador onde colocar
2. Se plugin temporario/teste: adicionar ao Hidden
3. Se plugin permanente: criar seccao propria ou adicionar a seccao existente
+130
View File
@@ -0,0 +1,130 @@
---
name: crocoblock
description: Desenvolvimento com o ecossistema Crocoblock e todos os JetPlugins para Elementor. Cobre selecção, licenciamento, instalação, configuração e integração entre JetEngine, JetSmartFilters, JetWooBuilder, JetElements, JetPopup, JetFormBuilder e mais.
layer: wiki
---
# /crocoblock — Ecossistema Crocoblock + JetPlugins
Desenvolvimento com o ecossistema Crocoblock: suite de plugins para Elementor cobrindo CPT, filtros AJAX, WooCommerce, forms, popups, booking e muito mais.
## Matriz de Plugins
| Plugin | Função Principal | Quando Usar |
|--------|-----------------|-------------|
| **JetEngine** | CPT, Meta Boxes, Relations, Query Builder, Listings, Dynamic Content | Qualquer site com conteúdo dinâmico/estruturado |
| **JetSmartFilters** | Filtros AJAX para listings e arquivos | Catálogos, portfolios, diretórios filtráveis |
| **JetWooBuilder** | Templates WooCommerce com Elementor | Lojas com design personalizado de produto/categoria/checkout |
| **JetElements** | Widgets adicionais (Timeline, Progress Bar, Circle Progress, Maps) | Adicionar widgets não disponíveis no Elementor base |
| **JetPopup** | Popups com triggers avançados | Lead capture, notificações, banners condicionais |
| **JetFormBuilder** | Forms com lógica condicional + notificações | Formulários complexos, integração CRM, booking light |
| **JetTabs** | Tabs, Accordions, Toggle | Conteúdo tabulado interactivo |
| **JetBlocks** | Widgets para header/footer (nav, cart, search) | Cabeçalhos e rodapés custom no Theme Builder |
| **JetSearch** | Pesquisa AJAX avançada | Sites com necessidade de search instantâneo |
| **JetBooking** | Sistema de booking/reservas | Alojamento, serviços com calendário de disponibilidade |
| **JetThemeCore** | Theme Builder alternativo ao Elementor Pro | Theme Builder sem licença Elementor Pro |
| **JetReviews** | Reviews/ratings para qualquer CPT | Reviews além de WooCommerce |
| **JetTricks** | Efeitos visuais (parallax, hotspot, unfold) | Elementos visuais avançados |
| **JetCompareWishlist** | Comparar e wishlist para produtos/CPT | E-commerce ou catálogos com comparação |
## Licença e Instalação
### Subscrição Crocoblock
```
Planos: Single Site / 3 Sites / Unlimited Sites (anual ou lifetime)
Todos os planos incluem todos os plugins + atualizações
Dashboard: account.crocoblock.com > Downloads
```
### Instalação (CWP)
```bash
PHP="/opt/alt/php-fpm83/usr/bin/php"
WP="$PHP /usr/local/bin/wp --allow-root --path=/home/USER/public_html"
# 1. Upload do ZIP via WP Admin ou WP-CLI
$WP plugin install /tmp/jet-engine.zip --activate
# 2. Activar licença (via WP Admin > JetPlugins Licences)
# Cada plugin tem campo de licença separado
# Ou activar globalmente em Crocoblock > License
# 3. Verificar versões
$WP plugin list --search="jet*" --fields=name,version,status --format=table
```
### Ordem de Instalação Recomendada
```
1. JetEngine (base do ecossistema — criar antes de outros)
2. JetSmartFilters (depende do JetEngine para filtrar listings)
3. JetElements (widgets independentes)
4. JetWooBuilder (se WooCommerce presente)
5. JetFormBuilder (forms independentes)
6. JetPopup (depende de Elementor Pro ou JetThemeCore)
7. JetBlocks (apenas se Theme Builder activo)
8. Restantes (conforme necessidade)
```
## Integrações Principais
### JetEngine + JetSmartFilters (padrão mais comum)
```
JetEngine Query Builder → Listing Grid → JetSmartFilters
↑
Filtros ligados ao mesmo Query Builder ID
```
**Passos:**
1. Criar CPT no JetEngine
2. Criar Query Builder (tipo: Posts)
3. Criar Listing Template com Elementor
4. Adicionar Listing Grid ao Elementor (apontar para o Query)
5. Adicionar Filter Widget e ligar ao mesmo Listing Grid
### JetEngine + JetWooBuilder
```
CPT Produto (WooCommerce) → JetEngine Meta Boxes → JetWooBuilder Template
↑
Campos custom visíveis no template
```
### JetEngine + JetFormBuilder
```
JetFormBuilder → Submit → Create/Update JetEngine Post
→ Notificação email
→ Redirect
```
### JetPopup Triggers Disponíveis
| Trigger | Uso |
|---------|-----|
| On page load | Newsletter, anúncio |
| Exit intent | Retenção de visitante |
| Scroll (%) | Conteúdo contextual |
| Click on element | Mais informação inline |
| After inactivity | Re-engagement |
| User logged in/out | Conteúdo condicional |
## Dify KB — Consultar Antes de Implementar
```javascript
// Dataset Crocoblock (primário)
mcp__dify-kb__dify_kb_retrieve_segments({
dataset_id: "bdf85c26-1824-4021-92d1-be20501b35ac",
query: "[componente ou funcionalidade]"
})
// Dataset Crocoblock alternativo
mcp__dify-kb__dify_kb_retrieve_segments({
dataset_id: "139cdf67-afce-46ec-9ccd-2a06040e5b9d",
query: "[componente ou funcionalidade]"
})
```
## Troubleshooting
@@ -0,0 +1,294 @@
# Crocoblock — Automação Headless e Integrações Avançadas
## 1. JetEngine 3.8.0 — MCP Server Nativo
A partir da versão 3.8.0, o JetEngine inclui um **MCP Server nativo** que expõe a topologia completa do site a agentes IA (Claude, Copilot, Cursor).
### Activar o MCP Server
```
WP Admin > JetEngine > AI Command Center > Enable MCP Server
→ URL do servidor: https://site.pt/wp-json/jet-engine/v1/mcp
→ Autenticação: Application Password
```
### Capacidades do MCP
| Capacidade | Descrição |
|-----------|-----------|
| Inspecção de schema | Lista CPTs, CCTs, Meta Boxes, Taxonomias, Relações |
| Criação de CPT | Instanciar novo Custom Post Type via prompt |
| Criação de CCT | Definir Custom Content Type com campos tipados |
| Definir relações | Criar estruturas relacionais entre entidades |
| Query Builder | Criar queries estruturadas |
**Caso de uso:** Cliente especifica necessidades → Claude interroga MCP → constrói arquitectura completa (CPT + CCT + Relations + Queries) sem GUI.
---
## 2. JetFormBuilder — Formless Actions Endpoints
O addon **Formless Actions Endpoints** permite usar as acções de um formulário JetFormBuilder como endpoint REST puro, sem renderizar formulário no frontend.
### Como Activar
```
JetFormBuilder > Settings > Formless Actions Endpoints: Enable
Cada formulário recebe rota: POST /wp-json/jet-fb/v1/form/{form_id}
```
### Payload de Exemplo
```bash
# Submeter dados para formulário sem interface visual
# Acciona todas as Post Submit Actions configuradas (Create Post, Email, Webhook, etc.)
curl -X POST https://site.pt/wp-json/jet-fb/v1/form/1234 \
-H "Authorization: Basic dXNlcjpwYXNz..." \
-H "Content-Type: application/json" \
-d '{
"nome_cliente": "Empresa ABC",
"email": "contacto@empresaabc.pt",
"valor_contrato": 5000,
"tipo_servico": "website"
}'
```
### Encadear Acções Complexas
```
Um único request pode:
1. Criar Post no CPT "Projecto" com status pending
2. Enviar email ao admin
3. Actualizar relação Cliente↔Projecto
4. Enviar Webhook para n8n
5. Criar registo em CCT "Notificacoes"
```
### Interceptar com Hook Custom
```php
<?php
// Interceptar antes de gravar para validação externa
add_action('jet-form-builder/custom-action/analise_risco', function($request, $action_handler) {
$dados = $request->get_all();
$texto = sanitize_textarea_field($dados['descricao'] ?? '');
$post_id = absint($dados['post_id'] ?? 0);
if (empty($texto) || !$post_id) {
// Cancelar o processamento com erro
throw new \Jet_Form_Builder\Exceptions\Action_Exception('campos_obrigatorios_vazios');
}
// Validação via API externa (exemplo: análise de risco)
$score = minha_api_de_risco($texto);
if ($score > 90) {
throw new \Jet_Form_Builder\Exceptions\Action_Exception('risco_inaceitavel');
}
// Actualizar meta field com o score calculado
update_post_meta($post_id, '_score_risco', $score);
$action_handler->add_response([
'status' => 'success',
'message' => 'Processado com sucesso.',
'score' => $score,
]);
}, 10, 2);
```
### Tabela de Registos
```sql
-- JetFormBuilder guarda registo de cada submissão
SELECT *
FROM wp_jet_fb_records
WHERE form_id = 1234
ORDER BY id DESC
LIMIT 10;
```
---
## 3. JetSmartFilters — Indexer e Automação
### O Indexer
O JetSmartFilters tem um subsistema de contagem chamado **Indexer** que mapeia contadores em `jet_smart_filters_indexer`.
```sql
-- Ver contagens indexadas
SELECT * FROM jet_smart_filters_indexer
WHERE filter_id = 550
LIMIT 20;
```
**Problema:** Após actualizações SQL directas nas tabelas CCT ou wp_jet_rel_default, os contadores dos filtros ficam desactualizados.
**Solução:**
```php
<?php
// Recalcular Indexer após bulk update
add_action('meu_evento_pos_atualizacao', function() {
if (function_exists('jet_smart_filters')) {
jet_smart_filters()->indexer->index_all();
}
});
```
```bash
# Via WP-CLI
PHP="/opt/alt/php-fpm83/usr/bin/php"
WP="$PHP /usr/local/bin/wp --allow-root --path=/home/USER/public_html"
$WP eval "jet_smart_filters()->indexer->index_all();"
```
### Manipular Filtros Programaticamente
```php
<?php
// Injectar limites automáticos num filtro (ex: baseado no role do utilizador)
add_filter('jet-smart-filters/filter-instance/args', function($args, $filter_instance) {
// Só aplicar ao filtro #2550
if ((int) $args['filter_id'] !== 2550) {
return $args;
}
// Forçar range baseado em contexto (ex: permissões do utilizador)
$args['current_value'] = [
'min' => '100',
'max' => '500',
];
// Forçar AJAX
$args['apply_type'] = 'ajax';
return $args;
}, 10, 2);
```
### Forçar Re-indexação após SQL Massivo
```php
<?php
// Pipeline completo pós-sync ERP (exemplo: n8n aciona via HTTP)
add_action('rest_api_init', function() {
register_rest_route('descomplicar/v1', '/reindex', [
'methods' => 'POST',
'callback' => function() {
wp_cache_flush();
\Jet_Engine\Query_Builder\Manager::instance()->listings_cache->clear();
jet_smart_filters()->indexer->index_all();
return rest_ensure_response(['success' => true, 'message' => 'Re-indexação concluída.']);
},
'permission_callback' => function() {
return current_user_can('edit_posts');
},
]);
});
```
---
## 4. Kits e Templates — Export/Import Programático
### Estrutura de um Kit
```
Kit JSON (manifest.json):
├── CPTs com todas as configurações
├── CCTs com schema de campos
├── Meta Boxes
├── Taxonomias
├── Relações
├── Query Builder queries
├── Listing Templates (Elementor)
└── JetFormBuilder forms
```
### Export via WP-CLI
```bash
PHP="/opt/alt/php-fpm83/usr/bin/php"
WP="$PHP /usr/local/bin/wp --allow-root --path=/home/USER/public_html"
# Exportar estrutura JetEngine para JSON
$WP eval '
$cpts = get_option("jet_engine_cpt", []);
$ccls = get_option("jet_engine_ccl", []);
$rels = get_option("jet_engine_relations", []);
$metas = get_option("jet_engine_meta_boxes", []);
$kit_data = compact("cpts", "ccls", "rels", "metas");
file_put_contents("/tmp/kit-jetengine.json", json_encode($kit_data, JSON_PRETTY_PRINT));
'
echo "Kit exportado para /tmp/kit-jetengine.json"
```
### Import em Novo Ambiente
```bash
# ATENÇÃO: os IDs das relações mudam entre ambientes
# Sempre usar slugs como referência, não IDs numéricos
$WP eval-file /tmp/import-kit.php
$WP cache flush
$WP rewrite flush
```
### Pitfall de Migração — IDs de Relações
```
Problema: CCT item #45 no staging → ID #45
Mesmo item no production após import → ID #102
→ Relações wp_jet_rel_default ficam com IDs errados
Solução: Usar slug/título como referência durante migração
Converter para ID após import via get_posts(['name' => 'slug'])
Ou usar WP All Import para recriar relações com mapeamento de IDs
```
---
## 5. Integração WooCommerce + JetEngine
### Queries em Produtos WooCommerce
```
Produtos WooCommerce são CPT 'product' → compatíveis com JetEngine
Query Builder pode filtrar produtos por meta fields JetEngine adicionados
JetWooBuilder consome estas queries para listagens customizadas
```
### Relações com Produtos
```sql
-- Produtos relacionados com CPT "Fornecedor" via JetEngine Relations
SELECT p.post_title AS produto, f.post_title AS fornecedor
FROM wp_posts p
JOIN wp_jet_rel_default r ON r.child_object_id = p.ID
JOIN wp_posts f ON f.ID = r.parent_object_id
WHERE r.rel_id = 'relacao_fornecedores_produtos'
AND p.post_status = 'publish';
```
---
## 6. Limitações e Riscos Críticos
| Risco | Impacto | Mitigação |
|-------|---------|-----------|
| SQL directo em wp_postmeta serializado | **Crítico** — corrupção de dados | NUNCA. Usar PHP `unserialize → edit → serialize` |
| store_items_type = "replace" na REST API | **Destrutivo** — apaga todas as relações | Usar "update" salvo intenção explícita |
| Alterar wp_options serializados via SQL | **Crítico** — instalação WordPress corrompida | Sempre via PHP `get_option → update_option` |
| SQL sem flush de cache | Dados obsoletos em listings | Sempre `wp_cache_flush()` + `listings_cache->clear()` + `indexer->index_all()` |
| Registar meta boxes antes de init prioridade 12 | Falha silenciosa | Usar hook `jet-engine/meta-boxes/register-instances` |
| Importar Kit entre versões JetEngine diferentes | Dependências fantasma no Query Builder | Manter versões alinhadas em staging e production |
---
*Crocoblock Automation | Descomplicar® | 18-02-2026*
@@ -0,0 +1,220 @@
# Crocoblock — Padrões de Integração Avançados
## Padrão 1: Directório com Filtros + Mapa
**Plugins:** JetEngine + JetSmartFilters + JetElements (Maps)
```
Arquitectura:
CPT "Negócio" (com meta: morada, coordenadas, categoria, horário)
↓
Query Builder (posts, post_type=negocio)
↓
┌────────────────┬──────────────────┐
│ Listing Grid │ Listing Map │
│ (cards) │ (pins) │
└────────────────┴──────────────────┘
↑ sincronizados ↑
JetSmartFilters:
- Categoria (checkbox, taxonomy)
- Localização (select, meta)
- Pesquisa (search text)
- Raio de distância (range, meta GPS)
```
**Configuração de sincronização:**
```
Listing Map > Settings:
├── Synchronize with Listing Grid: YES
└── Listing Grid Selector: ID do widget Listing Grid
JetSmartFilters > Apply For:
└── Seleccionar ID do Listing Grid
(o mapa segue automaticamente via sincronização)
```
---
## Padrão 2: Marketplace / Multi-vendor Simples
**Plugins:** JetEngine + JetFormBuilder + JetSmartFilters + Profile Builder
```
Fluxo do vendedor:
1. Registo (JetFormBuilder → Register User)
2. Login
3. Dashboard vendedor (Profile Builder)
4. Criar produto (JetFormBuilder → Create Post no CPT)
5. Produto aparece no catálogo (Listing Grid filtrado por author)
Fluxo do comprador:
1. Catálogo com filtros (JetSmartFilters)
2. Contactar vendedor (JetFormBuilder → Email ao author)
3. Ver perfil vendedor (Profile Builder page pública)
```
**Relations necessárias:**
```
User → N Produtos (one-to-many)
User → N Favoritos (many-to-many)
Produto → N Reviews (one-to-many, via JetReviews)
```
---
## Padrão 3: Portal de Imobiliário
**Plugins:** JetEngine + JetSmartFilters + JetWooBuilder (opcional) + JetBooking
```
CPT: Imóvel
Meta Boxes:
├── Preço (number)
├── Área m² (number)
├── Tipologia (select: T0/T1/T2/T3/T4+)
├── Tipo (select: venda/arrendamento)
├── Localização (taxonomy: Distrito → Concelho → Freguesia)
├── Coordenadas (map)
├── Galeria (gallery)
├── Características (checkbox-list: piscina, garagem, jardim...)
└── Agente responsável (posts: CPT Agente)
Query Builder:
├── Ordenação: data / preço / área
└── Meta Query: tipo = "arrendamento" (para página específica)
JetSmartFilters:
├── Range: Preço min-max
├── Range: Área min-max
├── Checkboxes: Tipologia
├── Select Hierarchical: Localização
├── Checkboxes: Características
└── Sorting: Preço ASC/DESC, Mais recentes
```
---
## Padrão 4: E-learning / Cursos
**Plugins:** JetEngine + JetFormBuilder + Profile Builder
```
CPTs:
├── Curso (conteúdo, preço, dutor)
├── Lição (sub-item do curso)
└── Avaliação (questionário)
Relations:
├── Curso → N Lições (one-to-many)
├── Utilizador → N Cursos Inscritos (many-to-many)
└── Utilizador → N Avaliações (one-to-many)
Profile Builder Pages:
├── Meus Cursos (Query: cursos onde utilizador está inscrito)
├── Continuar Lição (última lição acedida)
└── Certificados (download PDF após conclusão)
Automatização:
JetFormBuilder → compra → Inscrever no curso → Criar relação User↔Curso
```
---
## Padrão 5: Multi-Relation (Relações Aninhadas)
```
Exemplo: Agência → Clientes → Projectos → Tarefas
Relations JetEngine:
├── agencia_clientes: Agência (1) → Clientes (N)
├── cliente_projectos: Cliente (1) → Projectos (N)
└── projecto_tarefas: Projecto (1) → Tarefas (N)
No Listing Template de Agência:
├── Lista de Clientes (Relation Field Widget)
│ └── Dentro de cada Cliente: Lista de Projectos (nested listing)
│ └── Dentro de cada Projecto: Contador de Tarefas
Nota: Listings aninhados afectam performance.
Limitar a 2 níveis de profundidade.
Para listas longas, usar AJAX/JetSmartFilters paginado.
```
---
## Padrão 6: User-Generated Content (UGC)
**Plugins:** JetEngine + JetFormBuilder + Profile Builder + JetSmartFilters
```
Fluxo:
1. Utilizador logado → Dashboard (Profile Builder)
2. Formulário "Submeter Conteúdo" (JetFormBuilder)
→ Action: Create Post no CPT "Aviso" com status "pending"
→ Action: Email de notificação ao admin
3. Admin aprova → post_status = publish
4. Conteúdo aparece no listing público
Queries do Dashboard:
├── "Os meus avisos" → Query: author = %current_user_id% + status = any
└── "Aprovados" → Query: author = %current_user_id% + status = publish
Moderação rápida:
├── Admin lista pendentes com Listing Grid
└── JetFormBuilder inline para aprovar/rejeitar (Update Post Status)
```
---
## Padrão 7: JetBooking + WooCommerce + JetEngine
```
Fluxo de Reserva:
1. Página de serviço (CPT Serviço com meta JetEngine)
2. Calendário JetBooking (disponibilidade em tempo real)
3. Seleccionar datas → Form de reserva (JetFormBuilder ou JetBooking nativo)
4. Checkout WooCommerce (JetBooking cria produto temporário)
5. Pagamento → Reserva confirmada
6. Email de confirmação (JetBooking + SMTP)
7. Dashboard cliente (Profile Builder → Minhas Reservas)
Automatização n8n:
JetBooking webhook → n8n → Google Calendar → SMS (via Twilio)
```
---
## Anti-Padrões (Evitar)
### Demasiados JetPlugins Activos
```
Problema: Cada JetPlugin adiciona JS/CSS extra
Solução: Desactivar plugins não usados em cada site específico
Ferramenta: JetPlugins > Settings > Disable components não usados
```
### Listings Sem Paginação em Catálogos Grandes
```
Problema: Listing Grid sem paginação carrega todos os posts → PHP timeout
Solução: Sempre activar paginação ou Load More em listas >20 items
```
### JetSmartFilters Sem AJAX
```
Problema: URL-based filters recarregam a página inteira
Uso correcto: AJAX filters para UX fluida; URL-based apenas para SEO
```
### Meta Boxes em CCT Desnecessariamente
```
CCT não suporta taxonomies nativas, comentários, revisões.
Usar CPT quando precisar dessas funcionalidades WordPress.
```
---
*Crocoblock Patterns | Descomplicar® | 18-02-2026*
@@ -0,0 +1,240 @@
# Crocoblock — Referência Detalhada de Plugins
## JetElements
Widgets adicionais para Elementor. Não depende do JetEngine.
### Widgets Principais
| Widget | Uso |
|--------|-----|
| Animated Box | Caixa com flip/hover reveal |
| Circle Progress | Gauge circular animado |
| Timeline | Cronologia vertical/horizontal |
| Countdown Timer | Contador regressivo |
| Scroll Navigation | Navegação por secções |
| Maps | Google Maps avançado |
| Weather | Widget de meteorologia |
| Price List | Lista de preços |
| Progress Bar | Barra de progresso |
| Slider | Slider de imagens/conteúdo |
| Portfolio | Grid de portfolio com filtros |
| Testimonials | Slider de testemunhos |
---
## JetSmartFilters
Filtros AJAX para qualquer listing (JetEngine, WooCommerce, Archive).
### Tipos de Filtros
| Tipo | Quando Usar |
|------|-------------|
| Checkboxes | Categorias múltiplas |
| Radio | Opção única (ordenação) |
| Select | Dropdown de valores |
| Range | Preço, área (slider min-max) |
| Date Range | Filtrar por datas |
| Search | Pesquisa por texto |
| Rating | Filtrar por estrelas |
| Color Picker | Filtros visuais por cor |
### Configuração Essencial
```
1. Criar filter em JetSmartFilters > Filters
2. Seleccionar Source: JetEngine Query / WC_Query / WP_Query
3. Seleccionar Field: meta field / taxonomy / post field
4. Adicionar Filter Widget ao Elementor
5. Ligar ao Listing Grid via "Apply filters for" (seleccionar ID do listing)
```
### Filtros Aninhados (Dependent Filters)
```
Exemplo: Distrito → Concelho (filho depende do pai)
- Configurar taxonomy pai e filho no JetEngine
- Em JetSmartFilters: activar "Hierarchical taxonomy filter"
- Ou usar "Visible terms" com condição dinâmica
```
---
## JetWooBuilder
Templates custom para WooCommerce via Elementor.
### Templates Disponíveis
| Template | Substitui |
|----------|-----------|
| Shop (Archive) | Página principal da loja |
| Product | Single product page |
| Cart | Página de carrinho |
| Checkout | Página de checkout |
| My Account | Dashboard do cliente |
| Thank You | Página pós-compra |
| Category | Página de categoria |
### Widgets JetWooBuilder
```
Products Grid # Grid de produtos com query custom
Product Price # Preço com styling avançado
Product Gallery # Galeria com thumbnails
Add to Cart # Botão com variantes
Product Tabs # Descrição, Reviews, Atributos
Related Products # Produtos relacionados
Mini Cart # Cart dropdown
```
### Configurar Template de Produto
```
1. JetWooBuilder > Templates > Add New
2. Template Type: Product
3. Editar com Elementor — arrastar widgets JetWoo
4. Conditions: incluir "Product" (todos) ou específico por categoria/tag
```
---
## JetFormBuilder
Forms com lógica condicional, integrações e notificações.
### Tipos de Campos
```
Text, Email, Tel, URL, Number, Textarea
Date, Time, DateTime
Select, Radio, Checkboxes
File Upload, Image Upload
Hidden Field, Calculated Field
Repeater Field (subformulário dinâmico)
```
### Acções Pós-Submit
| Acção | Função |
|-------|--------|
| Send Email | Notificação email standard |
| Send Email (Advanced) | Template HTML customizado |
| Create Post | Criar CPT (com JetEngine) |
| Update User | Actualizar user meta |
| Webhook | Enviar para n8n/Make/Zapier |
| Redirect | Redirecionar após submit |
| Register User | Criar conta WP |
| Login User | Login automático |
| MailChimp/ActiveCampaign | Subscrição newsletter |
### Formulário Silencioso (Para n8n)
```php
// Receber POST externo que passa pelas validações do JetFormBuilder
// Criar formulário com campos que correspondem ao payload
// Submeter via HTTP POST para: /wp-json/jet-form-builder/v1/process-form
// Autenticação: Application Passwords
```
---
## JetPopup
Popups com triggers e condições de display avançadas.
### Triggers
| Trigger | Config |
|---------|--------|
| On Page Load | Delay em segundos |
| Exit Intent | Detecta movimento para fora |
| Scroll to Element | Scroll até % ou selector CSS |
| Click on Element | Selector CSS do trigger |
| After Inactivity | Segundos sem interacção |
| After N Page Views | Contador de visitas |
| Once Per Session | Não repete na sessão |
### Conditions (Display)
```
URL match / URL contains
User logged in / out
User role
Device type (mobile/desktop)
WooCommerce: cart empty/not empty, specific product in cart
JetEngine: post type, taxonomy, specific post
```
---
## JetBooking
Sistema de booking com calendário de disponibilidade.
### Fluxo Base
```
1. Configurar Unidade de Booking (ex: Quarto, Serviço)
2. Definir preços (diários, por horas, temporada)
3. Definir disponibilidade e check-in/check-out rules
4. Criar template de booking com Elementor
5. Adicionar Calendário + Booking Form ao template
6. Configurar emails de confirmação
```
### Integração WooCommerce
```
JetBooking → WooCommerce Product → Checkout WooCommerce
- Preço calculado automaticamente (n noites × preço/noite)
- Stock = disponibilidade do calendário
```
---
## JetBlocks
Widgets para construir headers e footers com Elementor.
### Widgets Disponíveis
```
Navigation Menu # Menu responsivo avançado
Site Logo # Logo com dark mode toggle
Cart Button # Contador de itens no carrinho
Search Box # Pesquisa com AJAX
Auth Links # Login/Logout dinâmico
User Name # Nome do utilizador logado
Breadcrumbs # Fil d'Ariane
Mobile Menu # Menu hamburger
```
---
## JetThemeCore
Theme Builder alternativo ao Elementor Pro (para licenças Free).
### Templates
```
Header / Footer
Single (Posts, CPT)
Archive (Category, Tag, CPT Archive)
Search Results
404
```
### Diferença vs Elementor Pro Theme Builder
```
JetThemeCore: Gratuito, condições básicas
Elementor Pro: Mais condições, melhor integração com Popup Builder
Recomendação: Usar Elementor Pro se disponível
```
---
*Crocoblock Plugins Reference | Descomplicar® | 18-02-2026*
+6
View File
@@ -0,0 +1,6 @@
{
"task_id": null,
"project_id": null,
"customer_id": null,
"gitea_repo": null
}
+231
View File
@@ -0,0 +1,231 @@
---
name: elementor
description: Desenvolvimento avançado com Elementor Pro e ecossistema Crocoblock. Cria widgets custom, configura Theme Builder, automatiza deploys, manipula _elementor_data via MySQL, REST API, Dynamic Tags e cache CSS.
layer: wiki
---
# /elementor - Elementor Development
Desenvolvimento avançado com Elementor Pro e Crocoblock.
## Quando Usar
- Criar widgets Elementor customizados
- Configurar Theme Builder
- Desenvolver com Crocoblock (JetEngine, JetWooBuilder)
- Optimizar performance Elementor
- Troubleshooting Elementor
## Contexto NotebookLM
ANTES de executar, consultar notebooks para contexto especializado:
| Notebook | ID | Consultar quando |
|----------|-----|-----------------|
| WordPress e Elementor | 5be0d1a6 | Sempre |
```
mcp__notebooklm__notebook_query({
notebook_id: "5be0d1a6-00f2-4cd9-b835-978cb7721601",
query: "<adaptar ao contexto do pedido do utilizador>"
})
```
Integrar insights do NotebookLM nas recomendações e decisões.
---
## Estrutura Custom Widget
```php
<?php
/**
* Custom Elementor Widget
*
* @author Descomplicar® Crescimento Digital
* @link https://descomplicar.pt
* @copyright 2026 Descomplicar®
*/
class Custom_Widget extends \Elementor\Widget_Base {
public function get_name() {
return 'custom-widget';
}
public function get_title() {
return 'Custom Widget';
}
public function get_icon() {
return 'eicon-code';
}
public function get_categories() {
return ['general'];
}
protected function register_controls() {
$this->start_controls_section(
'content_section',
[
'label' => 'Content',
'tab' => \Elementor\Controls_Manager::TAB_CONTENT,
]
);
$this->add_control(
'title',
[
'label' => 'Title',
'type' => \Elementor\Controls_Manager::TEXT,
'default' => 'Title',
]
);
$this->end_controls_section();
// Style controls
$this->start_controls_section(
'style_section',
[
'label' => 'Style',
'tab' => \Elementor\Controls_Manager::TAB_STYLE,
]
);
$this->add_control(
'title_color',
[
'label' => 'Color',
'type' => \Elementor\Controls_Manager::COLOR,
'selectors' => [
'{{WRAPPER}} .title' => 'color: {{VALUE}}',
],
]
);
$this->end_controls_section();
}
protected function render() {
$settings = $this->get_settings_for_display();
?>
<div class="custom-widget">
<h2 class="title"><?php echo esc_html($settings['title']); ?></h2>
</div>
<?php
}
}
```
## Registar Widget
```php
// functions.php ou plugin
add_action('elementor/widgets/register', function($widgets_manager) {
require_once(__DIR__ . '/widgets/custom-widget.php');
$widgets_manager->register(new \Custom_Widget());
});
```
## Theme Builder
### Header
```
Site Logo | Navigation Menu | Search | Cart Icon
Sticky: Yes | Transparent: Optional
```
### Footer
```
4 Columns: About | Links | Contact | Social
Copyright row
```
### Single Post
```
Featured Image
Title + Meta (Author, Date, Categories)
Content
Author Box
Related Posts
```
### Archive
```
Title
Filters (JetSmartFilters)
Post Grid/List (JetEngine)
Pagination
```
## Crocoblock Plugins
| Plugin | Uso Principal |
|--------|---------------|
| **JetEngine** | CPT, Meta Fields, Dynamic Content, Listings |
| **JetElements** | Widgets adicionais (Timeline, Progress Bar) |
| **JetSmartFilters** | Filtros AJAX para listings |
| **JetWooBuilder** | Templates WooCommerce avançados |
| **JetPopup** | Popups com triggers avançados |
| **JetTabs** | Tabs, Accordions, Toggle |
| **JetFormBuilder** | Forms com lógica condicional |
## JetEngine - Custom Post Type
```php
// Via código (alternativa ao JetEngine UI)
add_action('init', function() {
register_post_type('portfolio', [
'labels' => [
'name' => 'Portfolio',
'singular_name' => 'Project'
],
'public' => true,
'has_archive' => true,
'supports' => ['title', 'editor', 'thumbnail'],
'show_in_rest' => true,
]);
});
// Meta boxes via JetEngine UI recomendado
```
## Performance Tips
### 1. Lazy Load
```php
// Activar lazy load para imagens
add_filter('wp_lazy_loading_enabled', '__return_true');
```
### 2. Desactivar Google Fonts (se usar fonts locais)
```php
add_filter('elementor/frontend/print_google_fonts', '__return_false');
```
### 3. Limitar revisões
```php
// wp-config.php
define('WP_POST_REVISIONS', 3);
```
### 4. Usar Flexbox Container
```
Preferir: Flexbox Container (moderno)
Evitar: Section > Column (legacy, mais pesado)
```
### 5. Minimizar Widgets
```
Regra: M
@@ -0,0 +1,417 @@
# Elementor — Automação Programática e Pipelines
Referência técnica para manipular o Elementor sem GUI: WP-CLI, MySQL, REST API, PHP Hooks, Kits e CSS.
---
## 1. WP-CLI + Elementor (CWP)
### Formato Obrigatório CWP
```bash
PHP="/opt/alt/php-fpm83/usr/bin/php"
WP="$PHP /usr/local/bin/wp --allow-root --path=/home/USER/public_html"
```
### Comandos Essenciais
```bash
# Regenerar CSS (SEMPRE após qualquer alteração programática)
$WP elementor flush-css --regenerate
# Substituição segura de URLs (evita corrupção JSON)
# NUNCA usar wp search-replace directamente em _elementor_data
$WP elementor replace-urls $URL_OLD $URL_NEW
$WP search-replace $URL_OLD $URL_NEW --skip-columns=guid
# Importar Kit (design system completo)
$WP elementor kit import /tmp/kit.zip \
--include=site-settings,templates,content \
--overrideConditions=all
# Sincronizar esquema BD após update do plugin
$WP elementor update db
# Modo manutenção
$WP maintenance-mode activate
$WP maintenance-mode deactivate
```
### Pipeline de Deploy Completo
```bash
#!/bin/bash
# Pipeline de rebranding via WP-CLI (CWP)
PHP="/opt/alt/php-fpm83/usr/bin/php"
SITE_PATH="/home/USER/public_html"
KIT_PATH="/tmp/rebranding-2026.zip"
URL_OLD="https://staging.agencia.com"
URL_NEW="https://www.producao.com"
WP="$PHP /usr/local/bin/wp --allow-root --path=$SITE_PATH"
$WP maintenance-mode activate
$WP elementor kit import $KIT_PATH --include=site-settings,templates,content --overrideConditions=all
$WP elementor replace-urls $URL_OLD $URL_NEW
$WP search-replace $URL_OLD $URL_NEW --skip-columns=guid
$WP elementor flush-css --regenerate
$WP elementor update db
$WP maintenance-mode deactivate
```
### Avaliação do Método
| Critério | Análise |
|----------|---------|
| Complexidade | Baixa — sintaxe documentada, comportamento previsível |
| Segurança | Alta — invoca internos do WordPress, risco apenas em SSH |
| Usar quando | Deploy, purga de cache, migração de URLs, sincronização BD |
---
## 2. Manipulação Directa via MySQL
### Estrutura _elementor_data
O Elementor guarda layouts como JSON em `wp_postmeta`. Três meta keys obrigatórias:
| Meta Key | Valor |
|----------|-------|
| `_elementor_edit_mode` | `builder` |
| `_elementor_template_type` | `wp-page`, `kit`, `header`, `footer` |
| `_elementor_data` | JSON array com toda a árvore de widgets |
### Estrutura JSON Interna
```json
[{
"id": "3130e2cf",
"elType": "container",
"isInner": false,
"settings": { ... },
"elements": [{
"id": "a1b2c3d4",
"elType": "widget",
"widgetType": "button",
"settings": {
"text": "Clique aqui",
"link": { "url": "https://exemplo.com" }
}
}]
}]
```
### Query MySQL com JSON_REPLACE
```sql
-- Substituição cirúrgica num widget específico
-- SEMPRE fazer backup antes
UPDATE wp_postmeta
SET meta_value = JSON_REPLACE(
meta_value,
'$.elements.elements.settings.link.url',
'https://www.novo-destino.com/contacto'
)
WHERE meta_key = '_elementor_data'
AND post_id IN (1042, 1045, 1088);
-- OBRIGATÓRIO após qualquer UPDATE directo:
-- Executar 'wp elementor flush-css --regenerate' via WP-CLI
```
### Regra Crítica: NUNCA wp search-replace
```bash
# ERRADO — corrompe JSON silenciosamente (whitespace of death)
wp search-replace 'http://antigo.com' 'https://novo.com'
# CORRECTO — usa lógica nativa do Elementor
wp elementor replace-urls 'http://antigo.com' 'https://novo.com'
```
### Avaliação do Método
| Critério | Análise |
|----------|---------|
| Complexidade | Alta — parsing JSON, funções MySQL JSON_* |
| Segurança | Muito Baixa — sem validação, um char inválido corrompe a página |
| Usar quando | Auditoria forense, extracção analítica em massa (10.000+ posts) |
---
## 3. REST API Custom Endpoint
Para agentes externos (n8n, Make, Python) manipularem layouts via HTTP.
### Registar Endpoint (functions.php ou plugin MU)
```php
<?php
add_action('rest_api_init', function () {
register_rest_route('agencia-ai/v1', '/atualizar-layout', [
'methods' => 'POST',
'callback' => 'ai_agent_update_elementor_layout',
'permission_callback' => function() {
return current_user_can('edit_pages');
}
]);
});
function ai_agent_update_elementor_layout(WP_REST_Request $request) {
$post_id = absint($request->get_param('post_id'));
$json_data = $request->get_param('elementor_json');
// wp_slash é OBRIGATÓRIO — protege JSON de corrupção de escape
update_post_meta($post_id, '_elementor_data', wp_slash(json_encode($json_data)));
update_post_meta($post_id, '_elementor_edit_mode', 'builder');
// Invalidação de cache OBRIGATÓRIA
delete_post_meta($post_id, '_elementor_css');
if (did_action('elementor/loaded')) {
\Elementor\Plugin::$instance->files_manager->clear_cache();
}
return rest_ensure_response([
'status' => 'success',
'post_id' => $post_id
]);
}
```
### Autenticação
```bash
# Via Application Passwords (WordPress 5.6+)
curl -X POST https://site.pt/wp-json/agencia-ai/v1/atualizar-layout \
-H "Authorization: Basic $(echo -n 'user:app-password' | base64)" \
-H "Content-Type: application/json" \
-d '{"post_id": 2607, "elementor_json": [...]}'
```
### Avaliação do Método
| Critério | Análise |
|----------|---------|
| Complexidade | Média — endpoint PHP + formatar JSON _elementor_data válido |
| Segurança | Alta — usa permissões WordPress + HTTPS |
| Usar quando | Webhooks, n8n/Make, geração de páginas a partir de CRM |
---
## 4. PHP Hooks — Dynamic Tags
Para conteúdo que se actualiza em runtime sem reescrever JSON.
### Registar Dynamic Tag
```php
<?php
// Herdeiro da classe base de Dynamic Tags
class IA_Agente_Metrica_Tag extends \Elementor\Core\DynamicTags\Tag {
public function get_name() { return 'tag-agente-ia'; }
public function get_title() { return 'Relatório Dinâmico IA'; }
public function get_group() { return 'text'; }
public function get_categories() { return [\Elementor\Modules\DynamicTags\Module::TEXT_CATEGORY]; }
// Render lê do Transients API — o agente IA popula externamente
public function render() {
$dados = get_transient('ia_agente_ultima_metrica');
echo $dados ? wp_kses_post($dados) : 'A aguardar sincronização...';
}
}
// Registar
add_action('elementor/dynamic_tags/register', function($manager) {
$manager->register(new IA_Agente_Metrica_Tag());
});
```
### Padrão: Agente IA actualiza Transient
```php
// Agente IA executa isto remotamente (via eval-file ou endpoint)
set_transient('ia_agente_ultima_metrica', '<p>Taxa de conversão: 4.2%</p>', DAY_IN_SECONDS);
```
### Avaliação do Método
| Critério | Análise |
|----------|---------|
| Complexidade | Alta — OOP PHP, Event Loop WordPress, DynamicTags |
| Segurança | Muito Alta — lógica server-side, CI/CD, sem exposição JSON |
| Usar quando | Conteúdo em tempo real (cotações, stock, métricas) — requer Elementor **Pro** |
---
## 5. Site Kits — Estrutura e Geração
### Estrutura ZIP de um Kit
```
kit.zip
├── manifest.json # Índice mestre — mapeamento de todos os ficheiros
├── site-settings.json # Cores globais, tipografia, configurações do tema
├── header-template.json # Template de cabeçalho
├── footer-template.json # Template de rodapé
└── page-template.json # Templates de páginas
```
### manifest.json (estrutura mínima)
```json
{
"templates": [
{
"title": "Header Principal",
"type": "header",
"fileName": "header-template.json"
}
],
"site_settings": {
"title": "Site Settings",
"fileName": "site-settings.json"
}
}
```
### Geração em Python
```python
import json, zipfile, os
kit_settings = {
"version": "0.4",
"title": "Site Settings",
"settings": {
"system_colors": [
{"_id": "primary", "title": "Primária", "color": "#1E40AF"},
{"_id": "secondary", "title": "Secundária", "color": "#10B981"}
],
"system_typography": [
{"_id": "primary", "title": "Principal", "typography_font_family": "Inter"}
]
}
}
manifest = {
"templates": [],
"site_settings": {"title": "Site Settings", "fileName": "site-settings.json"}
}
os.makedirs('/tmp/kit_build', exist_ok=True)
with open('/tmp/kit_build/site-settings.json', 'w') as f:
json.dump(kit_settings, f)
with open('/tmp/kit_build/manifest.json', 'w') as f:
json.dump(manifest, f)
with zipfile.ZipFile('/tmp/agencia-kit.zip', 'w') as z:
z.write('/tmp/kit_build/manifest.json', 'manifest.json')
z.write('/tmp/kit_build/site-settings.json', 'site-settings.json')
# Importar via WP-CLI (CWP)
# /opt/alt/php-fpm83/usr/bin/php /usr/local/bin/wp elementor kit import /tmp/agencia-kit.zip \
# --include=site-settings,templates --overrideConditions=all \
# --allow-root --path=/home/USER/public_html
```
---
## 6. CSS — Invalidação de Cache
### Localização dos Ficheiros CSS
```
wp-content/uploads/elementor/css/post-{ID}.css # CSS por página
wp-content/uploads/elementor/css/global.css # CSS global
```
### Invalidação Programática (PHP)
```php
// Após qualquer alteração directa a _elementor_data ou post_meta
if (did_action('elementor/loaded')) {
\Elementor\Plugin::$instance->files_manager->clear_cache();
}
// Alternativa: apagar meta específica
delete_post_meta($post_id, '_elementor_css');
```
### Rebranding Global — Kit Activo
```php
// Obter ID do kit activo
$kit_id = get_option('elementor_active_kit');
// Ler configurações globais
$settings = get_post_meta($kit_id, '_elementor_page_settings', true);
// Alterar cor primária
if (!empty($settings['system_colors'])) {
foreach ($settings['system_colors'] as &$cor) {
if ($cor['_id'] === 'primary') {
$cor['color'] = '#FF0000';
}
}
}
// Guardar (wp_slash protege o JSON)
update_post_meta($kit_id, '_elementor_page_settings', $settings);
// Invalidar cache CSS
if (did_action('elementor/loaded')) {
\Elementor\Plugin::$instance->files_manager->clear_cache();
}
```
---
## 7. Regras Críticas e Riscos
### Nunca fazer
| Erro | Consequência | Correcto |
|------|-------------|----------|
| `wp search-replace` em `_elementor_data` | Corrupção silenciosa do JSON, layout desaparece | `wp elementor replace-urls` |
| UPDATE MySQL sem flush-css | CSS desactualizado, design dessincronizado | Sempre `wp elementor flush-css --regenerate` |
| `update_post_meta` sem `wp_slash()` | Corrupção de escapes no JSON | `wp_slash(json_encode($data))` |
| Múltiplos agentes a editar o mesmo post | Sobreposição de dados | Locks ou filas de processamento |
### IDs de Widgets São Aleatórios
Os IDs de nós JSON (ex: `"id":"3130e2cf"`) são alfanuméricos aleatórios. Para manipular um widget específico via MySQL ou REST API:
1. Fazer parsing da árvore JSON completa primeiro
2. Encontrar o widget pelo `widgetType` ou conteúdo
3. Só então operar no ID correcto
### Limitações Elementor Free vs Pro
| Funcionalidade | Free | Pro |
|----------------|------|-----|
| `wp elementor flush-css` | ✅ | ✅ |
| `wp elementor kit import` | ✅ | ✅ |
| Dynamic Tags | ❌ | ✅ |
| Theme Builder (header/footer) | ❌ | ✅ |
---
## 8. MCP Elementor (Integração Futura)
O projecto **Ultimate Elementor MCP** expõe ~60 ferramentas para manipulação via linguagem natural:
- `create_container`, `create_heading`, `delete_element`
- `get_page_structure` — lê árvore JSON (resolve problema dos IDs aleatórios)
- `update_widget_settings`
**Autenticação:** Application Passwords ou JWT (mesma arquitectura do REST API).
**Quando usar:** Assistentes conversacionais, automações complexas onde formatar JSON raw em Python seria impraticável.
**Referência:** [Ultimate Elementor MCP — LobeHub](https://lobehub.com/mcp/mbrown1837-ultimate-elementor-mcp)
---
*Automação Elementor | Descomplicar® | Baseado em: Manipulação Programática do Elementor — Automação via IA | 18-02-2026*
+8
View File
@@ -0,0 +1,8 @@
# skills
## Documentação
- [README](README.md)
- [AGENTS](AGENTS.md)
- [STATUS](STATUS.md)
- [MEMORY](MEMORY.md)
+176
View File
@@ -0,0 +1,176 @@
---
name: jetengine
description: Desenvolvimento com JetEngine (Crocoblock). Cobre CPT, CCT, Meta Boxes, Taxonomias, Relations, Query Builder, Listing Grid/Map, Dynamic Content, Dynamic Tags, Macros, Profile Builder, REST API e automação headless.
layer: wiki
---
# /jetengine — JetEngine Development
JetEngine é o núcleo do ecossistema Crocoblock: cria CPT, CCT, Meta Boxes, Relations, Queries e Listings com conteúdo dinâmico para Elementor.
## CPT vs CCT — Escolha Crítica
| | Custom Post Type (CPT) | Custom Content Type (CCT) |
|--|----------------------|--------------------------|
| **Storage** | `wp_posts` + `wp_postmeta` | Tabela SQL própria `wp_jet_cct_{slug}` |
| **Performance** | Lenta em queries complexas | Muito rápida (colunas reais) |
| **Compatibilidade** | Total WP ecosystem | Limitada (sem taxonomies nativas) |
| **REST API** | `/wp-json/wp/v2/{slug}` | `/wp-json/jet-cct/{slug}` |
| **Automação** | `wp_insert_post()` | `CCT_Data_Handler` ou SQL directo |
| **Usar quando** | Conteúdo editorial, SEO, plugins WP | Alta performance, CRUD automatizado, dashboards |
**Regra prática:** CPT para conteúdo público/SEO; CCT para dados internos, integrações e alta escala.
## Criar CPT
```
JetEngine > Post Types > Add New
├── Slug (único, sem espaços, lowercase)
├── Labels (Singular / Plural)
├── Suporte: title, editor, thumbnail, excerpt, custom-fields
├── REST API: Activar para integração externa
├── Archive: Activar se precisar de página de listagem
└── Admin Columns: Mostrar meta fields na tabela admin
```
## Meta Boxes e Campos
### Tipos de Campos
| Tipo | Uso |
|------|-----|
| `text` | Texto simples |
| `textarea` | Texto longo |
| `wysiwyg` | Editor rich text |
| `number` | Valor numérico |
| `select` | Dropdown de opções |
| `checkbox` | Verdadeiro/falso |
| `checkbox-list` | Múltipla selecção |
| `radio` | Opção única |
| `date` | Data (timestamp) |
| `media` | Upload de ficheiro/imagem |
| `gallery` | Múltiplas imagens |
| `repeater` | Campo repetível (sub-campos) |
| `posts` | Relação simples (seleccionar posts) |
| `color-picker` | Cor HEX |
| `map` | Coordenadas geográficas |
| `html` | HTML estático (apenas display) |
### Repeater — Estrutura
```
Repeater Field "galeria_servicos":
├── sub-campo: imagem (media)
├── sub-campo: titulo (text)
└── sub-campo: descricao (textarea)
Aceder em PHP:
$items = get_post_meta($post_id, 'galeria_servicos', true);
foreach ($items as $item) { ... }
```
## Relations
### Tipos de Relação
| Tipo | Exemplo |
|------|---------|
| One-to-Many | 1 Empresa → N Colaboradores |
| Many-to-Many | N Projectos ↔ N Tags custom |
| Self-relation | Artigo relacionado com outros artigos |
| User-to-Post | Utilizador → N Reservas |
### Tabela SQL das Relações
```sql
-- wp_jet_rel_default
-- parent_object_id | child_object_id | rel_id (hash da relação)
-- Ler todos os filhos de um pai
SELECT child_object_id
FROM wp_jet_rel_default
WHERE parent_object_id = 123
AND rel_id = 'relacao_empresa_colaboradores';
-- Ler o pai a partir de um filho
SELECT parent_object_id
FROM wp_jet_rel_default
WHERE child_object_id = 456
AND rel_id = 'relacao_empresa_colaboradores';
```
**Importante:** Nunca editar esta tabela directamente via SQL para criar relações — usar a API PHP do JetEngine (ver `references/automation.md`).
## Query Builder
Substitui o `WP_Query` com interface visual. Ligar ao Listing Grid.
### Tipos de Query
| Tipo | Usa |
|------|-----|
| Posts | CPT, posts, páginas |
| Terms | Taxonomias |
| Users | Utilizadores WP |
| Custom Content Type | Tabelas CCT |
| REST API | Endpoint externo |
### Configurar Query Posts
```
JetEngine > Query Builder > Add New
├── Query Type: Posts
├── Post Type: [seleccionar CPT]
├── Order By: date / meta_value / rand / menu_order
├── Meta Query: adicionar condições de meta fields
├── Tax Query: filtrar por taxonomia
└── Author: específico ou current user
```
### Usar no Listing Grid
```
Elemento Elementor: Listing Grid
├── Listing Item: [seleccionar Listing Template]
├── Use Custom Query: YES → seleccionar Query criado acima
├── Items per page: N
└── Pagination: Activar se necessário
```
## Listing Grid e Listing Template
### Criar Listing Template
```
JetEngine > Listings > Add New
├── Template Type: Default / Popup
├── Content From: Posts / CCT / User / Term
└── Editar com Elementor → usar Dynamic Tags
```
### Dynamic Tags no Listing Template
```
Post Title → {{ post_title }}
Field de Meta → Meta Field → seleccionar nome do campo
Imagem → Featured Image → meta field media
Link → Post URL / Custom Field URL
Relação → Relation Field
```
### Listing Map
```
JetEngine > Listings > Add New > Template Type: Map
├── Configurar campo de coordenadas (tipo: map no meta box)
├── Google Maps API Key: WP Admin > JetEngine > Settings
└── Ligar ao Query Builder
```
## Dynamic Content e Macros
### Macros Comuns
```
%current_user_id% # ID do utilizador logado
%current_post_id%
@@ -0,0 +1,226 @@
<?php
/**
* Plugin Name: Descomplicar JetEngine Bridge
* Description: API Endpoint customizado para manipulação atómica de CCTs e Relações JetEngine.
* Expõe /descomplicar/v1/manage para integração com n8n e agentes IA.
* Version: 1.1.0
* Author: Descomplicar® Crescimento Digital
* Author URI: https://descomplicar.pt
*
* Instalar em: /wp-content/mu-plugins/descomplicar-jet-bridge.php
* Autenticação: WordPress Application Passwords
*/
if ( ! defined( 'ABSPATH' ) ) exit;
class Descomplicar_Jet_Bridge {
public function __construct() {
add_action( 'rest_api_init', [ $this, 'register_routes' ] );
}
public function register_routes() {
register_rest_route( 'descomplicar/v1', '/manage', [
'methods' => 'POST',
'callback' => [ $this, 'handle_request' ],
'permission_callback' => function() {
// Autenticar via WordPress Application Passwords no n8n:
// Header: Authorization: Basic base64(user:app-password)
return current_user_can( 'edit_posts' );
},
]);
}
public function handle_request( WP_REST_Request $request ) {
$params = $request->get_json_params();
$action = sanitize_text_field( $params['action'] ?? '' );
if ( ! function_exists( 'jet_engine' ) || ! class_exists( 'Jet_Engine' ) ) {
return new WP_Error( 'no_jet_engine', 'JetEngine não está activo.', [ 'status' => 500 ] );
}
switch ( $action ) {
case 'create_cct_item':
case 'update_cct_item':
return $this->handle_cct_write( $params );
case 'get_cct_item':
return $this->handle_cct_get( $params );
case 'delete_cct_item':
return $this->handle_cct_delete( $params );
case 'manage_relation':
return $this->handle_relation( $params );
default:
return new WP_Error(
'invalid_action',
'Acção inválida. Disponíveis: create_cct_item, update_cct_item, get_cct_item, delete_cct_item, manage_relation',
[ 'status' => 400 ]
);
}
}
/**
* Criar ou actualizar item CCT
*
* Payload: {
* "action": "create_cct_item",
* "cct_slug": "faturas",
* "item_data": {
* "titulo": "Fatura #1024",
* "valor": 500,
* "estado": "pendente"
* }
* }
* Para update, incluir "_ID": 42 em item_data.
*/
private function handle_cct_write( array $params ) {
$cct_slug = sanitize_key( $params['cct_slug'] ?? '' );
$item_data = $params['item_data'] ?? [];
if ( empty( $cct_slug ) ) {
return new WP_Error( 'missing_slug', 'cct_slug é obrigatório.', [ 'status' => 400 ] );
}
if ( empty( $item_data ) ) {
return new WP_Error( 'missing_data', 'item_data é obrigatório.', [ 'status' => 400 ] );
}
$handler = $this->get_cct_handler( $cct_slug );
if ( is_wp_error( $handler ) ) return $handler;
$result_id = $handler->update_item( $item_data );
if ( ! $result_id ) {
return new WP_Error( 'db_error', 'Falha ao guardar item na base de dados.', [ 'status' => 500 ] );
}
return rest_ensure_response( [
'success' => true,
'item_id' => $result_id,
'action' => isset( $item_data['_ID'] ) ? 'updated' : 'created',
] );
}
/**
* Ler item CCT por ID
*
* Payload: { "action": "get_cct_item", "cct_slug": "faturas", "item_id": 42 }
*/
private function handle_cct_get( array $params ) {
$cct_slug = sanitize_key( $params['cct_slug'] ?? '' );
$item_id = absint( $params['item_id'] ?? 0 );
if ( empty( $cct_slug ) || ! $item_id ) {
return new WP_Error( 'missing_data', 'cct_slug e item_id são obrigatórios.', [ 'status' => 400 ] );
}
$handler = $this->get_cct_handler( $cct_slug );
if ( is_wp_error( $handler ) ) return $handler;
$item = $handler->get_item( $item_id );
if ( ! $item ) {
return new WP_Error( 'not_found', "Item {$item_id} não encontrado no CCT '{$cct_slug}'.", [ 'status' => 404 ] );
}
return rest_ensure_response( [
'success' => true,
'item' => $item,
] );
}
/**
* Eliminar item CCT
*
* Payload: { "action": "delete_cct_item", "cct_slug": "faturas", "item_id": 42 }
*/
private function handle_cct_delete( array $params ) {
$cct_slug = sanitize_key( $params['cct_slug'] ?? '' );
$item_id = absint( $params['item_id'] ?? 0 );
if ( empty( $cct_slug ) || ! $item_id ) {
return new WP_Error( 'missing_data', 'cct_slug e item_id são obrigatórios.', [ 'status' => 400 ] );
}
$handler = $this->get_cct_handler( $cct_slug );
if ( is_wp_error( $handler ) ) return $handler;
$result = $handler->delete_item( $item_id );
return rest_ensure_response( [
'success' => (bool) $result,
'item_id' => $item_id,
'deleted' => (bool) $result,
] );
}
/**
* Criar ou remover relação entre dois items
*
* Payload: {
* "action": "manage_relation",
* "rel_key": "clientes-projetos",
* "parent_id": 10,
* "child_id": 20,
* "connect": true
* }
*/
private function handle_relation( array $params ) {
$rel_key = sanitize_text_field( $params['rel_key'] ?? '' );
$parent_id = absint( $params['parent_id'] ?? 0 );
$child_id = absint( $params['child_id'] ?? 0 );
$connect = (bool) ( $params['connect'] ?? true );
if ( empty( $rel_key ) || ! $parent_id || ! $child_id ) {
return new WP_Error(
'missing_data',
'rel_key, parent_id e child_id são obrigatórios.',
[ 'status' => 400 ]
);
}
if ( ! class_exists( '\Jet_Engine\Relations\Manager' ) ) {
return new WP_Error( 'no_relations', 'Módulo de Relações JetEngine não disponível.', [ 'status' => 500 ] );
}
\Jet_Engine\Relations\Manager::get_instance()->process_relation(
$rel_key,
$parent_id,
$child_id,
$connect
);
return rest_ensure_response( [
'success' => true,
'action' => $connect ? 'connected' : 'disconnected',
'rel_key' => $rel_key,
'parent' => $parent_id,
'child' => $child_id,
] );
}
/**
* Obter CCT handler com verificação de erro
*/
private function get_cct_handler( string $cct_slug ) {
$cct_module = jet_engine()->modules->get_module( 'custom-content-types' );
if ( ! $cct_module ) {
return new WP_Error( 'no_cct_module', 'Módulo CCT não está activo no JetEngine.', [ 'status' => 500 ] );
}
$manager = $cct_module->instance->manager;
$type = $manager->get_content_type_instance( $cct_slug );
if ( ! $type ) {
return new WP_Error( 'invalid_cct', "CCT '{$cct_slug}' não encontrado.", [ 'status' => 404 ] );
}
return $type->get_handler_instance();
}
}
new Descomplicar_Jet_Bridge();
@@ -0,0 +1,668 @@
# JetEngine — Automação Headless e Integração com Agentes IA
Arquitectura técnica para manipular o JetEngine sem wp-admin: via PHP interno, REST API, SQL e n8n.
---
## 1. Estrutura de Dados — Base de Dados
### A. Custom Post Types (CPT) — Estrutura Standard WP
```sql
-- Dados em wp_posts
SELECT * FROM wp_posts WHERE post_type = 'meu_cpt' AND post_status = 'publish';
-- Meta fields em wp_postmeta
SELECT meta_key, meta_value FROM wp_postmeta WHERE post_id = 123;
```
**Limitação:** Queries complexas com múltiplos meta fields são lentas (JOIN por cada meta key).
### B. Custom Content Types (CCT) — Tabelas SQL Próprias
Cada CCT tem tabela dedicada: `wp_jet_cct_{slug_do_cct}`
```sql
-- Exemplo: CCT "faturas" → tabela wp_jet_cct_faturas
-- Colunas são os campos definidos no JetEngine
-- Ler todos os itens activos
SELECT * FROM wp_jet_cct_faturas WHERE cct_status = 'active';
-- Filtrar por campo
SELECT * FROM wp_jet_cct_clientes
WHERE cidade = 'Lisboa'
AND cct_status = 'publish'
ORDER BY _created_at DESC
LIMIT 20;
-- Campos especiais em todos os CCT:
-- _ID → ID numérico único
-- cct_status → publish / draft
-- cct_author_id → ID do utilizador criador
-- _created_at → Unix timestamp de criação
-- _modified → Unix timestamp de modificação
```
**Vantagem:** SQL directo é seguro e extremamente rápido. Ideal para leitura em dashboards e n8n.
### C. Relations — Tabelas wp_jet_rel_default e _meta
O JetEngine NÃO guarda relações em `wp_postmeta`. Tem tabela própria.
```sql
-- wp_jet_rel_default: relações básicas
-- parent_object_id | child_object_id | rel_id (hash/slug da relação)
-- Ler filhos de um pai
SELECT child_object_id
FROM wp_jet_rel_default
WHERE parent_object_id = 123
AND rel_id = 'relacao_clientes_projetos';
-- Ler pai a partir de filho
SELECT parent_object_id
FROM wp_jet_rel_default
WHERE child_object_id = 456
AND rel_id = 'relacao_clientes_projetos';
-- Contar relações
SELECT parent_object_id, COUNT(*) as total
FROM wp_jet_rel_default
WHERE rel_id = 'relacao_empresa_colaboradores'
GROUP BY parent_object_id;
```
**wp_jet_rel_default_meta** — metadados específicos de uma relação (ex: papel do actor num filme):
```sql
-- Exemplo: condição de uso de um veículo numa relação utilizador↔veículo
SELECT meta_key, meta_value
FROM wp_jet_rel_default_meta
WHERE rel_id = 8
AND parent_object_id = 45
AND child_object_id = 1020;
-- UPSERT massivo para sincronizações ETL (com integridade referencial)
START TRANSACTION;
INSERT INTO wp_jet_rel_default (rel_id, parent_object_id, child_object_id)
VALUES (8, 45, 1020), (8, 45, 1021), (8, 92, 1055)
ON DUPLICATE KEY UPDATE parent_object_id = VALUES(parent_object_id);
INSERT INTO wp_jet_rel_default_meta (rel_id, parent_object_id, child_object_id, meta_key, meta_value)
VALUES (8, 45, 1020, 'condicao_uso', 'Alugado')
ON DUPLICATE KEY UPDATE meta_value = VALUES(meta_value);
COMMIT;
-- OBRIGATÓRIO após SQL directo: chamar wp_cache_flush() + jet_smart_filters()->indexer->index_all()
```
**Nota:** Em relações críticas com muito tráfego, o JetEngine pode isolar por relação em tabela própria (`wp_jet_rel_81_meta`), reduzindo table locks.
**Guardrail:** NUNCA inserir directamente via SQL para criar relações em ambientes activos. Usar API PHP (ver secção 2). SQL directo só para sincronizações ETL massivas (centenas de milhares de linhas).
### D. Custom Meta Storage para CPT (Tabela Dedicada)
Alternativa ao `wp_postmeta` para CPTs com muitos campos e consultas complexas.
```
JetEngine > Post Types > Edit > Custom Meta Storage: Activar
→ Cria tabela dedicada: wp_{cpt_slug}_meta
(ex: wp_imoveis_meta com colunas tipadas por campo)
```
```sql
-- Com Custom Meta Storage activo, cada campo é uma coluna
SELECT preco, area_m2, tipologia, localizacao
FROM wp_imoveis_meta
WHERE preco BETWEEN 150000 AND 350000
AND tipologia IN ('T2', 'T3')
ORDER BY preco ASC;
-- Joins com wp_posts permanecem necessários para title/date
SELECT p.ID, p.post_title, m.preco, m.area_m2
FROM wp_posts p
JOIN wp_imoveis_meta m ON m.post_id = p.ID
WHERE p.post_status = 'publish'
AND m.preco < 300000;
```
**Quando usar:** CPTs com >10 campos numéricos/date, queries com múltiplos filtros simultâneos, alternativa a CCT quando se precisa de taxonomias e comentários nativos WP.
### E. Estrutura na wp_options
```sql
-- Configurações de CPTs
SELECT option_value FROM wp_options WHERE option_name = 'jet_engine_cpt';
-- Taxonomias
SELECT option_value FROM wp_options WHERE option_name = 'jet_engine_tax';
-- Relações
SELECT option_value FROM wp_options WHERE option_name = 'jet_engine_relations';
-- Meta Boxes
SELECT option_value FROM wp_options WHERE option_name = 'jet_engine_meta_boxes';
```
**Aviso:** São PHP serialized arrays. NUNCA editar via SQL string replace — vai corromper. Usar sempre `unserialize → edit → serialize` em PHP.
---
## 2. PHP — API Interna JetEngine
### Criar/Actualizar Item CCT
```php
<?php
// Usar CCT Data Handler (mantém hooks, validações e cache)
// Mais seguro que SQL directo para escrita
$cct_slug = 'faturas';
$item_data = [
'titulo' => 'Fatura #1024',
'valor' => 500.00,
'status' => 'pendente',
'cliente' => 'Empresa X Lda',
// Para update, incluir _ID existente:
// '_ID' => 15
];
// Aceder ao módulo CCT
if (function_exists('jet_engine')) {
$cct_module = jet_engine()->modules->get_module('custom-content-types');
if ($cct_module) {
$manager = $cct_module->instance->manager;
$type = $manager->get_content_type_instance($cct_slug);
if ($type) {
$result = $type->get_handler_instance()->update_item($item_data);
// $result = ID do item criado, ou false em caso de erro
}
}
}
```
### Gerir Relações Programaticamente
```php
<?php
// Método 1: process_relation (simples, para connect/disconnect)
// IMPORTANTE: Executar após o hook 'init' com prioridade > 12
if (class_exists('\Jet_Engine\Relations\Manager')) {
$relations_manager = \Jet_Engine\Relations\Manager::get_instance();
$relations_manager->process_relation(
$rel_id, // ex: 'relacao_empresa_colaboradores'
$parent_id,
$child_id,
true // true = conectar, false = desconectar
);
}
// Método 2: API completa (update com contexto — mais robusto)
function ia_conectar_com_contexto($cliente_id, $documento_id) {
$rel_id_numerico = 15; // ID numérico da relação (ver JetEngine > Relations)
$relation = jet_engine()->relations->get_active_relations($rel_id_numerico);
if ($relation) {
$relation->set_update_context('parent'); // perspectiva: 'parent' ou 'child'
$relation->update($cliente_id, $documento_id);
// Limpar cache do Query Builder após actualizar relações
\Jet_Engine\Query_Builder\Manager::instance()->listings_cache->clear();
}
}
```
### Criar/Actualizar CPT Post
```php
<?php
// CPTs usam funções standard do WordPress
$post_data = [
'post_title' => 'Novo Projecto',
'post_content' => '',
'post_status' => 'publish',
'post_type' => 'projecto',
];
$post_id = wp_insert_post($post_data);
if (!is_wp_error($post_id)) {
update_post_meta($post_id, 'cliente_id', 45);
update_post_meta($post_id, 'valor_contrato', 5000);
update_post_meta($post_id, 'data_inicio', '2026-03-01');
}
```
### Manipular Estrutura (Onboarding Programático)
```php
<?php
// Adicionar novo CPT à configuração do JetEngine
// CUIDADO: substitui configurações existentes se não fizer merge
$current_cpts = get_option('jet_engine_cpt', []);
$new_cpt = [
'slug' => 'imovel',
'singular_name' => 'Imóvel',
'name' => 'Imóveis',
'show_in_rest' => true,
'supports' => ['title', 'editor', 'thumbnail', 'custom-fields'],
// ... outras configurações
];
$current_cpts[] = $new_cpt;
update_option('jet_engine_cpt', $current_cpts);
// Obrigatório: regenerar permalinks após alterar CPTs
flush_rewrite_rules();
```
### Registar Meta Box Programaticamente (Hook)
```php
<?php
// Hook: jet-engine/meta-boxes/register-instances
// Ideal para plugins de agência (controlo por código, imutável pelo cliente)
add_action('jet-engine/meta-boxes/register-instances', function($meta_manager) {
$meta_fields = [
[
'title' => 'ID Remoto de Sincronização',
'name' => '_ia_sync_id',
'object_type' => 'field',
'type' => 'text',
'width' => '50%',
],
[
'title' => 'Score de Auditoria',
'name' => '_score_auditoria',
'object_type' => 'field',
'type' => 'number',
'width' => '50%',
],
];
$post_type = 'documento_legal';
if (!class_exists('Jet_Engine_CPT_Meta')) {
require $meta_manager->component_path('post.php');
}
$meta_manager->store_fields($post_type, $meta_fields, 'post_type');
new Jet_Engine_CPT_Meta($post_type, $meta_fields, 'Controlo IA', 'normal', 'high', [
'object_type' => 'post',
'allowed_post_type' => [$post_type],
'name' => 'Controlo de Metadados IA',
]);
});
```
### WP-CLI Batch Import para CCT
```php
<?php
// Ficheiro: import_cct_data.php
// Executar: wp eval-file import_cct_data.php
if (!class_exists('\Jet_Engine\Modules\Custom_Content_Types\Module')) {
WP_CLI::error('Módulo CCT não está activo.');
}
$cct_slug = 'veiculos';
$manager = \Jet_Engine\Modules\Custom_Content_Types\Module::instance()->manager;
$content_type = $manager->get_content_types($cct_slug);
if (!$content_type) {
WP_CLI::error("CCT '{$cct_slug}' não encontrado.");
}
$registos = json_decode(file_get_contents('/tmp/veiculos.json'), true);
$sucesso = 0;
foreach ($registos as $dados) {
// db->insert() respeita sanitizações e integridade do CCT
if ($content_type->db->insert($dados)) {
$sucesso++;
}
// Libertar memória em loops grandes
if ($sucesso % 500 === 0) {
wp_cache_flush();
}
}
wp_cache_flush();
WP_CLI::success("Importação concluída. Registos criados: {$sucesso}");
```
```bash
# Execução no servidor CWP:
PHP="/opt/alt/php-fpm83/usr/bin/php"
WP="$PHP /usr/local/bin/wp --allow-root --path=/home/USER/public_html"
$WP eval-file /tmp/import_cct_data.php
```
---
## 3. REST API JetEngine
### Endpoints
| Tipo | Endpoint | Método |
|------|----------|--------|
| CPT | `/wp-json/wp/v2/{cpt_slug}` | GET / POST / PUT / DELETE |
| CCT | `/wp-json/jet-cct/{cct_slug}` | GET / POST / PUT / DELETE |
| Relação | `/wp-json/jet-rel/{relation_id}` | GET / POST |
**Pré-requisito:** Activar REST API nas configurações do CPT/CCT/Relação em JetEngine.
### Autenticação
```bash
# Application Passwords (WP 5.6+) — recomendado para n8n
# Criar em: WP Admin > Users > Edit > Application Passwords
# Header de autenticação
Authorization: Basic $(echo -n 'username:app-password' | base64)
```
### Criar/Ler Item CCT via REST
```bash
# Criar registo no CCT "clientes"
curl -X POST https://site.pt/wp-json/jet-cct/clientes \
-H "Authorization: Basic dXNlcjpwYXNz..." \
-H "Content-Type: application/json" \
-d '{
"nome_cliente": "Empresa X Lda",
"email": "contacto@empresax.com",
"nif": "123456789",
"cct_status": "publish"
}'
# Resposta: { "_ID": 42, ... }
# Pesquisa com filtros — parâmetros CCT REST API
# _limit, _offset: paginação
# _cct_search: pesquisa de texto
# _cct_search_by: campo específico
curl "https://site.pt/wp-json/jet-cct/clientes?_limit=20&_offset=0&_cct_search=Lisboa&_cct_search_by=cidade"
# Header de resposta inclui: Jet-Query-Total (total de registos para paginação)
```
### Actualizar Relação via REST API
```bash
# POST para /wp-json/jet-rel/{relation_id}
# store_items_type: "update" (aditivo) | "replace" (destrutivo!) | "disconnect" (remover)
curl -X POST https://site.pt/wp-json/jet-rel/22 \
-H "Authorization: Basic dXNlcjpwYXNz..." \
-H "Content-Type: application/json" \
-d '{
"parent_id": 4500,
"child_id": 1020,
"context": "child",
"store_items_type": "update",
"meta": {
"etapa_validacao": "Aprovado",
"score_auditoria": 88.5,
"data_processamento": "2026-02-18T08:30"
}
}'
```
**Atenção `store_items_type`:**
- `update` — adiciona à relação existente (safe)
- `replace` — **DESTRÓI todas as relações existentes** e substitui (perigoso!)
- `disconnect` — remove a relação específica
### Criar Post (CPT) via REST
```bash
curl -X POST https://site.pt/wp-json/wp/v2/projecto \
-H "Authorization: Basic dXNlcjpwYXNz..." \
-H "Content-Type: application/json" \
-d '{
"title": "Projecto Website ABC",
"status": "publish",
"meta": {
"cliente_id": 45,
"valor_contrato": 5000
}
}'
```
### Campos Repeater via REST
```json
{
"galeria_servicos": [
{ "titulo": "Serviço A", "descricao": "Descrição A" },
{ "titulo": "Serviço B", "descricao": "Descrição B" }
]
}
```
---
## 4. Integração n8n
### Leitura (MySQL Node — mais rápido)
```javascript
// n8n MySQL Node — ler CCT directamente da BD
// Muito mais rápido que REST API para leitura em massa
// Query example:
SELECT
_ID,
nome_cliente,
email,
cidade,
cct_status,
_created_at
FROM wp_jet_cct_clientes
WHERE cct_status = 'publish'
ORDER BY _created_at DESC
LIMIT 100;
```
**Vantagem:** 100x mais rápido que HTTP Request ao WP REST API.
### Escrita (HTTP Request Node — via REST)
```javascript
// n8n HTTP Request Node
// Method: POST
// URL: https://site.pt/wp-json/jet-cct/faturas
// Authentication: Basic Auth
// Body (JSON):
{
"numero": "{{ $json.fatura_numero }}",
"cliente_id": "{{ $json.cliente_id }}",
"valor": "{{ $json.total }}",
"cct_status": "publish"
}
```
**Usar REST para escrita** (garante hooks JetEngine e validações).
### Webhooks via JetFormBuilder
```
JetFormBuilder pode disparar Webhook para n8n:
1. Criar formulário no JetFormBuilder
2. Post-submit Action: Webhook
3. URL: https://n8n.descomplicar.pt/webhook/jetengine
4. Formulários silenciosos: receber POST externos que passam pelas validações JetEngine
```
---
## 5. Tabela de Métodos
| Método | Complexidade | Performance | Risco | Caso de Uso |
|--------|-------------|-------------|-------|-------------|
| REST API | Baixa | Média | Baixo | Inserir dados externos via n8n |
| PHP (Classes Internas) | Média | Alta | Baixo | Plugins custom, scripts de migração |
| SQL Directo (CCT) | Alta | Muito Alta | Médio | Bulk updates, leitura analytics |
| SQL Directo (Meta) | Alta | Baixa | **Alto** | **Evitar** — corrupção de arrays |
| WP-CLI Custom | Média | Alta | Baixo | Manutenção, cron jobs |
---
## 6. Guardrails Críticos
### Cache JetEngine
```bash
# Após alterar estruturas via PHP/SQL na wp_options,
# forçar regeneração de cache:
PHP="/opt/alt/php-fpm83/usr/bin/php"
WP="$PHP /usr/local/bin/wp --allow-root --path=/home/USER/public_html"
$WP cache flush
$WP rewrite flush
$WP elementor flush-css --regenerate
```
**Cache do Query Builder** — obrigatório após alterações SQL directas:
```php
<?php
// Limpar cache de listagens após SQL bulk update
\Jet_Engine\Query_Builder\Manager::instance()->listings_cache->clear();
// Recalcular Indexer do JetSmartFilters (se filtros activos)
if (function_exists('jet_smart_filters')) {
jet_smart_filters()->indexer->index_all();
}
// Cache do WordPress
wp_cache_flush();
```
**Regra crítica:** Alterações SQL directas em `wp_jet_cct_*` ou `wp_jet_rel_default` NÃO invalidam automaticamente o Object Cache nem os transients do Query Builder. O site apresentará dados obsoletos até ao flush.
### NUNCA Editar Campos Serializados via SQL String Replace
```sql
-- ERRADO — corrompe o array PHP serializado
UPDATE wp_options
SET option_value = REPLACE(option_value, 'Imóveis', 'Propriedades')
WHERE option_name = 'jet_engine_cpt';
-- CORRECTO — usar PHP
$cpts = get_option('jet_engine_cpt');
// Fazer alterações via PHP
update_option('jet_engine_cpt', $cpts);
```
### IDs de Relações em Migrações
```
Problema: Ao migrar dados entre ambientes, IDs podem mudar → relações quebram
Solução: Usar Slugs como referência no código; converter para ID antes de gravar
Usar: get_posts(['name' => 'slug-do-post', 'post_type' => 'meu_cpt'])
```
### Repeater Fields via SQL
```
Repeater fields são PHP serialize() ou JSON dependendo da configuração.
NUNCA fazer string replace em SQL.
Usar sempre: unserialize() → editar array → serialize() → update_post_meta()
```
---
## 7. MU-Plugin: Wrapper de Endpoints Custom
Para expor endpoints seguros ao n8n sem passar pela REST API standard:
```php
<?php
/**
* Plugin Name: JetEngine AI Agent Endpoints
* Description: Endpoints seguros para criação de CCTs e Relações via n8n
* Must Use: sim — colocar em /wp-content/mu-plugins/
*/
add_action('rest_api_init', function() {
// Endpoint para criar item CCT
register_rest_route('jet-agent/v1', '/cct/(?P<slug>[a-z_]+)', [
'methods' => 'POST',
'callback' => 'jet_agent_create_cct_item',
'permission_callback' => function() {
return current_user_can('edit_posts');
},
'args' => [
'slug' => ['required' => true, 'sanitize_callback' => 'sanitize_key'],
]
]);
// Endpoint para gerir relações
register_rest_route('jet-agent/v1', '/relation', [
'methods' => 'POST',
'callback' => 'jet_agent_manage_relation',
'permission_callback' => function() {
return current_user_can('edit_posts');
}
]);
});
function jet_agent_create_cct_item(WP_REST_Request $request) {
$slug = $request->get_param('slug');
$data = $request->get_json_params();
// Sanitizar dados
$data = array_map('sanitize_text_field', $data);
// Usar handler interno do JetEngine
if (!function_exists('jet_engine')) {
return new WP_Error('jet_engine_missing', 'JetEngine não activo', ['status' => 500]);
}
$cct_module = jet_engine()->modules->get_module('custom-content-types');
$type = $cct_module->instance->manager->get_content_type_instance($slug);
if (!$type) {
return new WP_Error('cct_not_found', "CCT '{$slug}' não encontrado", ['status' => 404]);
}
$result = $type->get_handler_instance()->update_item($data);
return rest_ensure_response([
'success' => (bool)$result,
'id' => $result,
]);
}
function jet_agent_manage_relation(WP_REST_Request $request) {
$params = $request->get_json_params();
$rel_id = sanitize_text_field($params['rel_id'] ?? '');
$parent_id = absint($params['parent_id'] ?? 0);
$child_id = absint($params['child_id'] ?? 0);
$connect = (bool)($params['connect'] ?? true);
if (!$rel_id || !$parent_id || !$child_id) {
return new WP_Error('missing_params', 'rel_id, parent_id e child_id são obrigatórios', ['status' => 400]);
}
if (class_exists('\Jet_Engine\Relations\Manager')) {
\Jet_Engine\Relations\Manager::get_instance()->process_relation(
$rel_id, $parent_id, $child_id, $connect
);
return rest_ensure_response(['success' => true]);
}
return new WP_Error('relations_missing', 'Módulo Relations não disponível', ['status' => 500]);
}
```
---
*JetEngine Automation | Descomplicar® | 18-02-2026*
@@ -0,0 +1,271 @@
# JetEngine — Dynamic Content, Macros e Profile Builder
## Dynamic Tags no Elementor
### Categorias de Dynamic Tags JetEngine
| Categoria | Tags Disponíveis |
|-----------|-----------------|
| **Post** | Post Title, Post URL, Post Date, Post Author, Featured Image, Post Terms |
| **Meta** | Meta Field (qualquer campo JetEngine) |
| **User** | User Name, User Email, User Role, User Meta |
| **Relations** | Relation Field (conteúdo relacionado) |
| **Options** | Option Field (JetEngine Global Options Pages) |
| **Macros** | JetEngine Macros (ver secção abaixo) |
| **Query** | Query Variable (parâmetros URL) |
### Usar Meta Field Tag
```
Elemento Elementor > Campo de texto > ícone Dynamic Tags
→ JetEngine > Meta Field
→ Seleccionar meta key name
→ Field Type: auto / text / image / url / ...
Para imagens (campo tipo media):
→ Field Type: Image
→ Image Size: thumbnail / medium / large / full
Para URLs em botões:
→ Link > Dynamic Tag > Meta Field > Field Type: URL
```
### Tags em CSS Custom (background, cores)
```
Elemento > Style > Background > Dynamic Tag
→ Meta Field → campo cor HEX
→ Útil para cards coloridos por categoria
```
---
## Macros
### Macros Built-in
```
%current_user_id% # ID do utilizador logado
%current_user_name% # Nome do utilizador
%current_user_email% # Email do utilizador
%current_post_id% # ID do post actual
%current_post_type% # Post type do post actual
%queried_object_id% # ID em arquivo/single
%queried_object_slug% # Slug em arquivo/single
%request_field|param_name% # Parâmetro URL: ?param_name=valor
%current_meta|field_name% # Meta field do post actual
%user_meta|field_name% # Meta field do utilizador logado
%current_taxonomy|tax_name% # Taxonomy do post actual
%current_timestamp% # Unix timestamp actual
%site_url% # URL do site
```
### Usar Macros em Query Builder
```
Exemplo: Listar imóveis favoritos do utilizador
Meta Query:
├── Field: favorito_de (campo que guarda array de IDs de utilizadores)
├── Operator: LIKE
└── Value: %current_user_id%
Exemplo: Listar items criados pelo utilizador
Meta Query:
├── Field: cct_author_id (campo automático do CCT)
├── Operator: =
└── Value: %current_user_id%
```
### Macros em Textos
```
Texto: "Bem-vindo, %current_user_name%!"
→ Em runtime: "Bem-vindo, João Silva!"
Em Heading Widget: activar Dynamic Tag > JetEngine Macros
→ Escrever a macro no campo Macro Value
```
### Criar Macro Custom (PHP)
```php
<?php
// Registar macro custom
add_filter('jet-engine/listings/macros-list', function($macros) {
$macros[] = [
'id' => 'minha_macro',
'name' => 'Minha Macro Custom',
'args' => [], // parâmetros opcionais
];
return $macros;
});
add_filter('jet-engine/listings/macros-result', function($result, $macro, $args) {
if ($macro === 'minha_macro') {
// Lógica da macro
$result = 'valor calculado';
}
return $result;
}, 10, 3);
```
---
## Conditional Fields (Campos Condicionais)
### Em Meta Boxes
```
Campo B só aparece quando Campo A = "sim"
Meta Box > Campo B > Conditions:
├── Field: nome_campo_a
├── Operator: =
└── Value: sim
```
### Em Listing Templates (Visibility)
```
Elemento Elementor > Advanced > JetEngine Conditions:
├── Condition: Meta Field | Equals | "featured"
└── Effect: Show / Hide
```
---
## Global Options Pages
Para configurações globais do site (telefone, email, horários, redes sociais).
### Criar Options Page
```
JetEngine > Options Pages > Add New
├── Page Title: Informações Globais
├── Slug: informacoes-globais
└── Campos: Meta Box associado
```
### Usar em Elementor
```
Dynamic Tag > JetEngine > Option
→ Seleccionar Options Page
→ Seleccionar campo
Exemplo: footer com telefone dinâmico
→ Options Page: Informações Globais > campo: telefone
→ Editar telefone em 1 lugar, actualiza em todo o site
```
---
## Profile Builder
Sistema de áreas de conta para utilizadores logados.
### Estrutura
```
JetEngine > Profile Builder
├── Pages (áreas da conta):
│ ├── Dashboard (página principal)
│ ├── Submissões (posts do utilizador)
│ ├── Favoritos
│ ├── Configurações (editar perfil)
│ └── [custom pages]
├── Access Rules:
│ ├── Login Required: YES
│ ├── User Roles: subscriber / author / ...
│ └── Redirect não-logados: página de login
└── Profile Menu Widget (Elementor)
```
### Criar Página de Perfil
```
1. JetEngine > Profile Builder > Add Page
Nome: Dashboard | Slug: dashboard
Template: Criar com Elementor
2. No Template, usar Dynamic Tags:
→ User Name, User Email, User Avatar
→ Query Builder filtrado por %current_user_id%
3. Adicionar Profile Menu Widget ao header/sidebar
→ Mostra links para cada página do perfil
→ Altera links consoante se está logado ou não
```
### Submissão de Conteúdo por Utilizadores
```
JetFormBuilder + Profile Builder:
1. Criar formulário com acção "Create Post" (CPT)
2. Acção: Set Author = Current User
3. Colocar formulário na página de perfil
4. Query Builder na página de listagem: autor = current_user_id
```
### Editar Perfil com JetFormBuilder
```php
// JetFormBuilder Form:
// Acção: Update User
// Campos mapeados:
// ├── first_name → user first_name
// ├── last_name → user last_name
// ├── email → user email
// └── bio_custom → user_meta: bio_custom
// Pre-fill automático: activar "Use data from:" → Current User
```
---
## Conditional Content por Utilizador
### Mostrar Conteúdo Só a Logados
```
Elementor > Advanced > JetEngine Conditions:
Condition: Is User Logged In | Equals | True
Effect: Show (esconde elemento para não logados)
```
### Por Role
```
Condition: User Role | Contains | administrator
Effect: Show (só admins vêem este bloco)
```
### Por Meta do Utilizador
```
Condition: User Meta | Field: plano_subscricao | Equals | premium
Effect: Show (só utilizadores premium vêem este conteúdo)
```
---
## Repeater em Frontend
### Listar Items de um Repeater
```
Meta Box com campo Repeater "galeria_destaques":
├── Sub-campo: imagem (media)
├── Sub-campo: titulo (text)
└── Sub-campo: link (url)
Em Listing Template:
Adicionar widget "Dynamic Repeater"
→ Source: Meta Field → galeria_destaques
→ Criar template para cada item do repeater
```
---
*JetEngine Dynamic Content | Descomplicar® | 18-02-2026*
@@ -0,0 +1,225 @@
# JetEngine — Query Builder e Listings
## Query Builder Avançado
### Tipos de Query e Configuração
#### Query Posts
```
Campos principais:
├── Post Type: CPT ou post/page
├── Post Status: publish / draft / any
├── Posts per page: N (ou -1 para todos)
├── Order: ASC / DESC
├── Order By: date / title / menu_order / meta_value / meta_value_num / rand
│ └── Se meta_value: seleccionar meta key
├── Meta Query (múltiplas condições):
│ ├── Field: meta key name
│ ├── Type: CHAR / NUMERIC / DATE
│ ├── Operator: = / != / > / < / BETWEEN / LIKE / IN / NOT IN / EXISTS
│ └── Value: valor ou macro (%current_user_id%)
└── Tax Query:
├── Taxonomy: seleccionar taxonomy
├── Operator: IN / NOT IN / AND
└── Terms: valores ou relação dinâmica
```
#### Query CCT
```
Campos principais:
├── Content Type: seleccionar CCT
├── Items per page: N
├── Order By: qualquer coluna do CCT
├── Filters (equivalente a Meta Query):
│ ├── Field: coluna do CCT
│ ├── Operator: = / != / > / LIKE / IN
│ └── Value: valor ou macro
└── Show items only for current user: activar para perfis
```
#### Query Relação
```
Objetivo: listar items relacionados ao post/item actual
├── Relation: seleccionar relação criada no JetEngine
├── Get children of: post actual / ID específico / macro
└── Post Type: tipo dos items relacionados
```
### Meta Query com Macros
```
Exemplo: listar projectos do utilizador logado
Meta Query:
├── Field: cliente_user_id
├── Operator: =
└── Value: %current_user_id%
Exemplo: listar items de hoje
Meta Query:
├── Field: data_evento (timestamp)
├── Type: NUMERIC
├── Operator: >=
└── Value: %current_timestamp%
```
### Combinar Múltiplas Condições
```
Relation: AND (todas as condições verdadeiras)
OR (pelo menos uma verdadeira)
Exemplo: imóveis em Lisboa com preço < 300k
Tax Query 1: Localização = Lisboa
Meta Query 1: Preço < 300000
Relation: AND
```
---
## Listing Grid — Configuração Detalhada
### Criar Listing Template
```
JetEngine > Listings > Add New
Nome: [CPT]-listing-card
Listing From: Posts / Terms / Users / CCT
Post Type: [seleccionar]
Editar com Elementor:
├── Usar Dynamic Tags em todos os campos
├── Post Title Widget → ou Text Widget com dynamic tag
├── Meta Field → adicionar Dynamic Tag > JetEngine > Meta Field
├── Post Featured Image → ou Image Widget com dynamic tag
└── Button → URL com dynamic tag > Post URL
```
### Listing Grid Widget — Parâmetros
```
Listing Item: [seleccionar template]
Use Custom Query: YES → seleccionar Query Builder
Items per page: 12 (padrão)
Not Found Text: "Sem resultados"
Loader: activar para UX com filtros
Columns: 1/2/3/4 (responsivo)
Equal Columns Height: YES para cards uniformes
```
### Paginação
```
Listing Grid > Pagination:
├── Type: Default WP / Load More / Infinity Scroll
├── Load More Button Text: "Carregar mais"
├── On Scroll: activar para infinity
└── Starting From: 1
```
### Ordenação Dinâmica (Frontend)
```
Adicionar widget: JetSmartFilters > Sorting
├── Ligar ao mesmo Listing Grid
└── Opções: Date ASC/DESC, Title, Price, Custom Field
```
---
## Listing Map
### Configuração
```
1. Activar Google Maps API Key:
JetEngine > Settings > Google Maps API Key
2. Criar Meta Box com campo tipo Map no CPT
(O utilizador define latitude/longitude via picker no editor)
3. Criar Listing Template tipo: Map Marker
Adicionar: Post Title, info a mostrar no popup
4. Adicionar widget Listing Map ao Elementor
├── Listing Item: [marker template]
├── Use Custom Query: YES
├── Map Field: seleccionar campo coordenadas
└── Map Options: zoom, altura, cluster
```
### Mapa + Listing Grid Sincronizados
```
Adicionar os dois widgets na mesma página
Ligar ao mesmo Query Builder
Activar "Synchronize with Listing Grid": YES no Map
→ Ao fazer hover no card, destaca pin no mapa
→ Ao fazer hover no pin, destaca card na lista
```
---
## Archive e Single Templates
### Single CPT com Elementor (JetThemeCore ou Elementor Pro)
```
JetThemeCore > Templates > Add New > Single
Conditions: Post Type = [meu_cpt]
Editar com Elementor usando Dynamic Tags
OU
Elementor > Templates > Theme Builder > Single
Conditions: igual
```
### Archive Page
```
JetThemeCore > Templates > Add New > Archive
Conditions: Post Type Archive = [meu_cpt]
Adicionar:
├── Archive Title Widget
├── Listing Grid Widget (sem Custom Query — usa WP_Query do arquivo)
└── JetSmartFilters (ligado ao mesmo archive)
```
---
## JetSmartFilters — Integração Avançada
### AJAX Reload vs Full Page
```
Provider: Listing Grid → AJAX (sem reload de página)
Provider: Archive → Pode ser AJAX ou URL based
Provider: WooCommerce → AJAX
```
### URL Based Filters (SEO Friendly)
```
JetSmartFilters > Filters > Edit
├── Query Variable: activar
├── URL Based: activar
└── Clean URL: activar
Resultado: site.pt/imoveis/?cidade=Lisboa&preco=200000-400000
(Em vez de AJAX puro — melhor para SEO e partilha de URL)
```
### Hierarquical Taxonomy Filter
```
Exemplo: País → Distrito → Concelho
├── Criar taxonomia com hierarquia
├── Filtro: Checkboxes com hierarquia activada
└── "Show children of selected": activar
```
---
*JetEngine Query & Listings | Descomplicar® | 18-02-2026*
+1
View File
@@ -0,0 +1 @@
## 2026-06-30 init | Estrutura padrão
+135
View File
@@ -0,0 +1,135 @@
---
name: rank-math
description: Gestão programática do Rank Math SEO (Free e PRO) via WP-CLI em servidores CWP. Cobre auditoria SEO, meta em massa, schema markup, redirects, sitemaps, Instant Indexing, Analytics PRO, hooks/filtros, taxonomias WooCommerce, News/Video Sitemap, validação e rollback. Usar quando "rank math", "seo cli", "schema markup", "redirects seo", "instant indexing", "indexnow", "seo bulk", "rankmath pro".
layer: wiki
---
# /rank-math — Gestão Programática Rank Math SEO via WP-CLI
Operações SEO WordPress completas via WP-CLI no servidor CWP. Free + PRO.
**Manual completo:** `Hub/06-Operacoes/Documentacao/Manuais/WP-CLI/Rank-Math-WP-CLI-Manual-Definitivo.md`
**Quick Reference:** `Hub/06-Operacoes/Documentacao/Quick-Reference/QR-RankMath-WP-CLI.md`
**Pesquisa Claude:** `Hub/06-Operacoes/Documentacao/Manuais/WP-CLI/Rank-Math-SEO-WP-CLI-Skils-Pesquisa-Claude.md`
**Pesquisa Gemini:** `Hub/06-Operacoes/Documentacao/Manuais/WP-CLI/Rank-Math-SEO-WP-CLI-Skills-Pesquisa-Gemini.md`
**NotebookLM:** [WordPress Config CLI](https://notebooklm.google.com/notebook/fb2f26bd-8cb0-4d4c-bafc-4f1ebb51c51d)
**Skill relacionada:** `/seo-post` — optimizar posts individuais com pipeline completo
---
## Contexto NotebookLM
ANTES de executar, consultar notebooks para contexto especializado:
| Notebook | ID | Consultar quando |
|----------|-----|-----------------|
| WordPress e Elementor | 5be0d1a6 | Sempre |
| Marketing Digital PT | 4c595973 | Para contexto SEO |
```
mcp__notebooklm__notebook_query({
notebook_id: "5be0d1a6-00f2-4cd9-b835-978cb7721601",
query: "<adaptar ao contexto do pedido do utilizador>"
})
```
---
## Uso
```
/rank-math audit SITE_PATH
/rank-math update-meta SITE_PATH
/rank-math sitemap SITE_PATH
/rank-math redirects SITE_PATH
/rank-math schema post ID SITE_PATH
/rank-math indexing SITE_PATH [URL|bulk]
/rank-math analytics SITE_PATH
/rank-math taxonomy SITE_PATH
/rank-math hooks SITE_PATH
/rank-math backup SITE_PATH
/rank-math provision SITE_PATH
```
---
## Contexto CWP — sempre obrigatorio
```bash
# Formato base (PHP 8.3)
/opt/alt/php-fpm83/usr/bin/php /usr/local/bin/wp <comando> --allow-root --path=/home/USER/public_html
# Alias
wp83 <comando> --allow-root --path=/home/USER/public_html
```
**Acesso via MCP SSH:**
```
servidor: server.descomplicar.pt (176.9.3.158) | porta: 9443 | user: root
```
**Prefixo de tabelas:** Sites podem ter prefixo custom (ex: `wpah_` em vez de `wp_`). Detectar primeiro:
```bash
PREFIX=$(wp db prefix --allow-root --path=$PATH)
# Usar $PREFIX em todas as queries SQL: ${PREFIX}posts, ${PREFIX}postmeta, etc.
```
**Unico comando nativo Rank Math:**
```bash
wp rankmath sitemap generate --allow-root --path=$PATH
```
Tudo o resto usa `wp post meta`, `wp option patch`, `wp eval` ou `wp db query`.
**Options com defaults:** Muitas sub-keys (title_separator, strip_category_base, homepage_title, knowledgegraph_type) podem nao existir na BD se nunca foram alteradas — o Rank Math usa valores por defeito internos. `wp option pluck` retorna exit code 1 neste caso. Nao e erro — significa valor default.
---
## Decision tree — qual comando usar
| Operacao | Comando |
|----------|---------|
| Alterar meta de 1 post | `wp post meta update` |
| Alterar sub-key em option serializada | `wp option patch update` |
| Ler sub-key de option | `wp option pluck` |
| Schema complexo | `wp eval` com PHP |
| Redirects | `wp eval` com `\RankMath\Redirections\DB` |
| Bulk simples | shell loop com `wp post list \| while read` |
| Bulk complexo | `wp eval-file` com script PHP |
| SQL directo | apenas auditorias (seguido de `wp cache flush`) |
| Regenerar sitemap | `wp rankmath sitemap generate` |
| Instant Indexing | `wp eval` com IndexNow API ou classes RM |
| Side-effects RM | `wp eval` com `update_option()` (dispara hooks) |
---
## 1. Auditoria SEO (/rank-math audit)
### Passo 1 — diagnostico rapido
```bash
PATH=/home/USER/public_html
PREFIX=$(wp db prefix --allow-root --path=$PATH)
# Posts sem titulo SEO
wp db query "SELECT p.ID, p.post_title FROM ${PREFIX}posts p
LEFT JOIN ${PREFIX}postmeta pm ON p.ID=pm.post_id AND pm.meta_key='rank_math_title'
WHERE p.post_type='post' AND p.post_status='publish'
AND (pm.meta_value IS NULL OR pm.meta_value='');" --allow-root --path=$PATH
# Posts sem descricao SEO
wp db query "SELECT p.ID, p.post_title FROM ${PREFIX}posts p
LEFT JOIN ${PREFIX}postmeta pm ON p.ID=pm.post_id AND pm.meta_key='rank_math_description'
WHERE p.post_type='post' AND p.post_status='publish'
AND (pm.meta_value IS NULL OR pm.meta_value='');" --allow-root --path=$PATH
# Distribuicao de SEO scores
wp db query "SELECT
CASE WHEN CAST(meta_value AS UNSIGNED) >= 80 THEN 'Bom (80-100)'
WHEN CAST(meta_value AS UNSIGNED) >= 50 THEN 'OK (50-79)'
ELSE 'Fraco (0-49)' END AS faixa,
COUNT(*) AS total
FROM ${PREFIX}postmeta WHERE meta_key = 'rank_math_seo_score' GROUP BY faixa;" --allow-root --path=$PATH
# Top 404s (ultimos 30 dias)
wp db query "SELECT uri, SUM(times_acce
@@ -0,0 +1,379 @@
# Rank Math WP-CLI — Referência Completa de Comandos
**Manual Hub:** `06-Operacoes/Documentacao/Manuais/Rank-Math-WP-CLI-Manual-Definitivo.md`
---
## Arquitectura de Dados
### Tabelas WordPress Standard
| Tabela | Dados Rank Math |
|--------|-----------------|
| `wp_postmeta` | SEO por post: título, descrição, robots, schema, keywords |
| `wp_termmeta` | SEO por taxonomia (categories, tags) |
| `wp_usermeta` | SEO de páginas author archive |
| `wp_options` | Configurações globais do plugin |
### Tabelas Custom Rank Math
| Tabela | Conteúdo |
|--------|---------|
| `wp_rank_math_redirections` | Regras de redirect (301, 302, 410, etc.) |
| `wp_rank_math_redirections_cache` | Cache das regras |
| `wp_rank_math_404_logs` | Log de 404s com contadores |
| `wp_rank_math_analytics_objects` | Analytics (PRO) |
| `wp_rank_math_analytics_ga` | Google Analytics (PRO) |
| `wp_rank_math_analytics_gsc` | Search Console (PRO) |
---
## Meta Keys Completas (wp_postmeta)
### SEO Básico
| Meta Key | Tipo | Exemplo |
|----------|------|---------|
| `rank_math_title` | string | `%title% %sep% %sitename%` |
| `rank_math_description` | string | Máx 160 chars |
| `rank_math_focus_keyword` | string CSV | `kw1,kw2,kw3` |
| `rank_math_pillar_content` | string | `on` / `off` |
| `rank_math_canonical_url` | string URL | `https://site.pt/url/` |
| `rank_math_seo_score` | int | 0-100 (calculado no editor) |
### Robots e Indexação
| Meta Key | Tipo | Valores |
|----------|------|---------|
| `rank_math_robots` | **array JSON** | `["noindex"]`, `["nofollow"]`, `["noindex","nofollow"]` |
```bash
# CORRECTO
wp post meta update ID rank_math_robots '["noindex"]' --format=json --allow-root
# ERRADO (string, não aplicará)
wp post meta update ID rank_math_robots "noindex" --allow-root
```
### Open Graph / Social
| Meta Key | Tipo | Notas |
|----------|------|-------|
| `rank_math_facebook_image` | string URL | Imagem OG |
| `rank_math_facebook_title` | string | Título Facebook |
| `rank_math_facebook_description` | string | Descrição Facebook |
| `rank_math_twitter_card_type` | string | `summary_large_image` |
| `rank_math_twitter_title` | string | |
| `rank_math_twitter_description` | string | |
| `rank_math_twitter_image` | string URL | |
### Schema
| Meta Key | Tipo | Notas |
|----------|------|-------|
| `rank_math_schema_Article` | serialized PHP array | `wp eval` obrigatório |
| `rank_math_schema_FAQPage` | serialized PHP array | |
| `rank_math_schema_HowTo` | serialized PHP array | |
| `rank_math_schema_Product` | serialized PHP array | WooCommerce |
| `rank_math_schema_Recipe` | serialized PHP array | |
| `rank_math_schema_Review` | serialized PHP array | |
| `rank_math_schema_Event` | serialized PHP array | |
| `rank_math_schema_LocalBusiness` | serialized PHP array | |
| `rank_math_schema_VideoObject` | serialized PHP array | |
### WooCommerce
| Meta Key | Tipo | Notas |
|----------|------|-------|
| `rank_math_primary_category` | string (Term ID) | `"15"` |
| `rank_math_schema_Product` | serialized PHP array | Produto WC |
---
## Options Globais (wp_options)
### Options Principais
| Option Name | Tipo | Conteúdo |
|-------------|------|---------|
| `rank-math-options-general` | serialized array | Config geral |
| `rank-math-options-titles` | serialized array | Títulos e meta por post-type |
| `rank-math-options-sitemap` | serialized array | Configuração sitemap |
| `rank_math_modules` | serialized array | Módulos activos/inactivos |
| `rank_math_schema_types` | serialized array | Tipos schema habilitados |
### Sub-keys rank-math-options-general
```bash
wp option pluck rank-math-options-general strip_category_base # on/off
wp option pluck rank-math-options-general breadcrumbs # on/off
wp option pluck rank-math-options-general knowledgegraph_type # person/company
wp option pluck rank-math-options-general url # URL empresa
wp option pluck rank-math-options-general name # Nome empresa/pessoa
```
### Sub-keys rank-math-options-titles
```bash
wp option pluck rank-math-options-titles title_separator # - / | • …
wp option pluck rank-math-options-titles homepage_title # Título homepage
wp option pluck rank-math-options-titles homepage_description # Desc homepage
wp option pluck rank-math-options-titles pt_post_title # Título padrão posts
wp option pluck rank-math-options-titles pt_page_title # Título padrão pages
wp option pluck rank-math-options-titles pt_product_title # Título padrão products
```
### Sub-keys rank-math-options-sitemap
```bash
wp option pluck rank-math-options-sitemap items_per_page # Default: 200
wp option pluck rank-math-options-sitemap pt_post_sitemap # on/off
wp option pluck rank-math-options-sitemap pt_page_sitemap # on/off
wp option pluck rank-math-options-sitemap pt_product_sitemap # on/off
wp option pluck rank-math-options-sitemap pt_attachment_sitemap # on/off (default: off)
```
---
## Módulos Rank Math
```bash
# Ver todos os módulos
wp option get rank_math_modules --format=json --allow-root
# Activar módulo
wp option patch update rank_math_modules MODULE_NAME "1" --allow-root
# Desactivar módulo
wp option patch update rank_math_modules MODULE_NAME "0" --allow-root
```
| Módulo | Slug | Função |
|--------|------|--------|
| SEO Analysis | `seo-analysis` | Score SEO no editor |
| Link Counter | `link-counter` | Contagem links internos/externos |
| Redirections | `redirections` | Sistema de redirects |
| 404 Monitor | `404-monitor` | Log de 404s |
| Schema | `rich-snippet` | Schema markup |
| Sitemap | `sitemap` | XML Sitemap |
| Instant Indexing | `instant-indexing` | Google Indexing API (PRO) |
| Local SEO | `local-seo` | Schema LocalBusiness |
| WooCommerce | `woocommerce` | SEO WooCommerce |
---
## Leitura de Meta
```bash
# Todos os meta do post
wp post meta list 42 --format=table --allow-root | grep rank_math
# Meta específico
wp post meta get 42 rank_math_title --allow-root
wp post meta get 42 rank_math_description --allow-root
wp post meta get 42 rank_math_robots --allow-root
# Meta de term
wp term meta get 15 rank_math_title --allow-root
wp term meta get 15 rank_math_description --allow-root
```
---
## Operações em Massa
### Script de auditoria completo
```bash
#!/bin/bash
PATH_WP="/home/USER/public_html"
WP="/opt/alt/php-fpm83/usr/bin/php /usr/local/bin/wp --allow-root --path=$PATH_WP"
echo "=== AUDITORIA RANK MATH SEO ==="
echo "Site: $PATH_WP"
echo "Data: $(date)"
echo -e "\n--- Posts sem título SEO ---"
$WP db query "SELECT p.ID, LEFT(p.post_title,50) as titulo FROM wp_posts p
LEFT JOIN wp_postmeta pm ON p.ID=pm.post_id AND pm.meta_key='rank_math_title'
WHERE p.post_type='post' AND p.post_status='publish'
AND (pm.meta_value IS NULL OR pm.meta_value='')
LIMIT 20;"
echo -e "\n--- Posts sem descrição SEO ---"
$WP db query "SELECT p.ID, LEFT(p.post_title,50) as titulo FROM wp_posts p
LEFT JOIN wp_postmeta pm ON p.ID=pm.post_id AND pm.meta_key='rank_math_description'
WHERE p.post_type='post' AND p.post_status='publish'
AND (pm.meta_value IS NULL OR pm.meta_value='')
LIMIT 20;"
echo -e "\n--- Top 10 404s ---"
$WP db query "SELECT uri, SUM(times_accessed) as total FROM wp_rank_math_404_logs
GROUP BY uri ORDER BY total DESC LIMIT 10;"
echo -e "\n--- Módulos activos ---"
$WP option get rank_math_modules --format=json
echo -e "\n--- Tamanho options ---"
$WP db query "SELECT option_name, LENGTH(option_value) as bytes FROM wp_options
WHERE option_name LIKE 'rank%' AND autoload='yes' ORDER BY bytes DESC;"
echo "=== FIM AUDITORIA ==="
```
### Bulk: Aplicar título padrão a posts sem título
```bash
wp eval '
$posts = get_posts(["post_type"=>"post","numberposts"=>-1,"post_status"=>"publish"]);
$updated = 0;
foreach ($posts as $p) {
if (!get_post_meta($p->ID, "rank_math_title", true)) {
update_post_meta($p->ID, "rank_math_title", "%title% %sep% %sitename%");
$updated++;
}
}
echo "$updated posts actualizados\n";
' --allow-root --path=$PATH_WP
```
### Bulk: Aplicar descrição a partir do excerpt
```bash
wp eval '
$posts = get_posts(["post_type"=>"post","numberposts"=>-1,"post_status"=>"publish"]);
$updated = 0;
foreach ($posts as $p) {
if (!get_post_meta($p->ID, "rank_math_description", true) && $p->post_excerpt) {
$desc = wp_trim_words($p->post_excerpt, 20, "...");
update_post_meta($p->ID, "rank_math_description", $desc);
$updated++;
}
}
echo "$updated posts actualizados\n";
' --allow-root --path=$PATH_WP
```
---
## Schema — Exemplos Completos
### Article (blog post)
```bash
wp eval '
$post_id = ID;
update_post_meta($post_id, "rank_math_schema_Article", [
"metadata" => [
"title"=>"Article",
"type"=>"custom",
"shortcode"=>"s-".uniqid(),
"isPrimary"=>"1"
],
"@type"=>"Article",
"headline"=>"%seo_title%",
"description"=>"%seo_description%",
"url"=>"%url%",
"author"=>["@type"=>"Person","name"=>"%name%"],
"publisher"=>["@type"=>"Organization","name"=>"%sitename%","logo"=>""],
"datePublished"=>"%date(Y-m-dTH:i:sP)%",
"dateModified"=>"%modified(Y-m-dTH:i:sP)%",
"image"=>"%post_thumbnail%"
]);
echo "Article schema criado no post $post_id\n";
' --user=1 --allow-root --path=$PATH_WP
```
### LocalBusiness (página contacto)
```bash
wp eval '
update_post_meta(ID, "rank_math_schema_LocalBusiness", [
"metadata" => ["title"=>"LocalBusiness","type"=>"custom","shortcode"=>"s-".uniqid(),"isPrimary"=>"1"],
"@type"=>"LocalBusiness",
"name"=>"Nome Empresa",
"description"=>"%seo_description%",
"url"=>"%url%",
"email"=>"email@empresa.pt",
"telephone"=>"+351 XXX XXX XXX",
"address"=>["@type"=>"PostalAddress","streetAddress"=>"Rua X","addressLocality"=>"Cidade","postalCode"=>"XXXX-XXX","addressCountry"=>"PT"],
"geo"=>["@type"=>"GeoCoordinates","latitude"=>"38.7","longitude"=>"-9.1"],
"openingHoursSpecification"=>[
["@type"=>"OpeningHoursSpecification","dayOfWeek"=>["Monday","Tuesday","Wednesday","Thursday","Friday"],"opens"=>"09:00","closes"=>"18:00"]
],
"image"=>"%post_thumbnail%"
]);
echo "LocalBusiness schema criado\n";
' --user=1 --allow-root --path=$PATH_WP
```
---
## Instant Indexing (Google Indexing API - PRO)
```bash
# Submeter URL individual
wp eval '\RankMath\Instant_Indexing\Api::submit("https://site.pt/url/", "URL_UPDATED"); echo "OK\n";' --allow-root --path=$PATH_WP
# Verificar estado da API
wp option pluck rank-math-options-instant-indexing bing_api_key --allow-root --path=$PATH_WP
```
---
## Redirects — Referência Completa
### Tipos de Comparação
| comparison | Comportamento |
|-----------|---------------|
| `exact` | Match exacto do URL |
| `contains` | URL contém o pattern |
| `starts_with` | URL começa com o pattern |
| `ends_with` | URL termina com o pattern |
| `regex` | Regular expression |
### Códigos HTTP
| Código | Tipo | Uso |
|--------|------|-----|
| 301 | Moved Permanently | Redirect definitivo |
| 302 | Found | Redirect temporário |
| 307 | Temporary Redirect | Temporário (preserva método HTTP) |
| 410 | Gone | Conteúdo removido permanentemente |
| 451 | Unavailable For Legal | Removido por razões legais |
---
## Tabelas Rank Math — Verificação
```bash
# Ver tabelas custom
wp db query "SHOW TABLES LIKE '%rank_math%';" --allow-root --path=$PATH_WP
# Estrutura tabela redirections
wp db query "DESCRIBE wp_rank_math_redirections;" --allow-root --path=$PATH_WP
# Ver todos os redirects activos
wp db query "SELECT id, sources, url_to, header_code FROM wp_rank_math_redirections WHERE status='active';" --allow-root --path=$PATH_WP
```
---
## Free vs PRO — Funcionalidades WP-CLI
| Funcionalidade | Free | PRO | Notas |
|---------------|------|-----|-------|
| `wp rankmath sitemap generate` | ✅ | ✅ | Único comando nativo |
| Meta por post | ✅ | ✅ | `wp post meta` |
| Options globais | ✅ | ✅ | `wp option patch` |
| Schema markup | ✅ | ✅ | `wp eval` |
| Redirections | ✅ | ✅ | `\RankMath\Redirections\DB` |
| 404 Monitor | ✅ | ✅ | `wp_rank_math_404_logs` |
| Instant Indexing | ❌ | ✅ | `\RankMath\Instant_Indexing\Api` |
| Analytics GSC | ❌ | ✅ | Tabelas analytics_gsc |
| Analytics GA | ❌ | ✅ | Tabelas analytics_ga |
---
*Referência Rank Math WP-CLI | Descomplicar® | v1.0.0 | 18-02-2026*
+134
View File
@@ -0,0 +1,134 @@
---
name: seo-post
description: Optimizar posts WordPress com RankMath Pro via WP-CLI. Auditar, corrigir e aplicar SEO completo incluindo schema, OG, robots e links internos. Usar quando "seo post", "rankmath", "schema", "optimizar artigo", "seo wordpress".
context: fork
layer: wiki
---
# /seo-post — Optimizar Posts WordPress com RankMath Pro
Modos: auditar (com ID), aplicar (com ID + dados), bulk (com lista IDs), novo (pipeline completo).
## Constantes
```
SERVIDOR: server (via mcp__ssh-unified__ssh_execute)
WP_PATH: cd /home/ealmeida/public_html
WP_CLI: wp --allow-root
WP_USER: --user=2 (para wp eval)
MANUAL: Hub/06-Operacoes/Documentacao/Manuais/WP-CLI/Rank-Math-WP-CLI-Manual-Definitivo.md
```
## Modo auditar (/seo-post ID)
Verifica estado SEO de um post e lista problemas.
```bash
cd /home/ealmeida/public_html
# 1. Dados do post
wp --allow-root post get POST_ID --fields=ID,post_title,post_name,post_status --format=table
# 2. Todos os meta RankMath
wp --allow-root post meta list POST_ID --format=table | grep rank_math
# 3. Thumbnail e alt text
THUMB_ID=$(wp --allow-root post meta get POST_ID _thumbnail_id)
wp --allow-root post meta get $THUMB_ID _wp_attachment_image_alt
# 4. Schema no HTML
curl -s "URL_DO_POST" | grep -o '"@type":"[^"]*"' | sort -u
```
**Checklist de auditoria:**
| Campo | Verificacao |
|-------|-----------|
| rank_math_title | Existe? 50-70 chars? Tem keyword? |
| rank_math_description | Existe? 150-160 chars? Tem keyword? |
| rank_math_focus_keyword | Existe? 1-3 keywords CSV? |
| rank_math_primary_category | Existe? ID valido? |
| rank_math_robots | Array? Tem ["index"]? |
| rank_math_advanced_robots | Array JSON? max-image-preview large? |
| rank_math_rich_snippet | "article"? |
| rank_math_snippet_article_type | "NewsArticle" ou "BlogPosting" ou "Article"? |
| rank_math_schema_* | Existe meta com schema? Tem isPrimary 1? |
| rank_math_facebook_title | Existe? |
| rank_math_facebook_image | Existe? URL valido? |
| rank_math_twitter_card_type | "summary_large_image"? |
| _thumbnail_id | Existe? Imagem real? |
| Alt text imagem | Tem keywords? |
| post_name (slug) | Curto? <40 chars? Tem keyword? |
| rank_math_seo_score | Existe? >0? Se N/A -> executar Passo 6 |
**Output:** tabela com campo, estado (OK/FALHA), valor actual, accao sugerida.
## Modo aplicar (/seo-post ID com dados)
Aplica SEO completo a um post. Pedir ao utilizador:
- Titulo SEO (ou gerar a partir do post_title)
- Meta description
- Focus keywords (CSV)
- Tipo schema: NewsArticle (noticias) ou Article (guias) ou BlogPosting (blog)
### Passo 1: Strings simples
```bash
wp --allow-root post meta update POST_ID rank_math_title 'TITULO SEO'
wp --allow-root post meta update POST_ID rank_math_description 'META DESC'
wp --allow-root post meta update POST_ID rank_math_focus_keyword 'kw1,kw2,kw3'
wp --allow-root post meta update POST_ID rank_math_primary_category 'CAT_ID'
```
### Passo 2: Arrays (OBRIGATORIO --format=json)
```bash
wp --allow-root post meta update POST_ID rank_math_robots '["index"]' --format=json
wp --allow-root post meta update POST_ID rank_math_advanced_robots '{"max-snippet":"-1","max-video-preview":"-1","max-image-preview":"large"}' --format=json
```
### Passo 3: Schema — DOIS formatos (AMBOS obrigatorios)
```bash
# Legacy (dashboard RankMath)
wp --allow-root post meta update POST_ID rank_math_rich_snippet 'article'
wp --allow-root post meta update POST_ID rank_math_snippet_article_type 'TIPO'
# TIPO: NewsArticle (noticias), Article (guias), BlogPosting (blog)
# Novo (schema real no HTML — via wp eval)
wp --allow-root eval '
$schema = [
"metadata" => ["title" => "TIPO", "type" => "custom", "shortcode" => "s-" . uniqid(), "isPrimary" => "1"],
"@type" => "TIPO",
"headline" => "%seo_title%",
"description" => "%seo_description%",
"keywords" => "%keywords%",
"image" => ["@type" => "ImageObject", "url" => "%post_thumbnail%"],
"author" => ["@type" => "Organization", "name" => "Descomplicar", "url" => "https://descomplicar.pt"],
"publisher" => ["@type" => "Organization", "name" => "Descomplicar", "url" => "https://descomplicar.pt"],
"datePublished" => "%date(Y-m-dTH:i:sP)%",
"dateModified" => "%modified(Y-m-dTH:i:sP)%",
"articleSection" => "%categories%"
];
update_post_meta(POST_ID, "rank_math_schema_TIPO", $schema);
echo "Schema OK\n";
' --user=2
```
### Passo 4: Social / OG
```bash
wp --allow-root post meta update POST_ID rank_math_facebook_title 'TITULO OG'
wp --allow-root post meta update POST_ID rank_math_facebook_description 'DESC OG'
wp --allow-root post meta update POST_ID rank_math_facebook_image 'IMAGE_URL'
wp --allow-root post meta update POST_ID rank_math_og_content_image 'IMAGE_URL'
wp --allow-root post meta update POST_ID rank_math_twitter_card_type 'summary_large_image'
```
### Passo 5: Slug (se necessario)
```bash
wp --allow-root post update POST_ID --post_name='slug-curto-keyword'
```
### Passo 6: SEO Score (OBRIGATORIO — posts via CLI nao cal
+161
View File
@@ -0,0 +1,161 @@
---
name: woocommerce-cli
description: Gestão WooCommerce via WP-CLI no servidor CWP. Cobre produtos, encomendas, clientes, shipping, gateways de pagamento, importação/exportação em massa e automação.
layer: wiki
---
# /woocommerce-cli — WooCommerce via WP-CLI no CWP
Gestão completa de lojas WooCommerce via linha de comandos no servidor CWP. Cobre produtos, encomendas, clientes, configuração, automação e scripting.
**Servidor:** server.descomplicar.pt (176.9.3.158) | porta SSH: 9443
**Manual:** `Hub/06-Operacoes/Documentacao/Manuais/WooCommerce com WP-CLI_ Guia Completo.md`
**Skill relacionada:** `/woocommerce` — desenvolvimento e extensões WooCommerce
---
## Contexto CWP — Regras Obrigatórias
```bash
# 1. SEMPRE prefixar com PHP completo
# 2. SEMPRE --allow-root (CWP executa como root)
# 3. SEMPRE --user=1 em operações de escrita (mapeamento REST API)
# 4. SEMPRE --path= (múltiplos sites no mesmo servidor)
# Formato base
/opt/alt/php-fpm83/usr/bin/php /usr/local/bin/wp wc <ENTIDADE> <OP> \
--user=1 --allow-root --path=/home/USER/public_html
# Variável de conveniência para scripts
PATH_WP="/home/USER/public_html"
WP="/opt/alt/php-fpm83/usr/bin/php /usr/local/bin/wp --allow-root --path=$PATH_WP"
$WP wc product list --user=1
```
### Versões PHP disponíveis
| Alias | PHP | Path |
|-------|-----|------|
| `wp74` | 7.4 | `/opt/alt/php-fpm74/usr/bin/php` |
| `wp82` | 8.2 | `/opt/alt/php-fpm82/usr/bin/php` |
| `wp83` | 8.3 | `/opt/alt/php-fpm83/usr/bin/php` (recomendado) |
### Parâmetros essenciais WC CLI
| Parâmetro | Uso |
|-----------|-----|
| `--user=1` | Obrigatório em escrita (autenticação REST API) |
| `--porcelain` | Retorna apenas ID numérico (para scripts) |
| `--format=table\|json\|csv\|ids` | Formato do output |
| `--fields=id,name,price` | Limitar campos (reduz memória) |
| `--per_page=100` | Limite máximo da API (paginar para mais) |
| `--force` | Eliminar definitivamente (sem lixo) |
---
## Produtos
### Operações Comuns
```bash
PATH_WP="/home/USER/public_html"
# Listar produtos
/opt/alt/php-fpm83/usr/bin/php /usr/local/bin/wp wc product list \
--user=1 --fields=id,name,price,stock_quantity --format=table \
--allow-root --path=$PATH_WP
# Criar produto simples
/opt/alt/php-fpm83/usr/bin/php /usr/local/bin/wp wc product create \
--user=1 \
--name="Produto Exemplo" \
--type=simple \
--regular_price="29.99" \
--sku="PROD-001" \
--manage_stock=true \
--stock_quantity=100 \
--status=publish \
--allow-root --path=$PATH_WP
# Actualizar preço e stock
/opt/alt/php-fpm83/usr/bin/php /usr/local/bin/wp wc product update 456 \
--user=1 \
--regular_price="34.99" \
--stock_quantity=50 \
--allow-root --path=$PATH_WP
# Eliminar definitivamente
/opt/alt/php-fpm83/usr/bin/php /usr/local/bin/wp wc product delete 456 \
--user=1 --force --allow-root --path=$PATH_WP
```
### Produto Variável (capturar ID)
```bash
# 1. Criar produto pai (--porcelain retorna só o ID)
PARENT_ID=$(/opt/alt/php-fpm83/usr/bin/php /usr/local/bin/wp wc product create \
--user=1 --name="T-Shirt" --type=variable \
--attributes='[{"name":"Tamanho","variation":true,"visible":true,"options":["S","M","L","XL"]}]' \
--porcelain --allow-root --path=$PATH_WP)
# 2. Criar variação
/opt/alt/php-fpm83/usr/bin/php /usr/local/bin/wp wc product_variation create $PARENT_ID \
--user=1 \
--regular_price="24.99" \
--sku="TSHIRT-M" \
--attributes='[{"name":"Tamanho","option":"M"}]' \
--manage_stock=true --stock_quantity=30 \
--allow-root --path=$PATH_WP
```
### Actualização em Massa por SKU
```bash
#!/bin/bash
PATH_WP="/home/USER/public_html"
WP="/opt/alt/php-fpm83/usr/bin/php /usr/local/bin/wp --allow-root --path=$PATH_WP"
IDS=$($WP wc product list --user=1 --sku="TSHIRT-*" --format=ids)
for ID in $IDS; do
$WP wc product update $ID --user=1 --stock_quantity=200 --porcelain
done
```
### Exportar Catálogo
```bash
/opt/alt/php-fpm83/usr/bin/php /usr/local/bin/wp wc product list \
--user=1 --per_page=5000 --format=csv \
--allow-root --path=$PATH_WP \
> /home/USER/catalogo_$(date +%Y%m%d).csv
```
---
## Encomendas
```bash
PATH_WP="/home/USER/public_html"
# Listar encomendas em processamento
/opt/alt/php-fpm83/usr/bin/php /usr/local/bin/wp wc shop_order list \
--user=1 --status=processing \
--fields=id,total,date_created \
--allow-root --path=$PATH_WP
# Actualizar estado
/opt/alt/php-fpm83/usr/bin/php /usr/local/bin/wp wc shop_order update 355 \
--user=1 --status=completed \
--allow-root --path=$PATH_WP
# Adicionar nota ao cliente
/opt/alt/php-fpm83/usr/bin/php /usr/local/bin/wp wc order_note create 355 \
--user=1 \
--note="Encomenda expedida. Rastreio: PT-99887766." \
--customer_note=true \
--allow-root --path=$PATH_WP
# Conclusão massiva de todas as encomendas em processamento
WP="/opt/alt/php-fpm83/usr/bin/php /usr/local/bin/wp --allow-root --path=$PATH_WP"
for ID in $($WP wc shop_order list --user=1 --status=processing --format=ids); do
$WP wc shop_order update $ID --us
@@ -0,0 +1,621 @@
# WooCommerce WP-CLI — Referência Completa (CWP)
**Manual Hub:** `06-Operacoes/Documentacao/Manuais/WooCommerce com WP-CLI_ Guia Completo.md`
---
## Namespace e Entidades Disponíveis
O WooCommerce CLI opera sob `wp wc`. Todas as operações mapeiam para REST API v3.
| Entidade | Comando Base |
|----------|-------------|
| `customer` | Contas, moradas, métricas |
| `product` | Inventário, preços, stock |
| `product_cat` / `product_tag` | Categorias e tags |
| `product_attribute` | Atributos globais |
| `product_variation` | Variações de produto variável |
| `shop_order` | Encomendas |
| `order_note` | Notas de encomenda |
| `shop_coupon` | Cupões de desconto |
| `setting` | Settings WooCommerce |
| `shipping_zone` | Zonas de envio |
| `shipping_zone_location` | Localizações por zona |
| `shipping_zone_method` | Métodos por zona |
| `payment_gateway` | Gateways de pagamento |
| `tax` | Taxas de imposto |
| `tax_class` | Classes de imposto |
| `tool` | Ferramentas de manutenção |
| `webhook` | Webhooks server-to-server |
---
## Tipos de Produto — Exemplos Completos
### Produto Simples
```bash
PATH_WP="/home/USER/public_html"
/opt/alt/php-fpm83/usr/bin/php /usr/local/bin/wp wc product create \
--user=1 \
--name="T-Shirt Premium Algodão Orgânico" \
--type=simple \
--regular_price="29.99" \
--sale_price="24.99" \
--sku="TSHIRT-PREM-001" \
--manage_stock=true \
--stock_quantity=150 \
--status=publish \
--description="Descrição completa do produto." \
--short_description="Descrição curta para listagens." \
--allow-root --path=$PATH_WP
```
### Produto Virtual (sem envio)
```bash
/opt/alt/php-fpm83/usr/bin/php /usr/local/bin/wp wc product create \
--user=1 \
--name="Consulta Online 1h" \
--type=simple \
--virtual=true \
--regular_price="75.00" \
--sku="CONSULT-1H" \
--status=publish \
--allow-root --path=$PATH_WP
```
### Produto Downloadable
```bash
/opt/alt/php-fpm83/usr/bin/php /usr/local/bin/wp wc product create \
--user=1 \
--name="eBook Guia SEO 2026" \
--type=simple \
--virtual=true \
--downloadable=true \
--regular_price="19.90" \
--sku="EBOOK-SEO-2026" \
--downloads='[{"name":"eBook SEO 2026","file":"https://site.pt/ficheiros/ebook-seo-2026.pdf"}]' \
--download_limit=3 \
--download_expiry=365 \
--allow-root --path=$PATH_WP
```
### Produto Externo/Afiliado
```bash
/opt/alt/php-fpm83/usr/bin/php /usr/local/bin/wp wc product create \
--user=1 \
--name="Software Cloud Parceiro" \
--type=external \
--regular_price="99.00" \
--product_url="https://parceiro.com/checkout/123" \
--button_text="Comprar na Loja do Parceiro" \
--sku="EXT-SOFT-123" \
--allow-root --path=$PATH_WP
```
### Produto Agrupado (Grouped)
```bash
# 1. Criar produto pai agrupado
GROUP_ID=$(/opt/alt/php-fpm83/usr/bin/php /usr/local/bin/wp wc product create \
--user=1 --name="Pack Bundle Completo" --type=grouped --sku="PACK-BUNDLE-001" \
--porcelain --allow-root --path=$PATH_WP)
# 2. Criar filho vinculado ao grupo
/opt/alt/php-fpm83/usr/bin/php /usr/local/bin/wp wc product create \
--user=1 \
--name="Item A do Pack" \
--type=simple \
--regular_price="15.00" \
--sku="PACK-ITEM-A" \
--parent_id=$GROUP_ID \
--allow-root --path=$PATH_WP
```
### Produto Variável com Variações
```bash
# 1. Criar produto pai variável
PARENT_ID=$(/opt/alt/php-fpm83/usr/bin/php /usr/local/bin/wp wc product create \
--user=1 \
--name="Sapatilhas Running" \
--type=variable \
--attributes='[{"name":"Tamanho","variation":true,"visible":true,"options":["39","40","41","42","43"]},{"name":"Cor","variation":true,"visible":true,"options":["Preto","Branco"]}]' \
--porcelain --allow-root --path=$PATH_WP)
# 2. Criar variação (Tamanho 40, Cor Preto)
/opt/alt/php-fpm83/usr/bin/php /usr/local/bin/wp wc product_variation create $PARENT_ID \
--user=1 \
--regular_price="89.99" \
--sku="RUN-40-PT" \
--attributes='[{"name":"Tamanho","option":"40"},{"name":"Cor","option":"Preto"}]' \
--manage_stock=true \
--stock_quantity=25 \
--allow-root --path=$PATH_WP
```
### Produto com Categorias e Atributos Visíveis
```bash
/opt/alt/php-fpm83/usr/bin/php /usr/local/bin/wp wc product create \
--user=1 \
--name="Casaco Impermeável Inverno" \
--type=simple \
--regular_price="120.00" \
--categories='[{"id":15},{"id":22}]' \
--attributes='[{"name":"Material","visible":true,"options":["Poliéster","Gore-Tex"]},{"name":"Cor","visible":true,"options":["Preto","Azul Marinho"]}]' \
--allow-root --path=$PATH_WP
```
---
## Gestão de Stock
```bash
PATH_WP="/home/USER/public_html"
# Actualizar stock de produto único
/opt/alt/php-fpm83/usr/bin/php /usr/local/bin/wp wc product update 456 \
--user=1 \
--manage_stock=true \
--stock_quantity=75 \
--stock_status=instock \
--allow-root --path=$PATH_WP
# Colocar produto fora de stock (sem alterar quantidade)
/opt/alt/php-fpm83/usr/bin/php /usr/local/bin/wp wc product update 456 \
--user=1 --stock_status=outofstock \
--allow-root --path=$PATH_WP
# Actualizar stock em massa por prefixo SKU
WP="/opt/alt/php-fpm83/usr/bin/php /usr/local/bin/wp --allow-root --path=$PATH_WP"
for ID in $($WP wc product list --user=1 --sku="TSHIRT-*" --format=ids); do
$WP wc product update $ID --user=1 --stock_quantity=200 --porcelain
done
```
---
## Gestão de Encomendas
### Consulta e Filtros
```bash
PATH_WP="/home/USER/public_html"
# Encomendas por estado
/opt/alt/php-fpm83/usr/bin/php /usr/local/bin/wp wc shop_order list \
--user=1 --status=processing \
--fields=id,total,currency,date_created \
--format=table \
--allow-root --path=$PATH_WP
# Estados disponíveis: pending, processing, on-hold, completed, cancelled, refunded, failed
```
### Actualizar Estado
```bash
/opt/alt/php-fpm83/usr/bin/php /usr/local/bin/wp wc shop_order update 355 \
--user=1 --status=completed \
--allow-root --path=$PATH_WP
```
### Adicionar Nota (Visível ao Cliente)
```bash
/opt/alt/php-fpm83/usr/bin/php /usr/local/bin/wp wc order_note create 355 \
--user=1 \
--note="Expedido via CTT. Rastreio: PT-99887766." \
--customer_note=true \
--allow-root --path=$PATH_WP
```
### Conclusão Massiva com Relatório
```bash
#!/bin/bash
PATH_WP="/home/USER/public_html"
WP="/opt/alt/php-fpm83/usr/bin/php /usr/local/bin/wp --allow-root --path=$PATH_WP"
ORDER_IDS=$($WP wc shop_order list --user=1 --status=processing --format=ids)
TOTAL=0
for ID in $ORDER_IDS; do
$WP wc shop_order update $ID --user=1 --status=completed --porcelain
TOTAL=$((TOTAL+1))
echo "[OK] Encomenda #$ID concluída."
done
echo "Total processado: $TOTAL encomendas."
```
---
## Clientes
```bash
PATH_WP="/home/USER/public_html"
# Listar com métricas de lifetime value
/opt/alt/php-fpm83/usr/bin/php /usr/local/bin/wp wc customer list \
--user=1 \
--role=customer \
--fields=id,email,orders_count,total_spent \
--format=table \
--allow-root --path=$PATH_WP
# Criar cliente completo (B2B)
/opt/alt/php-fpm83/usr/bin/php /usr/local/bin/wp wc customer create \
--user=1 \
--email="empresa@exemplo.pt" \
--first_name="Carlos" \
--last_name="Mendes" \
--username="carlosmendes_b2b" \
--password="SenhaForte!2026" \
--billing='{"first_name":"Carlos","last_name":"Mendes","company":"Empresa Lda","address_1":"Avenida da Liberdade 123","city":"Lisboa","postcode":"1250-140","country":"PT"}' \
--shipping='{"first_name":"Carlos","last_name":"Mendes","company":"Armazém Empresa","address_1":"Rua da Indústria 45","city":"Sintra","postcode":"2710-000","country":"PT"}' \
--allow-root --path=$PATH_WP
```
---
## Shipping Zones — Configuração Completa
```bash
PATH_WP="/home/USER/public_html"
# 1. Criar zona geográfica
ZONE_ID=$(/opt/alt/php-fpm83/usr/bin/php /usr/local/bin/wp wc shipping_zone create \
--user=1 --name="Portugal Continental" \
--porcelain --allow-root --path=$PATH_WP)
# 2. Adicionar países à zona
/opt/alt/php-fpm83/usr/bin/php /usr/local/bin/wp wc shipping_zone_location create $ZONE_ID \
--user=1 --code="PT" --type=country \
--allow-root --path=$PATH_WP
# 3. Adicionar método de portes fixos
METHOD_ID=$(/opt/alt/php-fpm83/usr/bin/php /usr/local/bin/wp wc shipping_zone_method create $ZONE_ID \
--user=1 --method_id=flat_rate \
--porcelain --allow-root --path=$PATH_WP)
# 4. Definir custo
/opt/alt/php-fpm83/usr/bin/php /usr/local/bin/wp wc shipping_zone_method update $ZONE_ID $METHOD_ID \
--user=1 --settings='{"cost":"3.99","title":"Portes Padrão"}' \
--allow-root --path=$PATH_WP
# 5. Adicionar envio gratuito acima de X€
FREE_ID=$(/opt/alt/php-fpm83/usr/bin/php /usr/local/bin/wp wc shipping_zone_method create $ZONE_ID \
--user=1 --method_id=free_shipping \
--porcelain --allow-root --path=$PATH_WP)
/opt/alt/php-fpm83/usr/bin/php /usr/local/bin/wp wc shipping_zone_method update $ZONE_ID $FREE_ID \
--user=1 --settings='{"requires":"min_amount","min_amount":"50"}' \
--allow-root --path=$PATH_WP
# Listar todas as zonas
/opt/alt/php-fpm83/usr/bin/php /usr/local/bin/wp wc shipping_zone list \
--user=1 --format=table --allow-root --path=$PATH_WP
```
---
## Payment Gateways
```bash
PATH_WP="/home/USER/public_html"
# Listar todos os gateways
/opt/alt/php-fpm83/usr/bin/php /usr/local/bin/wp wc payment_gateway list \
--user=1 --fields=id,title,enabled --allow-root --path=$PATH_WP
# Activar gateway
/opt/alt/php-fpm83/usr/bin/php /usr/local/bin/wp wc payment_gateway update bacs \
--user=1 --enabled=true --allow-root --path=$PATH_WP
# Desactivar gateway (emergência)
/opt/alt/php-fpm83/usr/bin/php /usr/local/bin/wp wc payment_gateway update paypal \
--user=1 --enabled=false --allow-root --path=$PATH_WP
# Configurar Stripe em modo teste
/opt/alt/php-fpm83/usr/bin/php /usr/local/bin/wp wc payment_gateway update stripe \
--user=1 \
--settings='{"testmode":"yes","test_publishable_key":"pk_test_XXXX","test_secret_key":"sk_test_XXXX"}' \
--allow-root --path=$PATH_WP
```
---
## Tributação
```bash
PATH_WP="/home/USER/public_html"
# Criar taxa IVA 23% (Portugal)
/opt/alt/php-fpm83/usr/bin/php /usr/local/bin/wp wc tax create \
--user=1 \
--country=PT \
--rate="23" \
--name="IVA 23%" \
--class=standard \
--allow-root --path=$PATH_WP
# Criar taxa reduzida 6%
/opt/alt/php-fpm83/usr/bin/php /usr/local/bin/wp wc tax create \
--user=1 --country=PT --rate="6" --name="IVA 6%" --class=reduced-rate \
--allow-root --path=$PATH_WP
# Criar classe de taxa personalizada
/opt/alt/php-fpm83/usr/bin/php /usr/local/bin/wp wc tax_class create \
--user=1 --name="Taxa Super Reduzida" \
--allow-root --path=$PATH_WP
# Activar cálculo de impostos
/opt/alt/php-fpm83/usr/bin/php /usr/local/bin/wp option update woocommerce_calc_taxes "yes" \
--allow-root --path=$PATH_WP
```
---
## Cupões
```bash
PATH_WP="/home/USER/public_html"
# Criar cupão de desconto percentual
/opt/alt/php-fpm83/usr/bin/php /usr/local/bin/wp wc shop_coupon create \
--user=1 \
--code="VERAO2026" \
--discount_type=percent \
--amount="15" \
--usage_limit=500 \
--expiry_date="2026-09-30" \
--allow-root --path=$PATH_WP
# Criar cupão de desconto fixo
/opt/alt/php-fpm83/usr/bin/php /usr/local/bin/wp wc shop_coupon create \
--user=1 \
--code="DESCONTO10" \
--discount_type=fixed_cart \
--amount="10" \
--minimum_amount="50" \
--allow-root --path=$PATH_WP
# Listar cupões activos
/opt/alt/php-fpm83/usr/bin/php /usr/local/bin/wp wc shop_coupon list \
--user=1 --fields=id,code,amount,discount_type,usage_count \
--format=table --allow-root --path=$PATH_WP
```
---
## Ferramentas de Manutenção
```bash
PATH_WP="/home/USER/public_html"
WP="/opt/alt/php-fpm83/usr/bin/php /usr/local/bin/wp --allow-root --path=$PATH_WP"
# Listar todas as ferramentas
$WP wc tool list --user=1 --fields=id,name,description --format=table
# Limpar transients
$WP wc tool run clear_transients --user=1
# Recalcular contadores de taxonomias (categorias/tags)
$WP wc tool run recount_terms --user=1
# Eliminar variações órfãs
$WP wc tool run delete_orphaned_variations --user=1
# Limpar sessões e carrinhos abandonados
$WP wc tool run clear_sessions --user=1
# Regenerar imagens de produto
$WP wc tool run regenerate_thumbnails --user=1
# Regenerar dados de lookup (tabela de relatórios)
$WP wc tool run clear_lookup_table --user=1
# Sequência completa de limpeza (recomendada após alterações massivas)
$WP wc tool run delete_orphaned_variations --user=1
$WP wc tool run recount_terms --user=1
$WP wc tool run clear_sessions --user=1
$WP wc tool run clear_transients --user=1
$WP cache flush
$WP rewrite flush
echo "Limpeza concluída."
```
---
## WP All Import — Importação Massiva
```bash
PATH_WP="/home/USER/public_html"
# Verificar se plugin está activo
/opt/alt/php-fpm83/usr/bin/php /usr/local/bin/wp plugin is-active wp-all-import-pro \
--allow-root --path=$PATH_WP && echo "Activo" || echo "Inactivo"
# Executar perfil de importação pré-configurado (ID=5)
# Configurar mapeamentos no painel WP All Import primeiro
/opt/alt/php-fpm83/usr/bin/php /usr/local/bin/wp all-import run 5 \
--force-run --disable-log \
--allow-root --path=$PATH_WP
# Listar perfis de importação
/opt/alt/php-fpm83/usr/bin/php /usr/local/bin/wp all-import status \
--allow-root --path=$PATH_WP
```
---
## Importação via CSV (Script)
```bash
#!/bin/bash
# Importar produtos a partir de CSV (colunas: SKU,TITULO,PRECO)
PATH_WP="/home/USER/public_html"
WP="/opt/alt/php-fpm83/usr/bin/php /usr/local/bin/wp --allow-root --path=$PATH_WP"
CSV="/home/USER/produtos.csv"
ERROS=0
OK=0
tail -n +2 "$CSV" | while IFS=, read -r SKU TITULO PRECO; do
ID=$($WP wc product create \
--user=1 \
--sku="$SKU" \
--name="$TITULO" \
--regular_price="$PRECO" \
--type=simple \
--status=publish \
--porcelain 2>/dev/null)
if [ -n "$ID" ] && [ "$ID" -gt 0 ] 2>/dev/null; then
echo "[OK] $SKU → ID $ID"
OK=$((OK+1))
else
echo "[ERRO] $SKU — falhou"
ERROS=$((ERROS+1))
fi
done
echo "Importação concluída. OK: $OK | Erros: $ERROS"
```
---
## Scripts de Manutenção
### Normalização Preventiva de Dados
```bash
#!/bin/bash
PATH_WP="/home/USER/public_html"
WP="/opt/alt/php-fpm83/usr/bin/php /usr/local/bin/wp --allow-root --path=$PATH_WP"
echo "=== LIMPEZA WOOCOMMERCE ==="
echo "Site: $PATH_WP | $(date)"
$WP wc tool run delete_orphaned_variations --user=1 --quiet && echo "[OK] Variações órfãs"
$WP wc tool run recount_terms --user=1 --quiet && echo "[OK] Contadores taxonomias"
$WP wc tool run clear_sessions --user=1 --quiet && echo "[OK] Sessões"
$WP wc tool run clear_transients --user=1 --quiet && echo "[OK] Transients"
$WP cache flush && echo "[OK] Object cache"
$WP rewrite flush && echo "[OK] Rewrite rules"
$WP db optimize --quiet && echo "[OK] BD optimizada"
echo "=== CONCLUÍDO ==="
```
### Manutenção WooCommerce em Todos os Sites CWP
```bash
#!/bin/bash
find /home -name wp-config.php -not -path "*/backup*" 2>/dev/null | while read config; do
dir=$(dirname "$config")
user=$(stat -c '%U' "$dir")
php_fpm=$(ps aux | grep "php-fpm: pool ${user}" | grep -v grep | head -1 | grep -o 'php-fpm[0-9][0-9]' | head -1)
php_ver=${php_fpm:-php-fpm82}
php_bin="/opt/alt/${php_ver}/usr/bin/php"
WP="$php_bin /usr/local/bin/wp --allow-root --path=$dir"
echo "=== $user ($dir) ==="
# Verificar se WooCommerce está activo
if $WP plugin is-active woocommerce 2>/dev/null; then
$WP wc tool run clear_transients --user=1 --quiet 2>/dev/null
$WP wc tool run recount_terms --user=1 --quiet 2>/dev/null
echo " [WC] Limpeza WooCommerce concluída."
else
echo " [--] WooCommerce não activo."
fi
done
```
---
## Limitações e Bugs Conhecidos
### Memory Exhaustion — Override CWP
```bash
# -d memory_limit antes de /usr/local/bin/wp
/opt/alt/php-fpm83/usr/bin/php -d memory_limit=2G /usr/local/bin/wp wc product list \
--user=1 --per_page=100 --format=ids \
--allow-root --path=/home/USER/public_html
# Para catálogos muito grandes: iterar com --per_page=100 e --page=N
for PAGE in 1 2 3 4 5; do
/opt/alt/php-fpm83/usr/bin/php /usr/local/bin/wp wc product list \
--user=1 --per_page=100 --page=$PAGE --format=ids \
--allow-root --path=/home/USER/public_html
done
```
### Bug `wp wc update` — Versão BD não actualiza
```bash
# Verificar versão instalada
/opt/alt/php-fpm83/usr/bin/php /usr/local/bin/wp plugin get woocommerce \
--field=version --allow-root --path=/home/USER/public_html
# Forçar actualização manual do registo BD
/opt/alt/php-fpm83/usr/bin/php /usr/local/bin/wp option update woocommerce_db_version "9.X.X" \
--allow-root --path=/home/USER/public_html
```
### Bypass Wizard de Onboarding
```bash
/opt/alt/php-fpm83/usr/bin/php /usr/local/bin/wp option update woocommerce_onboarding_opt_in "yes" \
--allow-root --path=/home/USER/public_html
/opt/alt/php-fpm83/usr/bin/php /usr/local/bin/wp option update woocommerce_task_list_hidden "yes" \
--allow-root --path=/home/USER/public_html
/opt/alt/php-fpm83/usr/bin/php /usr/local/bin/wp option update woocommerce_setup_wizard_data \
'{"setup_wizard":"completed"}' --allow-root --path=/home/USER/public_html
```
---
## Settings via wp_options
Algumas configurações WooCommerce não têm endpoint CLI nativo. Usar `wp option update` directamente:
```bash
PATH_WP="/home/USER/public_html"
# Moeda
/opt/alt/php-fpm83/usr/bin/php /usr/local/bin/wp option update woocommerce_currency "EUR" \
--allow-root --path=$PATH_WP
# Posição do símbolo de moeda
/opt/alt/php-fpm83/usr/bin/php /usr/local/bin/wp option update woocommerce_currency_pos "right_space" \
--allow-root --path=$PATH_WP
# Separadores de preço (PT-PT)
/opt/alt/php-fpm83/usr/bin/php /usr/local/bin/wp option update woocommerce_price_thousand_sep "." \
--allow-root --path=$PATH_WP
/opt/alt/php-fpm83/usr/bin/php /usr/local/bin/wp option update woocommerce_price_decimal_sep "," \
--allow-root --path=$PATH_WP
# País de base da loja
/opt/alt/php-fpm83/usr/bin/php /usr/local/bin/wp option update woocommerce_default_country "PT" \
--allow-root --path=$PATH_WP
# Activar impostos
/opt/alt/php-fpm83/usr/bin/php /usr/local/bin/wp option update woocommerce_calc_taxes "yes" \
--allow-root --path=$PATH_WP
```
---
*Referência WooCommerce CLI CWP | Descomplicar® | v1.0.0 | 18-02-2026*
+190
View File
@@ -0,0 +1,190 @@
---
name: woocommerce
description: Desenvolvimento e optimização de lojas WooCommerce. Personalização de checkout, integração de gateways de pagamento portugueses (Multibanco, MB Way, IfthenPay), funcionalidades custom e performance.
layer: wiki
---
# /woocommerce - WooCommerce Development
Desenvolvimento e optimização de lojas WooCommerce.
## Quando Usar
- Configurar loja WooCommerce
- Personalizar checkout
- Integrar gateways de pagamento PT
- Desenvolver funcionalidades custom
- Optimizar performance
## Contexto NotebookLM
ANTES de executar, consultar notebooks para contexto especializado:
| Notebook | ID | Consultar quando |
|----------|-----|-----------------|
| WordPress e Elementor | 5be0d1a6 | Sempre |
| E-commerce Prático | 226e384e | Para estratégia e-commerce |
```
mcp__notebooklm__notebook_query({
notebook_id: "5be0d1a6-00f2-4cd9-b835-978cb7721601",
query: "<adaptar ao contexto do pedido do utilizador>"
})
mcp__notebooklm__notebook_query({
notebook_id: "226e384e-d4bc-48f4-bb82-7927360436cc",
query: "<adaptar ao contexto e-commerce do pedido>"
})
```
Integrar insights do NotebookLM nas recomendações e decisões.
### Procedimentos Relacionados
- `PROC-WiP-Starter-Template.md` — Template inicial para lojas WooCommerce
---
## Hooks WooCommerce Essenciais
### Actions
```php
// Before/after cart
add_action('woocommerce_before_cart', 'show_cart_notice');
add_action('woocommerce_after_cart', 'show_shipping_info');
// Checkout
add_action('woocommerce_before_checkout_form', 'show_checkout_notice');
add_action('woocommerce_checkout_order_processed', 'after_order', 10, 3);
// Product
add_action('woocommerce_before_single_product', 'show_product_notice');
add_action('woocommerce_after_add_to_cart_button', 'add_custom_button');
// Admin
add_action('woocommerce_admin_order_data_after_billing_address', 'show_admin_meta');
add_action('woocommerce_process_product_meta', 'save_product_meta');
```
### Filters
```php
// Modificar preço
add_filter('woocommerce_cart_item_price', 'custom_price', 10, 2);
add_filter('woocommerce_get_price_html', 'custom_price_html', 10, 2);
// Checkout fields
add_filter('woocommerce_checkout_fields', 'custom_checkout_fields');
add_filter('woocommerce_billing_fields', 'custom_billing_fields');
// Cart
add_filter('woocommerce_add_cart_item_data', 'add_cart_item_data', 10, 2);
add_filter('woocommerce_cart_item_name', 'custom_cart_item_name', 10, 3);
// Emails
add_filter('woocommerce_email_subject_new_order', 'custom_email_subject', 10, 2);
```
## Gateways Portugal
| Gateway | Plugin | Notas |
|---------|--------|-------|
| **MB Way** | IfthenPay / Eupago | Pagamento instantâneo |
| **Multibanco** | IfthenPay / Eupago | Referências MB |
| **Stripe** | Stripe for WooCommerce | Cartões + Apple/Google Pay |
| **PayPal** | WooCommerce PayPal | Standard e Express |
### Configuração IfthenPay
```php
// Webhook para confirmação automática
add_action('woocommerce_api_ifthenpay_webhook', 'handle_ifthenpay_callback');
function handle_ifthenpay_callback() {
$order_id = $_GET['order_id'] ?? null;
$status = $_GET['status'] ?? null;
if ($status === 'success') {
$order = wc_get_order($order_id);
$order->payment_complete();
}
}
```
## Customização Checkout
```php
// Adicionar campo NIF
add_filter('woocommerce_checkout_fields', function($fields) {
$fields['billing']['billing_nif'] = [
'label' => 'NIF',
'required' => true,
'class' => ['form-row-wide'],
'validate' => ['validate_nif']
];
return $fields;
});
// Validar NIF
add_action('woocommerce_checkout_process', function() {
if (empty($_POST['billing_nif'])) {
wc_add_notice('NIF é obrigatório', 'error');
}
});
// Guardar NIF
add_action('woocommerce_checkout_update_order_meta', function($order_id) {
if (!empty($_POST['billing_nif'])) {
update_post_meta($order_id, '_billing_nif', sanitize_text_field($_POST['billing_nif']));
}
});
```
## REST API
```php
// Endpoint custom
add_action('rest_api_init', function() {
register_rest_route('wc/v3', '/products/featured', [
'methods' => 'GET',
'callback' => function() {
$args = [
'status' => 'publish',
'featured' => true,
'limit' => 10
];
$products = wc_get_products($args);
return rest_ensure_response($products);
}
]);
});
```
## WP-CLI WooCommerce
```bash
# Listar produtos
wp wc product list --user=1 --format=table
# Criar produto
wp wc product create --name="Produto Teste" --regular_price=99.99 --user=1
# Listar pedidos
wp wc order list --status=processing --user=1
# Actualizar stock
wp wc product update 123 --stock_quantity=50 --user=1
# Limpar transients
wp transient delete --all
```
## Integrações
### Moloni (Facturação)
```php
// Auto-criar factura após pedido pago
add_action('woocommerce_order_status_completed', 'create_moloni_invoice');
functi
+185
View File
@@ -0,0 +1,185 @@
---
name: wp-cli
description: Gestão WordPress via WP-CLI no servidor CWP. Cobre core, plugins, temas, base de dados, utilizadores, migração, search-replace e automação com suporte multi-PHP.
layer: wiki
---
# /wp-cli - Gestão WordPress via WP-CLI no CWP
Operações WordPress via linha de comandos no servidor CWP (server.descomplicar.pt). Cobre core, plugins, temas, base de dados, utilizadores, migração e automação.
**Servidor:** server.descomplicar.pt (176.9.3.158) | porta SSH: 9443
**Fonte:** `Hub/06-Operacoes/Documentacao/Manuais/WP-CLI no CWP.md`
**NotebookLM:** [WordPress Config CLI](https://notebooklm.google.com/notebook/fb2f26bd-8cb0-4d4c-bafc-4f1ebb51c51d)
---
## Contexto CWP — Regras Obrigatórias
```bash
# 1. Usar sempre --allow-root (CWP executa como root)
# 2. Especificar sempre --path= (não fazer cd)
# 3. Usar o PHP correcto para cada utilizador
# Formato base
/opt/alt/php-fpm83/usr/bin/php /usr/local/bin/wp COMANDO --allow-root --path=/home/USER/public_html
# Aliases ~/.bashrc (se configurados)
alias wp74='/opt/alt/php-fpm74/usr/bin/php /usr/local/bin/wp --allow-root'
alias wp82='/opt/alt/php-fpm82/usr/bin/php /usr/local/bin/wp --allow-root'
alias wp83='/opt/alt/php-fpm83/usr/bin/php /usr/local/bin/wp --allow-root'
```
### Versões PHP disponíveis
| Alias | PHP | Path |
|-------|-----|------|
| `wp74` | 7.4 | `/opt/alt/php-fpm74/usr/bin/php` |
| `wp80` | 8.0 | `/opt/alt/php-fpm80/usr/bin/php` |
| `wp81` | 8.1 | `/opt/alt/php-fpm81/usr/bin/php` |
| `wp82` | 8.2 | `/opt/alt/php-fpm82/usr/bin/php` |
| `wp83` | 8.3 | `/opt/alt/php-fpm83/usr/bin/php` |
### Detectar PHP activo de um utilizador
```bash
# Ver pool PHP-FPM do utilizador
ps aux | grep "php-fpm: pool USER" | grep -v grep | head -1
# Ou usar wp-auto.sh (auto-detect)
/root/wp-auto.sh --path=/home/USER/public_html plugin list
```
O script `wp-auto.sh` está em `scripts/wp-auto.sh` desta skill.
---
## Core WordPress
```bash
PATH=/home/USER/public_html
# Verificar versão
wp core version --allow-root --path=$PATH
# Actualizar WordPress
wp core update --allow-root --path=$PATH
# Verificar integridade dos ficheiros
wp core verify-checksums --allow-root --path=$PATH
# Instalar versão específica
wp core download --version=6.4.3 --force --allow-root --path=$PATH
# Informações ambiente
wp --info --allow-root --path=$PATH
```
---
## Plugins
```bash
# Listar todos os plugins
wp plugin list --allow-root --path=$PATH
# Listar plugins desactualizados
wp plugin list --update=available --format=table --allow-root --path=$PATH
# Actualizar todos os plugins
wp plugin update --all --allow-root --path=$PATH
# Instalar e activar
wp plugin install akismet --activate --allow-root --path=$PATH
# Activar / desactivar
wp plugin activate NOME --allow-root --path=$PATH
wp plugin deactivate NOME --allow-root --path=$PATH
# Remover plugin
wp plugin delete NOME --allow-root --path=$PATH
# Verificar integridade
wp plugin verify-checksums --all --allow-root --path=$PATH
```
---
## Temas
```bash
# Listar temas
wp theme list --allow-root --path=$PATH
# Instalar e activar
wp theme install hello-elementor --activate --allow-root --path=$PATH
# Actualizar todos os temas
wp theme update --all --allow-root --path=$PATH
# Activar tema
wp theme activate NOME --allow-root --path=$PATH
```
---
## Base de Dados
```bash
# Exportar (backup)
wp db export /home/USER/backup_$(date +%Y%m%d_%H%M).sql --allow-root --path=$PATH
# Importar (restaurar)
wp db import backup.sql --allow-root --path=$PATH
# Optimizar tabelas
wp db optimize --allow-root --path=$PATH
# Reparar tabelas corrompidas
wp db repair --allow-root --path=$PATH
# Query directa
wp db query "SELECT option_name, option_value FROM wp_options WHERE option_name='siteurl';" --allow-root --path=$PATH
# Tamanho tabelas
wp db query "SELECT table_name, ROUND((data_length+index_length)/1024/1024,2) AS MB FROM information_schema.tables WHERE table_schema=DATABASE() ORDER BY MB DESC LIMIT 20;" --allow-root --path=$PATH
```
---
## Migração de Site (Search-Replace)
```bash
# Preview (dry-run) — SEMPRE primeiro
wp search-replace 'https://old.com' 'https://new.com' --dry-run --allow-root --path=$PATH
# Executar após confirmar
wp search-replace 'https://old.com' 'https://new.com' --allow-root --path=$PATH
# Com relatório
wp search-replace 'old.com' 'new.com' --report --allow-root --path=$PATH
# Após migração: limpar cache e regenerar rewrite rules
wp cache flush --allow-root --path=$PATH
wp rewrite flush --allow-root --path=$PATH
```
---
## Utilizadores
```bash
# Listar utilizadores
wp user list --allow-root --path=$PATH
# Criar administrador
wp user create admin admin@site.pt --role=administrator --user_pass=SENHA --allow-root --path=$PATH
# Redefinir password
wp user update USERNAME --user_pass=NOVA_SENHA --allow-root --path=$PATH
# Adicionar papel a utilizador existente
wp user set-role USERNAME administrator --allow-root --path=$PATH
# Remover utilizador (transferindo conteúdo)
wp user delete USERNAME
@@ -0,0 +1,376 @@
# WP-CLI no CWP — Referência Completa de Comandos
**Servidor:** server.descomplicar.pt (176.9.3.158) | porta 9443
**PHP padrão:** `/opt/alt/php-fpm83/usr/bin/php`
---
## Formato Base
```bash
/opt/alt/php-fpm83/usr/bin/php /usr/local/bin/wp GRUPO SUBCOMANDO [FLAGS] --allow-root --path=/home/USER/public_html
```
---
## Core WordPress
```bash
wp core version # Versão actual
wp core update # Actualizar para latest
wp core update --version=6.4.3 --force # Versão específica
wp core download # Descarregar sem instalar
wp core verify-checksums # Verificar integridade ficheiros
wp core install --url=... --title=... \
--admin_user=admin --admin_email=... # Instalar novo WordPress
wp core is-installed # Verificar se está instalado
wp core update-db # Actualizar base de dados após update
```
---
## Plugins
```bash
wp plugin list # Listar todos
wp plugin list --status=inactive # Só inactivos
wp plugin list --update=available # Com updates disponíveis
wp plugin install SLUG # Instalar
wp plugin install SLUG --activate # Instalar e activar
wp plugin install SLUG --version=X.X # Versão específica
wp plugin activate SLUG # Activar
wp plugin deactivate SLUG # Desactivar
wp plugin deactivate --all # Desactivar todos (debug)
wp plugin delete SLUG # Remover ficheiros
wp plugin update SLUG # Actualizar plugin específico
wp plugin update --all # Actualizar todos
wp plugin update --all --dry-run # Preview de updates
wp plugin verify-checksums SLUG # Verificar integridade
wp plugin verify-checksums --all # Verificar todos
wp plugin search TERMO # Pesquisar no repo WordPress.org
wp plugin get SLUG # Info detalhada
wp plugin path SLUG # Path do plugin no servidor
```
---
## Temas
```bash
wp theme list # Listar todos
wp theme install SLUG # Instalar
wp theme install SLUG --activate # Instalar e activar
wp theme activate SLUG # Activar tema
wp theme update SLUG # Actualizar
wp theme update --all # Actualizar todos
wp theme delete SLUG # Remover
wp theme get SLUG # Info detalhada
wp theme is-active SLUG # Verificar se activo
```
---
## Base de Dados
```bash
wp db export /caminho/backup.sql # Exportar
wp db export - | gzip > backup.sql.gz # Exportar comprimido
wp db import backup.sql # Importar
wp db optimize # Optimizar tabelas
wp db repair # Reparar tabelas
wp db check # Verificar integridade
wp db size # Tamanho da BD
wp db tables # Listar tabelas
wp db prefix # Prefixo actual
wp db query "SQL;" # Query directa
wp db search "TERMO" # Pesquisar em toda a BD
wp db clean --tables=revision # Limpar revisões
```
---
## Search-Replace (Migração)
```bash
# SEMPRE fazer dry-run primeiro
wp search-replace 'old.com' 'new.com' --dry-run
# Executar
wp search-replace 'https://old.com' 'https://new.com'
# Só numa tabela
wp search-replace 'old' 'new' wp_options wp_postmeta
# Com relatório detalhado
wp search-replace 'old' 'new' --report-changed-only
# Sem serialization (mais rápido, menos seguro)
wp search-replace 'old' 'new' --no-php
# Número de linhas por batch (para BDs grandes)
wp search-replace 'old' 'new' --precise
# Skip tabelas específicas
wp search-replace 'old' 'new' --skip-tables=wp_users
```
---
## Options WordPress
```bash
wp option get NOME # Ler option
wp option update NOME VALOR # Actualizar (simples)
wp option patch update NOME SUBKEY VALOR # Actualizar sub-key (serialized)
wp option pluck NOME SUBKEY # Ler sub-key
wp option add NOME VALOR # Adicionar
wp option delete NOME # Apagar
wp option list # Listar todas
wp option list --search='rank*' # Filtrar por nome
wp option list --autoload=yes # Só com autoload
```
---
## Post Meta / User Meta / Term Meta
```bash
# Post Meta
wp post meta list POST_ID # Listar meta de um post
wp post meta get POST_ID META_KEY # Ler valor
wp post meta update POST_ID META_KEY VAL # Actualizar
wp post meta add POST_ID META_KEY VAL # Adicionar
wp post meta delete POST_ID META_KEY # Apagar
# Com formato JSON (para arrays/objects)
wp post meta update POST_ID META_KEY '["val1","val2"]' --format=json
# Term Meta
wp term meta list TERM_ID
wp term meta get TERM_ID META_KEY
wp term meta update TERM_ID META_KEY VAL
# User Meta
wp user meta list USER_ID
wp user meta get USER_ID META_KEY
wp user meta update USER_ID META_KEY VAL
```
---
## Posts e Conteúdo
```bash
wp post list # Listar posts
wp post list --post_type=page # Só páginas
wp post list --post_status=draft # Só rascunhos
wp post get POST_ID # Info completa
wp post create --post_type=post \
--post_title="Título" \
--post_status=publish # Criar post
wp post update POST_ID --post_title="..." # Actualizar
wp post delete POST_ID # Apagar (lixo)
wp post delete POST_ID --force # Apagar definitivo
wp post generate --count=10 # Gerar posts de teste
```
---
## Utilizadores
```bash
wp user list # Listar todos
wp user list --role=administrator # Filtrar por papel
wp user get USER_ID # Info utilizador
wp user get USERNAME --field=ID # Obter ID por username
wp user create LOGIN EMAIL \
--role=administrator \
--user_pass=SENHA # Criar
wp user update USER_ID --user_pass=SENHA # Alterar password
wp user update USER_ID --user_email=... # Alterar email
wp user set-role USER_ID administrator # Alterar papel
wp user delete USER_ID --reassign=1 # Apagar, transferindo conteúdo
wp user check-password USER_ID SENHA # Verificar password
wp user reset-password USER_ID # Reset password (envia email)
```
---
## Cache e Transientes
```bash
wp cache flush # Limpar object cache
wp cache get KEY [GROUP] # Ler cache
wp transient delete --all # Apagar todos transientes
wp transient delete NOME # Apagar transiente específico
wp transient get NOME # Ler transiente
wp rewrite flush # Regenerar rewrite rules
wp rewrite list # Listar rules actuais
```
---
## Cron
```bash
wp cron event list # Listar eventos agendados
wp cron event run HOOK_NAME # Executar agora
wp cron event delete HOOK_NAME # Apagar evento
wp cron event schedule HOOK cron now # Agendar evento
wp cron schedule list # Ver schedules disponíveis
wp cron test # Testar se cron está funcional
```
---
## wp eval — Execução PHP
```bash
# Código PHP inline
wp eval 'echo PHP_VERSION."\n";' --allow-root --path=$PATH
# Ficheiro PHP
wp eval-file script.php --allow-root --path=$PATH
# Com parâmetros
wp eval 'global $wpdb; echo $wpdb->prefix;' --allow-root --path=$PATH
# Ver constantes WordPress
wp eval 'echo WP_CONTENT_DIR."\n"; echo ABSPATH;' --allow-root --path=$PATH
# Executar com utilizador específico
wp eval 'echo get_current_user_id();' --user=1 --allow-root --path=$PATH
```
---
## Multisite
```bash
wp site list # Listar sites
wp site create --slug=subdomain # Criar site
wp site activate ID # Activar
wp site deactivate ID # Desactivar
wp site delete ID # Apagar
# Executar comando em todos os sites
wp site list --field=url | xargs -I {} wp --url={} plugin list --allow-root --path=$PATH
```
---
## WooCommerce CLI
```bash
wp wc product list # Listar produtos
wp wc product get ID # Produto específico
wp wc product create --name="Nome" \
--regular_price="19.99" # Criar produto
wp wc product update ID --regular_price="29.99" # Actualizar
wp wc product delete ID # Apagar
wp wc order list # Listar encomendas
wp wc order get ID # Encomenda específica
wp wc order update ID --status=completed # Mudar estado
wp wc customer list # Listar clientes
wp wc shipping_zone list # Zonas de entrega
```
---
## Segurança e Diagnóstico
```bash
# Verificar integridade completa
wp core verify-checksums --allow-root --path=$PATH
wp plugin verify-checksums --all --allow-root --path=$PATH
# Configuração PHP activa
wp eval 'echo PHP_VERSION."\n".php_ini_loaded_file()."\n";' --allow-root --path=$PATH
wp eval 'echo ini_get("memory_limit")."\n".ini_get("max_execution_time");' --allow-root --path=$PATH
# Ver configuração WordPress
wp option get siteurl --allow-root --path=$PATH
wp option get blogname --allow-root --path=$PATH
wp option get active_plugins --format=json --allow-root --path=$PATH
# Verificar se WP_DEBUG activo
wp eval 'echo WP_DEBUG ? "DEBUG ON" : "DEBUG OFF";' --allow-root --path=$PATH
# Listar ficheiros modificados recentemente (possível hack)
find /home/USER/public_html -name "*.php" -newer /home/USER/public_html/wp-config.php -not -path "*/wp-content/cache/*" 2>/dev/null | head -20
```
---
## Scripts de Manutenção
### Backup completo de um site
```bash
#!/bin/bash
PATH_WP="/home/USER/public_html"
BACKUP_DIR="/media/ealmeida/Externo/LocalBackups/WP"
DATE=$(date +%Y%m%d_%H%M)
SITE=$(basename $(dirname $PATH_WP))
mkdir -p "$BACKUP_DIR"
# Backup BD
/opt/alt/php-fpm83/usr/bin/php /usr/local/bin/wp db export \
"$BACKUP_DIR/${SITE}_db_${DATE}.sql" \
--allow-root --path=$PATH_WP
# Backup ficheiros
tar -czf "$BACKUP_DIR/${SITE}_files_${DATE}.tar.gz" \
-C $(dirname $PATH_WP) public_html \
--exclude="public_html/wp-content/cache"
echo "Backup completo: $BACKUP_DIR"
```
### Manutenção semanal (todos os sites)
```bash
#!/bin/bash
find /home -name wp-config.php -not -path "*/backup*" 2>/dev/null | while read config; do
dir=$(dirname "$config")
user=$(stat -c '%U' "$dir")
# Detectar PHP do utilizador
php_fpm=$(ps aux | grep "php-fpm: pool ${user}" | grep -v grep | head -1 | grep -o 'php-fpm[0-9][0-9]' | head -1)
php_ver=${php_fpm:-php-fpm82}
php_bin="/opt/alt/${php_ver}/usr/bin/php"
echo "=== $user ($dir) — PHP $php_ver ==="
$php_bin /usr/local/bin/wp core update --allow-root --path="$dir" 2>/dev/null
$php_bin /usr/local/bin/wp plugin update --all --allow-root --path="$dir" 2>/dev/null
$php_bin /usr/local/bin/wp theme update --all --allow-root --path="$dir" 2>/dev/null
$php_bin /usr/local/bin/wp db optimize --allow-root --path="$dir" 2>/dev/null
$php_bin /usr/local/bin/wp cache flush --allow-root --path="$dir" 2>/dev/null
done
```
---
## Flags Globais Úteis
| Flag | Descrição |
|------|-----------|
| `--allow-root` | Obrigatório no CWP (executa como root) |
| `--path=/home/USER/public_html` | Caminho da instalação WordPress |
| `--url=https://site.pt` | URL do site (importante em multisite) |
| `--user=1` | Executar como utilizador WordPress específico |
| `--format=json\|table\|csv\|ids` | Formato do output |
| `--fields=ID,name` | Campos a mostrar |
| `--debug` | Output de debug detalhado |
| `--quiet` | Sem output (para scripts) |
| `--dry-run` | Preview sem executar (onde disponível) |
| `--skip-plugins` | Não carregar plugins (troubleshooting) |
| `--skip-themes` | Não carregar temas |
---
*WP-CLI CWP — Referência Completa | Descomplicar® | v1.0.0 | 18-02-2026*
@@ -0,0 +1,34 @@
#!/bin/bash
# wp-auto.sh — WP-CLI com detecção automática de PHP no CWP
# Uso: /root/wp-auto.sh --path=/home/USER/public_html [comando wp-cli]
# Exemplo: /root/wp-auto.sh --path=/home/site/public_html plugin list
# Pegar utilizador do directório de trabalho
if [[ "$*" == *"--path="* ]]; then
WP_PATH=$(echo "$@" | grep -o '\-\-path=[^ ]*' | cut -d= -f2)
else
WP_PATH=$(pwd)
fi
USER=$(stat -c '%U' "$WP_PATH" 2>/dev/null || echo "nobody")
# Encontrar versão PHP-FPM activa para este utilizador
PHP_FPM_PROCESS=$(ps aux | grep "php-fpm: pool ${USER}" | grep -v grep | head -n 1)
PHP_VERSION=$(echo "$PHP_FPM_PROCESS" | grep -o 'php-fpm[0-9][0-9]' | grep -o '[0-9][0-9]' | head -1)
if [ -z "$PHP_VERSION" ]; then
echo "⚠ Usando PHP 8.2 (padrão — pool ${USER} não encontrado)" >&2
PHP_PATH="/opt/alt/php-fpm82/usr/bin/php"
else
PHP_PATH="/opt/alt/php-fpm${PHP_VERSION}/usr/bin/php"
echo "ℹ PHP ${PHP_VERSION} detectado para utilizador ${USER}" >&2
fi
# Verificar se o PHP existe
if [ ! -f "$PHP_PATH" ]; then
echo "❌ PHP não encontrado: $PHP_PATH — usando PHP 8.3" >&2
PHP_PATH="/opt/alt/php-fpm83/usr/bin/php"
fi
# Executar WP-CLI
exec "$PHP_PATH" /usr/local/bin/wp --allow-root "$@"
+178
View File
@@ -0,0 +1,178 @@
---
name: wp-dev
description: Desenvolvimento WordPress especializado com Quality Gate 70+. Cria e modifica plugins seguindo WordPress Coding Standards, desenvolve temas child, integra WooCommerce e resolve problemas WordPress.
layer: wiki
---
# WordPress Plugin Developer
Skill para desenvolvimento WordPress profissional seguindo padrões Descomplicar®.
## Quando Usar
- Criar ou modificar plugins WordPress
- Desenvolver temas ou child themes
- Integrar funcionalidades WooCommerce
- Resolver problemas específicos WordPress
- Criar custom post types, taxonomias, widgets
## Quando NÃO Usar
- Para operações CRM (usar /crm-ops)
- Para operações servidor/SSH genéricas (usar agente especializado)
- Para manutenção rotineira WP (usar /today às segundas)
## Contexto NotebookLM
ANTES de executar, consultar notebooks para contexto especializado:
| Notebook | ID | Consultar quando |
|----------|-----|-----------------|
| WordPress e Elementor | 5be0d1a6 | Sempre |
| WordPress Config CLI | fb2f26bd-8cb0-4d4c-bafc-4f1ebb51c51d | Para WP-CLI e configuração |
```
mcp__notebooklm__notebook_query({
notebook_id: "5be0d1a6-00f2-4cd9-b835-978cb7721601",
query: "<adaptar ao contexto do pedido do utilizador>"
})
mcp__notebooklm__notebook_query({
notebook_id: "fb2f26bd-8cb0-4d4c-bafc-4f1ebb51c51d",
query: "<adaptar ao contexto de WP-CLI e configuração WordPress>"
})
```
Integrar insights do NotebookLM nas recomendações e decisões.
### Procedimentos Relacionados
- `PROC-WiP-Definicao.md` — Definição de projectos WordPress in a Package
- `PROC-WiP-Instalacao.md` — Instalação e configuração WiP
- `PROC-WiP-Gestao.md` — Gestão e manutenção WiP
---
## Protocolo Obrigatório
### 1. Pesquisa Inicial (MCP)
Antes de desenvolver, SEMPRE executar:
```
mcp__mem_search_memories "[keywords wordpress]"
mcp__wikijs__search_pages "[plugin/funcionalidade]"
```
### 2. Quality Gate 70+
Todo código WordPress deve atingir score mínimo 70/100:
- WordPress Coding Standards (WPCS)
- Funções documentadas com PHPDoc
- Hooks e filters bem estruturados
- Sem warnings/notices PHP
- Segurança: nonces, sanitização, escaping
### 3. Assinatura Obrigatória
Todo ficheiro PHP deve incluir:
```php
<?php
/**
* [Nome do Plugin/Ficheiro]
*
* @author Descomplicar® Crescimento Digital
* @link https://descomplicar.pt
* @copyright 2025 Descomplicar®
*/
```
## Segurança WordPress (OBRIGATÓRIO)
- **Sanitizar inputs**: `sanitize_text_field()`, `sanitize_email()`, `absint()`
- **Escapar outputs**: `esc_html()`, `esc_attr()`, `esc_url()`
- **Nonces para forms**: `wp_nonce_field()`, `wp_verify_nonce()`
- **AJAX nonces**: `check_ajax_referer()`
- **Capability checks**: `current_user_can()` antes de acções admin
- **Prepared statements**: `$wpdb->prepare()` para queries
## Performance
- Enqueueing correcto: `wp_enqueue_script()`, `wp_enqueue_style()`
- Hooks no momento certo: `init`, `admin_init`, `wp_enqueue_scripts`
- Evitar queries em loops
- Usar transients para cache: `get_transient()`, `set_transient()`
## Estrutura Plugin Standard
```
plugin-name/
├── plugin-name.php # Ficheiro principal
├── includes/
│ ├── class-plugin-name.php
│ └── class-plugin-name-admin.php
├── assets/
│ ├── css/
│ └── js/
├── templates/
├── languages/
└── readme.txt
```
## Template Plugin Principal
```php
<?php
/**
* Plugin Name
*
* @package PluginName
* @author Descomplicar® Crescimento Digital
* @copyright 2025 Descomplicar®
* @license GPL-2.0-or-later
*
* @wordpress-plugin
* Plugin Name: Plugin Name
* Plugin URI: https://descomplicar.pt/plugin-name
* Description: Descrição do plugin.
* Version: 1.0.0
* Requires at least: 6.0
* Requires PHP: 8.0
* Author: Descomplicar®
* Author URI: https://descomplicar.pt
* Text Domain: plugin-name
* License: GPL v2 or later
* License URI: https://www.gnu.org/licenses/gpl-2.0.html
*/
defined('ABSPATH') || exit;
define('PLUGIN_NAME_VERSION', '1.0.0');
define('PLUGIN_NAME_PATH', plugin_dir_path(__FILE__));
define('PLUGIN_NAME_URL', plugin_dir_url(__FILE__));
// Autoloader ou includes
require_once PLUGIN_NAME_PATH . 'includes/class-plugin-name.php';
// Inicialização
function plugin_name_init() {
load_plugin_textdomain('plugin-name', false, dirname(plugin_basename(__FILE__)) . '/languages/');
}
add_action('plugins_loaded', 'plugin_name_init');
// Activação
register_activation_hook(__FILE__, 'plugin_name_activate');
function plugin_name_activate() {
// Criar tabelas, opções, etc.
}
// Desactivação
register_deactivation_hook(__FILE__, 'plugin_name_deactivate');
function plugin_name_deactivate() {
// Limpeza
}
```
## Checklist Antes de Entregar
- [ ] Assinatura Descomplicar® em todos os ficheiros
- [ ] Segurança: nonces, sanitização, escaping
- [ ] WordPress Coding Standards
- [ ] Sem erros PHP (php -l)
- [ ] Textdomain para traduções
- [ ] Uninstall hook para limpeza
+158
View File
@@ -0,0 +1,158 @@
---
name: wp-hardening
description: Aplicar hardening de seguranca standard a sites WordPress via SSH. Desactiva XML-RPC, file editing, directory listing, esconde versao WP, verifica permissoes e security headers.
layer: wiki
---
# /wp-hardening - Hardening WordPress
Aplica medidas de hardening standard a sites WordPress no servidor CWP (176.9.3.158).
> **Nota:** Esta skill APLICA alteracoes. Para auditar sem alterar, usar `/security-check`.
## Contexto NotebookLM
ANTES de executar, consultar notebook para contexto:
| Notebook | ID | Consultar quando |
|----------|-----|-----------------|
| WordPress e Elementor | 5be0d1a6 | Sempre |
```
mcp__notebooklm__notebook_query({
notebook_id: "5be0d1a6-00f2-4cd9-b835-978cb7721601",
query: "wordpress security hardening best practices"
})
```
---
## Uso
```
/wp-hardening descomplicar.pt
/wp-hardening all # todos os 16 sites
/wp-hardening descomplicar.pt --dry # apenas verificar, sem aplicar
```
**Argumento:** dominio do site (ver tabela de sites em /wp-update) ou `all`.
**Flag `--dry`:** apenas verifica estado actual sem aplicar alteracoes.
---
## Sites WordPress no servidor
| Conta | Site | Path |
|-------|------|------|
| carstuff | carstuff.pt | /home/carstuff/public_html |
| ealmeida | descomplicar.pt | /home/ealmeida/public_html |
| ealmeida | emanuelalmeida.pt | /home/ealmeida/emanuelalmeida.pt |
| ealmeida | ecommerce.descomplicar.pt | /home/ealmeida/ecommerce.descomplicar.pt |
| ealmeida | ecommerce-demo.descomplicar.pt | /home/ealmeida/ecommerce-demo.descomplicar.pt |
| ealmeida | starter.descomplicar.pt | /home/ealmeida/starter.descomplicar.pt |
| ealmeida | care.descomplicar.pt | /home/ealmeida/care.descomplicar.pt |
| ealmeida | e-commerce.descomplicar.pt | /home/ealmeida/e-commerce.descomplicar.pt |
| espiral | espiralsenior.pt | /home/espiral/public_html |
| espiral | dev.espiralsenior.pt | /home/espiral/dev.espiralsenior.pt |
| familycl | familyclinic.pt | /home/familycl/public_html |
| ignition | ignitionvortex.pt | /home/ignition/public_html |
| karate | karateclubedegaia.com | /home/karate/karateclubedegaia.com |
| sintri | sintricare.com.pt | /home/sintri/public_html |
| solarfv | solarfvengenharia.com | /home/solarfv/public_html |
| wtc | watercontrol.pt | /home/wtc/public_html |
---
## Execucao
### Passo 0: Identificar site e path
Mapear o dominio fornecido para o path e conta do servidor usando a tabela acima.
```bash
SITE="dominio.pt"
WP_PATH="/home/USER/public_html"
WP_USER="user"
```
### Passo 1: Verificar estado actual (antes)
Executar via `mcp__ssh-unified__ssh_execute` no servidor `cwp`:
```bash
# 1. XML-RPC activo?
curl -s -o /dev/null -w "%{http_code}" -X POST "${SITE}/xmlrpc.php" -d '<?xml version="1.0"?><methodCall><methodName>system.listMethods</methodName></methodCall>'
# 2. DISALLOW_FILE_EDIT definido?
grep -c "DISALLOW_FILE_EDIT" ${WP_PATH}/wp-config.php
# 3. Permissoes directórios (esperado: 755)
find ${WP_PATH} -type d -not -perm 755 | head -10
# 4. Permissoes ficheiros (esperado: 644)
find ${WP_PATH} -type f -not -perm 644 -not -name "wp-config.php" | head -10
# 5. Versao WP exposta no HTML?
curl -s ${SITE} | grep -o 'content="WordPress [0-9.]*"' | head -1
# 6. Versao WP exposta no feed?
curl -s ${SITE}/feed/ | grep -o '<generator>.*</generator>' | head -1
# 7. Directory listing activo?
curl -s -o /dev/null -w "%{http_code}" ${SITE}/wp-content/uploads/
# 8. wp-config.php permissoes (esperado: 400 ou 440)
stat -c "%a" ${WP_PATH}/wp-config.php
# 9. .htaccess security headers?
grep -c "X-Frame-Options\|X-Content-Type-Options\|X-XSS-Protection\|Referrer-Policy\|Content-Security-Policy\|Strict-Transport-Security" ${WP_PATH}/.htaccess 2>/dev/null
# 10. debug.log exposto?
test -f ${WP_PATH}/wp-content/debug.log && echo "EXPOSTO" || echo "OK"
```
Registar cada resultado como ANTES.
### Passo 2: Aplicar hardening (se nao --dry)
> **Regra:** Fazer backup de cada ficheiro ANTES de alterar.
#### 2.1 Desactivar XML-RPC
```bash
# Backup .htaccess
cp ${WP_PATH}/.htaccess ${WP_PATH}/.htaccess.bak.$(date +%Y%m%d)
# Adicionar bloqueio XML-RPC ao .htaccess (antes de # BEGIN WordPress)
sed -i '/# BEGIN WordPress/i \
# BEGIN Block XML-RPC\
<Files xmlrpc.php>\
Require all denied\
</Files>\
# END Block XML-RPC\
' ${WP_PATH}/.htaccess
```
**Excepcao:** Se o site usa JetPack ou aplicacao mobile WP, NAO desactivar XML-RPC. Verificar primeiro:
```bash
sudo -u ${WP_USER} wp plugin list --status=active --path=${WP_PATH} | grep -i "jetpack"
```
#### 2.2 Desactivar file editing (DISALLOW_FILE_EDIT)
```bash
# Backup wp-config.php
cp ${WP_PATH}/wp-config.php ${WP_PATH}/wp-config.php.bak.$(date +%Y%m%d)
# Adicionar se nao existir
grep -q "DISALLOW_FILE_EDIT" ${WP_PATH}/wp-config.php || \
sed -i "/\/\* That's all, stop editing/i define('DISALLOW_FILE_EDIT', true);" ${WP_PATH}/wp-config.php
```
#### 2.3 Corrigir permissoes
```bash
# Directorios: 755
find ${WP_PATH} -type d -exec chmod 755 {} \;
# Ficheiros
+218
View File
@@ -0,0 +1,218 @@
---
name: wp-performance
description: Auditoria e optimização de performance WordPress. Analisa velocidade, caching, base de dados e assets. Usa Lighthouse, métricas Core Web Vitals e checklist sistemático.
layer: wiki
---
# /wp-performance - Auditoria Performance WordPress
Checklist sistemático para auditar e optimizar performance de sites WordPress.
## Contexto NotebookLM
ANTES de executar, consultar notebooks para contexto especializado:
| Notebook | ID | Consultar quando |
|----------|-----|-----------------|
| WordPress e Elementor | 5be0d1a6 | Sempre |
```
mcp__notebooklm__notebook_query({
notebook_id: "5be0d1a6-00f2-4cd9-b835-978cb7721601",
query: "<adaptar ao contexto do pedido do utilizador>"
})
```
Integrar insights do NotebookLM nas recomendações e decisões.
### Procedimentos Relacionados
- `PROC-WordPress-Performance-Standard.md` — Standards de performance WP
---
## Uso
```
/wp-performance https://site.pt
/wp-performance site.pt
```
---
## Execução
### Passo 1: Lighthouse (Performance Geral)
```javascript
// Auditoria Lighthouse mobile + desktop
const [mobile, desktop] = await Promise.all([
mcp__lighthouse__get_performance_score({ url, device: "mobile" }),
mcp__lighthouse__get_performance_score({ url, device: "desktop" })
]);
const vitals = await mcp__lighthouse__get_core_web_vitals({ url });
```
### Passo 2: Verificações Servidor (SSH)
```javascript
// Se site está no servidor CWP (176.9.3.158)
const server_checks = [
// GZIP activo?
`curl -sI -H 'Accept-Encoding: gzip' ${url} | grep -i 'content-encoding'`,
// HTTP/2 activo?
`curl -sI --http2 ${url} | head -1`,
// Certificado SSL válido?
`echo | openssl s_client -connect ${domain}:443 2>/dev/null | openssl x509 -noout -dates`,
// PHP version
`php -v | head -1`
];
```
### Passo 3: Verificações WordPress (WP-CLI via SSH)
```javascript
// Se temos acesso SSH ao site
const wp_checks = [
// Cache plugin activo?
`sudo -u ${user} wp plugin list --status=active | grep -E 'cache|w3|wp-super|litespeed'`,
// Plugins activos (contar)
`sudo -u ${user} wp plugin list --status=active --format=count`,
// Tema activo
`sudo -u ${user} wp theme list --status=active --format=csv`,
// Tamanho da BD
`sudo -u ${user} wp db size --format=csv`,
// Autoload options (problema comum)
`sudo -u ${user} wp db query "SELECT SUM(LENGTH(option_value)) as autoload_size FROM wp_options WHERE autoload='yes'" --skip-column-names`,
// Revisões de posts (limpeza)
`sudo -u ${user} wp db query "SELECT COUNT(*) FROM wp_posts WHERE post_type='revision'" --skip-column-names`,
// Transients expirados
`sudo -u ${user} wp transient delete --expired --network 2>/dev/null || echo 'N/A'`
];
```
### Passo 4: Análise de Imagens
```javascript
// Verificar imagens não optimizadas
const image_checks = [
// Imagens grandes (>500KB)
`find ${wp_path}/wp-content/uploads -type f \\( -name "*.jpg" -o -name "*.png" \\) -size +500k | wc -l`,
// WebP disponível?
`find ${wp_path}/wp-content/uploads -name "*.webp" | head -5`,
// Total imagens
`find ${wp_path}/wp-content/uploads -type f \\( -name "*.jpg" -o -name "*.png" -o -name "*.webp" \\) | wc -l`
];
```
---
## Output Template
```markdown
# WP Performance Audit - [site.pt]
**Data:** YYYY-MM-DD
**URL:** https://site.pt
---
## Resumo Executivo
| Métrica | Mobile | Desktop | Status |
|---------|--------|---------|--------|
| Performance | XX | XX | ✅/⚠️/❌ |
| LCP | X.Xs | X.Xs | ✅/⚠️/❌ |
| CLS | X.XX | X.XX | ✅/⚠️/❌ |
| TBT | XXXms | XXXms | ✅/⚠️/❌ |
---
## Checklist Servidor
| Item | Status | Nota |
|------|--------|------|
| GZIP | ✅/❌ | |
| HTTP/2 | ✅/❌ | |
| SSL | ✅/❌ | Expira: DD/MM |
| PHP Version | X.X | |
---
## Checklist WordPress
| Item | Status | Valor |
|------|--------|-------|
| Cache Plugin | ✅/❌ | [nome] |
| Plugins Activos | ⚠️ | XX (recomendado <20) |
| Tamanho BD | ⚠️ | XX MB |
| Autoload Size | ⚠️ | XX KB (recomendado <1MB) |
| Revisões | ⚠️ | XX (limpar se >100) |
---
## Checklist Imagens
| Item | Valor | Acção |
|------|-------|-------|
| Imagens >500KB | XX | Optimizar |
| WebP activo | ✅/❌ | Converter |
| Total imagens | XX | - |
---
## Recomendações Prioritárias
1. **[CRÍTICO]** Descrição...
2. **[ALTO]** Descrição...
3. **[MÉDIO]** Descrição...
---
## Comandos de Correcção
```bash
# Limpar revisões (manter últimas 5)
wp post delete $(wp post list --post_type='revision' --format=ids) --force
# Limpar transients
wp transient delete --all
# Optimizar BD
wp db optimize
```
---
*Gerado via /wp-performance - YYYY-MM-DD*
```
---
## Thresholds
| Métrica | ✅ Bom | ⚠️ Médio | ❌ Mau |
|---------|--------|----------|--------|
| Performance Score | >70 | 50-70 | <50 |
| LCP | <2.5s | 2.5-4s | >4s |
| CLS | <0.1 | 0.1-0.25 | >0.25 |
| TBT | <200ms | 200-600ms | >600ms |
| Plugins | <20 | 20-30 | >30 |
| BD Size | <100MB | 100-500MB | >500MB |
| Autoload | <1MB | 1-2MB | >2MB |
| Revisões | <100 | 100-500 | >500 |
---
## Sites WordPres
+157
View File
@@ -0,0 +1,157 @@
---
name: wp-translate
description: Tradução de plugins WordPress para PT-PT com DeepL Pro, glossário próprio e scripts de automação. Gere ficheiros .po/.mo/.json, corrige traduções PT-BR e faz deploy para starter.descomplicar.pt.
layer: wiki
---
# /wp-translate — Traduções WordPress PT-PT
## Visão Geral
Skill para traduzir plugins WordPress para Português Europeu (PT-PT) usando DeepL Pro com glossário, scripts de automação próprios e deploy directo para starter.descomplicar.pt.
---
## Scripts Disponíveis
**Localização local:** `/media/ealmeida/Dados/Dev/Scripts/translate-wp-plugin/`
**No servidor (tmp):** `/tmp/translate_missing.py`, `/tmp/fix_ptbr_safe.py`
| Script | Função |
|--------|--------|
| `translate_missing.py` | Traduz strings vazias via DeepL Pro + glossário. Suporta batch 50 strings/pedido |
| `fix_ptbr_safe.py` | Corrige ~100 padrões PT-BR → PT-PT |
| `fix_malformed.py` | Corrige sintaxe .po mal formada |
| `batch_process_library.py` | Orquestra fix_malformed → fix_ptbr → translate → compile .mo para toda a biblioteca |
| `setup_glossary.py` | Cria/actualiza glossário DeepL (175 entradas WP PT-PT) |
**Configuração (.env):**
```
DEEPL_API_KEY=3c74e27a-f688-4de2-927b-e91da46cf9cd
DEEPL_GLOSSARY_ID=b9db5639-c201-479f-87af-adb81f112067
```
---
## DeepL Pro
- **Endpoint:** `api.deepl.com` (Pro — sem `:fx`)
- **Quota:** ~1,6M / 1 trilião de chars
- **Glossário:** 160 entradas brand names + termos WordPress (EN→PT)
- **Atenção:** DeepL também gera padrões PT-BR — sempre correr `fix_ptbr` ANTES E DEPOIS da tradução
---
## Workflow: Plugin Individual
```bash
# 1. No servidor — gerar .pot (se não existir)
wp --path=/home/ealmeida/starter.descomplicar.pt i18n make-pot \
wp-content/plugins/PLUGIN/ \
/tmp/PLUGIN-pt_PT.po \
--domain=TEXT_DOMAIN --allow-root
# 2. Traduzir com DeepL Pro
python3 /tmp/translate_missing.py /tmp/PLUGIN-pt_PT.po
# 3. Corrigir PT-BR pós-tradução
python3 /tmp/fix_ptbr_safe.py /tmp/PLUGIN-pt_PT.po
# 4. Para plugins React — gerar JSON para strings JS
wp --path=/home/ealmeida/starter.descomplicar.pt i18n make-json \
/tmp/PLUGIN-pt_PT.po /path/to/languages/ --no-purge --allow-root
# 5. Copiar para servidor
cp /tmp/PLUGIN-pt_PT.{po,mo} /home/ealmeida/starter.descomplicar.pt/wp-content/languages/plugins/
chown ealmeida:ealmeida /home/ealmeida/starter.descomplicar.pt/wp-content/languages/plugins/PLUGIN-pt_PT.*
```
## Workflow: Biblioteca Completa
```bash
python3 /media/ealmeida/Dados/Dev/Scripts/translate-wp-plugin/batch_process_library.py \
/path/to/library
# Opções: --skip-translate, --dry-run, --only-ptbr
```
---
## Encontrar o Text Domain
**Crítico:** O nome da pasta do plugin ≠ text domain. Sempre verificar:
```bash
grep "Text Domain" /path/to/plugin/plugin-main.php
```
**Casos conhecidos de divergência:**
| Pasta | Text Domain | Ficheiro .po |
|-------|-------------|--------------|
| `pro-elements` | `elementor-pro` | `elementor-pro-pt_PT.po` |
| `branda-white-labeling` | `ub` | `ub-pt_PT.po` |
| `seo-by-rank-math` | `rank-math` | `rank-math-pt_PT.po` |
---
## Plugins React/JS — Ficheiros JSON
Plugins com `wp_set_script_translations()` precisam de um `.json` além do `.mo`.
**Detectar:**
```bash
grep -r "wp_set_script_translations" /path/plugin/ --include="*.php"
```
**Gerar JSON:**
```bash
wp i18n make-json PLUGIN-pt_PT.po /path/languages/ --no-purge --allow-root
```
**Atenção ao MD5:** WordPress calcula `md5(relative_path_of_script)` para o nome do JSON.
Se o `source` no JSON não bater com o script registado, copiar o JSON com o MD5 correcto:
```bash
# Encontrar qual script está registado
grep "wp_register_script\|wp_enqueue_script" plugin.php | grep "HANDLE"
# Calcular MD5 do caminho relativo
python3 -c "import hashlib; print(hashlib.md5('app/index.js'.encode()).hexdigest())"
# Copiar com nome correcto
cp PLUGIN-pt_PT-{md5_errado}.json PLUGIN-pt_PT-{md5_correcto}.json
```
**Exemplo real — ai-engine:**
- Handle `mwai` → script `app/index.js` → MD5: `2018087f584c4398b5c3a23fc0e5f9db`
- JSON gerado com source `app/i18n.js` → MD5: `0bdc92e05d8f3ab638aa855679db059e`
- Solução: copiar JSON com o MD5 de `app/index.js`
---
## Padrões PT-BR Comuns (fix_ptbr)
DeepL introduz PT-BR mesmo com `target_lang=PT-PT`. Correr fix_ptbr **depois** da tradução:
| PT-BR | PT-PT |
|-------|-------|
| atualiz* | actualiz* |
| ativar/desativar | activar/desactivar |
| você/vocês | o utilizador/os utilizadores |
| excluir/excluído | eliminar/eliminado |
| acessar | aceder |
| compartilhar | partilhar |
| postagem/postagens | publicação/publicações |
| ativo/inativo | activo/inactivo |
| ativação/desativação | activação/desactivação |
| digite | introduza |
| clique | clique (OK) |
---
## Servidor starter.descomplicar.pt
```
SSH: mcp__ssh-unified__ssh_execute server:"server"
Path traduções: /home/ealmeida/starter.descomplicar.pt/wp-content/languages/plugins/
Loco Translate: /home/ealmeida/starter.descomplicar.pt/wp-content/languages/loco/plugins/
WP-CLI: wp --path=/hom
+153
View File
@@ -0,0 +1,153 @@
---
name: wp-update
description: Actualização segura de WordPress core, plugins e temas em todos os sites CWP. Inclui backups pré-actualização, verificação pós-actualização e relatório de estado.
disable-model-invocation: true
layer: wiki
---
# /wp-update - Actualização WordPress
Actualização e verificação de todos os sites WordPress no servidor CWP (176.9.3.158).
---
## SITES ACTIVOS (16)
| Conta | Site | Notas |
|-------|------|-------|
| carstuff | carstuff.pt | |
| ealmeida | descomplicar.pt | principal |
| ealmeida | emanuelalmeida.pt | |
| ealmeida | ecommerce.descomplicar.pt | |
| ealmeida | ecommerce-demo.descomplicar.pt | |
| ealmeida | starter.descomplicar.pt | |
| ealmeida | care.descomplicar.pt | KiviCare |
| ealmeida | e-commerce.descomplicar.pt | |
| espiral | espiralsenior.pt | booking plugin desactivado |
| espiral | dev.espiralsenior.pt | |
| familycl | familyclinic.pt | KiviCare |
| ignition | ignitionvortex.pt | |
| karate | karateclubedegaia.com | |
| sintri | sintricare.com.pt | |
| solarfv | solarfvengenharia.com | |
| wtc | watercontrol.pt | |
**Domínios expirados (excluídos):** jornadadoheroi.pt, tecoworking.pt, socialboost.pt
---
## SCRIPT DE VERIFICAÇÃO
**Local:** `/media/ealmeida/Dados/Dev/ClaudeDev/Claude-Scripts/wp-update/check-wp-sites.sh`
**No servidor:** `/root/scripts/check-wp-sites.sh`
O script verifica:
- Código HTTP (200, 301, 403, 500, etc.)
- **Conteúdo da página** por erros WordPress específicos:
- "Ocorreu um erro crítico neste site"
- "There has been a critical error"
- "Fatal error:.*in /home/"
- "Error establishing a database connection"
> **IMPORTANTE:** Códigos HTTP 200 NÃO garantem que o site está OK.
> WordPress pode devolver 200 com página de erro crítico.
---
## WORKFLOW
### 1. Verificação Inicial (OBRIGATÓRIO)
Executar script de verificação no servidor:
```bash
/root/scripts/check-wp-sites.sh carstuff.pt descomplicar.pt emanuelalmeida.pt ecommerce.descomplicar.pt ecommerce-demo.descomplicar.pt starter.descomplicar.pt care.descomplicar.pt e-commerce.descomplicar.pt espiralsenior.pt dev.espiralsenior.pt familyclinic.pt ignitionvortex.pt karateclubedegaia.com sintricare.com.pt solarfvengenharia.com watercontrol.pt
```
**REGRA CRÍTICA:** Se site reportar ❌ → investigar e corrigir ANTES de actualizar.
### 2. Actualização por Site
Para cada site funcional:
```bash
# Actualizar plugins
wp plugin update --all --allow-root --path=/home/USER/PATH
# Actualizar temas
wp theme update --all --allow-root --path=/home/USER/PATH
# Actualizar core
wp core update --allow-root --path=/home/USER/PATH
# Corrigir permissões (OBRIGATÓRIO)
chown -R USER:USER /home/USER/PATH/wp-content/
```
### 3. Verificação Final (OBRIGATÓRIO)
Re-executar script de verificação para confirmar que todos os sites continuam funcionais.
### 4. Relatório Desk CRM (OBRIGATÓRIO)
Após concluir as actualizações e verificação final, publicar relatório completo como comentário na **Discussão #52** ("Logs de Atualização de Websites") do **Projecto #69**.
**Tool:** `mcp__desk-crm-v3__add_discussion_comment`
- `discussion_id`: 52
- `staff_id`: 25 (AikTop)
- `content`: HTML formatado (template abaixo)
**Template HTML do Relatório:**
```html
<h4>🔄 Relatório WordPress Update - YYYY-MM-DD</h4>
<h5>Verificação Inicial</h5>
<ul>
<li>✅ X/16 sites OK</li>
<li>❌ Y sites com problemas: [lista se houver]</li>
</ul>
<h5>Actualizações Aplicadas</h5>
<table border="1" cellpadding="5" cellspacing="0" style="border-collapse:collapse;width:100%">
<tr style="background:#f0f0f0"><th>Site</th><th>Plugins</th><th>Temas</th><th>Core</th><th>Notas</th></tr>
<tr><td>site.pt</td><td>✅ 3/3</td><td>✅ 1/1</td><td>—</td><td></td></tr>
<!-- uma linha por site com alterações -->
</table>
<p><strong>Sites sem alterações:</strong> [lista de sites já actualizados]</p>
<h5>Falhas (Licenças Premium)</h5>
<table border="1" cellpadding="5" cellspacing="0" style="border-collapse:collapse;width:100%">
<tr style="background:#fff3cd"><th>Plugin</th><th>Sites Afectados</th><th>Erro</th></tr>
<tr><td>plugin-name</td><td>site1, site2</td><td>Unauthorized</td></tr>
</table>
<h5>Verificação Final</h5>
<ul>
<li>✅ X/16 sites OK após actualizações</li>
</ul>
<h5>Alertas Wordfence</h5>
<ul>
<li>[Listar alertas Wordfence recentes da conta it@descomplicar.pt se existirem]</li>
<li>Sem alertas (se nenhum)</li>
</ul>
<hr>
<p><em>Relatório automático gerado por /wp-update v1.2.0 | AikTop</em></p>
```
**Regras do Relatório:**
- Verificar data actual com `mcp__mcp-time__current_time` antes de gerar
- Incluir TODOS os sites (16), mesmo os que não tinham updates
- Separar claramente sucessos de falhas
- Listar alertas Wordfence da pasta `INBOX.Wordpress.Alertas Wordfence` da conta `it` (últimos 7 dias)
- Se a verificação final detectar problemas, destacar em **vermelho**
- Omitir a tabela de Falhas se não houver nenhuma
---
## PATHS DOS SITES
| Site | Path |
|------|------|
| carstuff.pt | /home/carstuff/public_html |
| descompl