feat: sync all plugins, skills, agents updates
New plugins: core-tools New skills: auto-expense, ticket-triage, design, security-check, aiktop-tasks, daily-digest, imap-triage, index-update, mindmap, notebooklm, proc-creator, tasks-overview, validate-component, perfex-module, report, calendar-manager New agents: design-critic, design-generator, design-lead, design-prompt-architect, design-researcher, compliance-auditor, metabase-analyst, gitea-integration-specialist Updated: all plugin configs, knowledge datasets, existing skills Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
295
wordpress/skills/jetengine/SKILL.md
Normal file
295
wordpress/skills/jetengine/SKILL.md
Normal file
@@ -0,0 +1,295 @@
|
||||
---
|
||||
name: jetengine
|
||||
description: >
|
||||
JetEngine development: Custom Post Types (CPT), Custom Content Types (CCT), Meta Boxes, Taxonomies,
|
||||
Relations, Query Builder, Listing Grid, Listing Map, Dynamic Content, Dynamic Tags, Macros, Profile Builder,
|
||||
REST API, and headless/programmatic automation for AI agents and n8n integration.
|
||||
This skill should be used when the user asks about "jetengine", "jet engine", "custom post type jetengine",
|
||||
"meta box jetengine", "relations jetengine", "query builder jetengine", "listing grid", "listing map",
|
||||
"jetengine dynamic content", "jetengine macros", "profile builder jetengine", "cct jetengine",
|
||||
"custom content type", "jetengine cct", "jetengine rest api", "jetengine n8n", "jetengine sql",
|
||||
"jetengine automação", "jetengine headless", "wp_jet_rel_default", "wp_jet_cct", "jetengine relations",
|
||||
"conteúdo dinâmico elementor jetengine", "jetengine filtros", "jetengine repeater".
|
||||
author: Descomplicar® Crescimento Digital
|
||||
version: 1.0.0
|
||||
user_invocable: true
|
||||
tags: [wordpress, elementor, crocoblock, jetengine, cpt, cct, listings, dynamic-content]
|
||||
allowed-tools: Read, Write, Edit, Bash, mcp__ssh-unified__ssh_execute, mcp__notebooklm__notebook_query, mcp__dify-kb__dify_kb_retrieve_segments
|
||||
category: dev
|
||||
quality_score: 85
|
||||
updated: "2026-02-18T00:00:00Z"
|
||||
---
|
||||
|
||||
# /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% # ID do post actual
|
||||
%queried_object_id% # ID do objecto em arquivo/single
|
||||
%request_field|name% # Parâmetro URL (?name=valor)
|
||||
%current_meta|field_name% # Meta field do post actual
|
||||
%user_meta|field_name% # Meta field do utilizador logado
|
||||
```
|
||||
|
||||
### Usar Macros em Widgets Elementor
|
||||
|
||||
```
|
||||
Textos: escrever macro directamente no campo de texto
|
||||
Links: usar em URL field
|
||||
Queries: usar em Meta Query → Valor = macro
|
||||
```
|
||||
|
||||
### Profile Builder
|
||||
|
||||
```
|
||||
JetEngine > Profile Builder > Add New
|
||||
├── Criar páginas de conta (Dashboard, Submissões, etc.)
|
||||
├── Cada página = Elementor Template
|
||||
├── Acesso: utilizadores logados, por role, por condição
|
||||
└── Menu de Profile: widget JetEngine Profile Menu
|
||||
```
|
||||
|
||||
## Dify KB — Consultar Antes de Implementar
|
||||
|
||||
```javascript
|
||||
// Dataset Crocoblock (JetEngine incluído)
|
||||
mcp__dify-kb__dify_kb_retrieve_segments({
|
||||
dataset_id: "bdf85c26-1824-4021-92d1-be20501b35ac",
|
||||
query: "[funcionalidade JetEngine]"
|
||||
})
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Problema | Causa | Solução |
|
||||
|----------|-------|---------|
|
||||
| Listing não mostra resultados | Query sem posts ou condição errada | Debug Query: activar `WP_DEBUG` e verificar |
|
||||
| Dynamic Tag retorna vazio | Campo meta não preenchido ou nome errado | Verificar key do meta field em JetEngine > Meta Boxes |
|
||||
| Relações não aparecem | rel_id errado ou sem items relacionados | Verificar nome da relação em JetEngine > Relations |
|
||||
| CCT não aparece no REST | REST API não activada | JetEngine > Content Types > Edit > Enable REST API |
|
||||
| Filtros não funcionam | Listing ID errado no filtro | JetEngine Listing Grid > copiar ID do widget |
|
||||
|
||||
## Bridge Plugin — Deploy para Integração n8n
|
||||
|
||||
Para integração headless com n8n ou agentes IA, fazer deploy do MU plugin incluído:
|
||||
|
||||
```bash
|
||||
PHP="/opt/alt/php-fpm83/usr/bin/php"
|
||||
WP="$PHP /usr/local/bin/wp --allow-root --path=/home/USER/public_html"
|
||||
|
||||
# Copiar bridge plugin para mu-plugins (via SSH)
|
||||
# O ficheiro está em: assets/descomplicar-jet-bridge.php (nesta skill)
|
||||
scp descomplicar-jet-bridge.php server.descomplicar.pt:/home/USER/public_html/wp-content/mu-plugins/
|
||||
|
||||
# Verificar que está activo (MU plugins carregam automaticamente)
|
||||
$WP plugin list --skip-plugins --skip-themes # MU plugins não aparecem aqui — normal
|
||||
```
|
||||
|
||||
**Endpoint exposto:** `POST /wp-json/descomplicar/v1/manage`
|
||||
|
||||
**Acções disponíveis:**
|
||||
| Acção | Descrição |
|
||||
|-------|-----------|
|
||||
| `create_cct_item` | Criar item num CCT |
|
||||
| `update_cct_item` | Actualizar item (`_ID` em item_data) |
|
||||
| `get_cct_item` | Ler item por ID |
|
||||
| `delete_cct_item` | Eliminar item |
|
||||
| `manage_relation` | Criar/remover relação (connect: true/false) |
|
||||
|
||||
```bash
|
||||
# Testar endpoint (exemplo: criar fatura)
|
||||
curl -X POST https://site.pt/wp-json/descomplicar/v1/manage \
|
||||
-H "Authorization: Basic $(echo -n 'user:app-password' | base64)" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"action": "create_cct_item",
|
||||
"cct_slug": "faturas",
|
||||
"item_data": { "titulo": "Fatura #001", "valor": 500 }
|
||||
}'
|
||||
```
|
||||
|
||||
## JetEngine MCP Server (v3.8.0+)
|
||||
|
||||
A partir da versão 3.8.0, o JetEngine tem **MCP Server nativo**:
|
||||
|
||||
```
|
||||
WP Admin > JetEngine > AI Command Center > Enable MCP Server
|
||||
→ Agentes IA podem inspecionar e criar CPTs/CCTs/Relations via linguagem natural
|
||||
→ Claude consegue ler o schema completo e instanciar estruturas directamente
|
||||
```
|
||||
|
||||
## Referências Adicionais
|
||||
|
||||
- **`assets/descomplicar-jet-bridge.php`** — MU Plugin pronto a deployar: CRUD CCT + Relations via REST API
|
||||
- **`references/automation.md`** — BD completa (CCT, Relations meta, Custom Meta Storage), WP-CLI batch, PHP hooks, REST API avançada (store_items_type, filtros CCT), cache e guardrails
|
||||
- **`references/query-listings.md`** — Query Builder avançado, Listing Map, paginação, ordenação dinâmica
|
||||
- **`references/dynamic-content.md`** — Dynamic Tags, Profile Builder, Macros, Conditional Fields
|
||||
|
||||
---
|
||||
|
||||
**Versão**: 1.1.0 | **Autor**: Descomplicar® | **Data**: 18-02-2026
|
||||
226
wordpress/skills/jetengine/assets/descomplicar-jet-bridge.php
Normal file
226
wordpress/skills/jetengine/assets/descomplicar-jet-bridge.php
Normal file
@@ -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();
|
||||
668
wordpress/skills/jetengine/references/automation.md
Normal file
668
wordpress/skills/jetengine/references/automation.md
Normal file
@@ -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*
|
||||
271
wordpress/skills/jetengine/references/dynamic-content.md
Normal file
271
wordpress/skills/jetengine/references/dynamic-content.md
Normal file
@@ -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*
|
||||
225
wordpress/skills/jetengine/references/query-listings.md
Normal file
225
wordpress/skills/jetengine/references/query-listings.md
Normal file
@@ -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*
|
||||
Reference in New Issue
Block a user