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>
227 lines
7.4 KiB
PHP
227 lines
7.4 KiB
PHP
<?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();
|