11 KiB
Hooks & Server-Side API
Hooks
Hooks son archivos PHP que ejecutan lógica server-side. Pueden existir en dos sitios:
- Hooks globales en
hooks/hooks.<hook-id>.php - Hooks propios de módulo en
template/estandar/modulos/<module-id>/hook.php
Tipos de hooks
1. Hook global
- Archivo:
hooks/hooks.<hook-id>.php - Endpoint:
/hooks/<hook-id>/ - Úsalo cuando la lógica se reutiliza entre módulos, páginas o formularios
2. Hook propio de módulo
- Archivo:
template/estandar/modulos/<module-id>/hook.php - Endpoint:
/hooks/<module-id>/ - Úsalo cuando la lógica pertenece solo a ese módulo
Ejemplos:
hooks/hooks.buscar_barcos.php->/hooks/buscar_barcos/template/estandar/modulos/hero_banner/hook.php->/hooks/hero_banner/
Regla práctica:
- Si el hook solo sirve a un módulo, créalo dentro del módulo
- Si varias piezas del proyecto lo van a consumir, créalo como hook global
Estructura de un Hook
<?php
// Los parámetros se reciben como variables directamente
// Ejemplo: Si llamas hook con {param1: 100}, tendrás $param1 = 100
$resultado = $param1 * 2;
// Retornar un array (se convierte a JSON)
return [
"success" => true,
"message" => "Valor procesado: " . $resultado,
"value" => $resultado
];
?>
Testing Hooks
El Docker debe estar corriendo. Hacer curl al endpoint del hook:
curl http://localhost:8080/hooks/example_hook/
No usar X-Hooks-Token en desarrollo local.
Cómo Llamar Hooks
La referencia siempre se hace con el endpoint /hooks/<id>/:
- Para hooks globales,
<id>es el nombre lógico del hook sinhooks.ni.php - Para hooks de módulo,
<id>es elmodule-idde la carpeta del módulo
Desde HTML (recomendado para módulos):
<hook result="myVar" endpoint="/hooks/module_id/" :param1="value1" :param2="'string'"></hook>
<p>{{ myVar.message }}</p>
Desde Twig:
{% set resultado = 'hooks/mimodulo/' | hook({param1: 100, param2: 'texto'}) %}
<p>{{ resultado.message }}</p>
Desde JavaScript:
CmsApi.hook('/hooks/mimodulo/', {param1: 100, param2: 'texto'}, (data) => {
console.log(data.message);
});
Desde otro Hook PHP:
<?php
$result = hook("/hooks/mimodulo/", ["param1" => 100, "param2" => "texto"]);
$mensaje = $result["message"];
?>
Desde c-form: Los hooks se ejecutan automáticamente al enviar el formulario si están configurados.
CmsApi (PHP)
API server-side para operaciones de base de datos. Disponible en todos los hooks.
Read — CmsApi::get()
// Todos los registros
$products = CmsApi::get('productos');
// Con condición WHERE
$active = CmsApi::get('productos', ['active' => 1]);
// Con orden y límite
$latest = CmsApi::get('noticias', [], 'fecha DESC', 5);
// Con condición string
$activos = CmsApi::get('productos', 'activo=1');
// Condición compleja como array
$caros = CmsApi::get('productos', [
["column" => "precio", "operator" => ">", "value" => 100]
]);
// Múltiples condiciones (AND)
$resultados = CmsApi::get('productos', [
["column" => "activo", "operator" => "=", "value" => 1],
["column" => "stock", "operator" => ">", "value" => 0]
]);
// Con operadores
$expensive = CmsApi::get('productos', ['precio' => ['>=' => 100]]);
$search = CmsApi::get('productos', ['nombre' => ['LIKE' => '%keyword%']]);
$inList = CmsApi::get('productos', ['categoria_num' => ['IN' => [1, 2, 3]]]);
// Con opciones
$datos = CmsApi::get('productos', '', '', '', [
'translates' => true,
'uploads' => true,
'relations' => true,
'relationsDepth' => 2
]);
Insert — CmsApi::insert()
// Un registro
CmsApi::insert('contacto', [
["nombre" => "John", "email" => "john@example.com", "mensaje" => "Hello"]
]);
// Múltiples registros
CmsApi::insert('productos', [
["nombre" => "Producto A", "precio" => 100],
["nombre" => "Producto B", "precio" => 200]
]);
// Con retorno del último ID
CmsApi::insert('productos',
[["nombre" => "Nuevo", "precio" => 150]],
[],
['return_last_id' => true]
);
Update — CmsApi::update()
// Con condición string
CmsApi::update('productos', ["precio" => 150], "num=1");
// Con condición array
CmsApi::update('productos',
["activo" => 1],
[["column" => "num", "operator" => "=", "value" => 1]]
);
// Múltiples registros
CmsApi::update('productos', ["activo" => 0], "precio < 50");
Delete — CmsApi::delete()
CmsApi::delete('productos', "num=5");
CmsApi::delete('productos',
[["column" => "activo", "operator" => "=", "value" => 0]]
);
Reglas importantes
- Nombres de tabla sin prefijo
cms_ - Primary key siempre es
num, nuncaid - Foreign keys:
categoria_num, nocategoria_id - Upload fields: no se manejan via insert/update
- Operadores:
=,!=,>,>=,<,<=,LIKE,IN
CmsApi (JavaScript — Client-Side)
// Llamar hook
CmsApi.hook('/hooks/module_id/', { param: 'value' }, function(response) {
// response es la salida del hook
});
// Leer registros (si está expuesto via hooks)
CmsApi.get('tableName', { where: conditions }, function(records) {
// records array
});
CocoDB
Capa de abstracción de BD de bajo nivel usada internamente por CmsApi. Usar directamente desde hooks cuando necesites más control.
CocoDB::get($table, $where, $order, $limit, $options)
// Básico
$records = CocoDB::get('productos', ['activo' => 1], 'orden ASC', 10);
// Where con operadores avanzados
$records = CocoDB::get('productos', [
['column' => 'precio', 'value' => 100, 'operator' => '>='],
['column' => 'categoria_num', 'value' => [1, 2, 3], 'operator' => 'IN'],
]);
// Condiciones OR
$records = CocoDB::get('productos', [
['column' => 'nombre', 'value' => '%keyword%', 'operator' => 'LIKE'],
['column' => 'descripcion', 'value' => '%keyword%', 'operator' => 'LIKE', 'or' => true],
]);
// NOT
$records = CocoDB::get('productos', [
['column' => 'estado', 'value' => 'borrador', 'operator' => '=', 'not' => true],
]);
// IS NULL
$records = CocoDB::get('productos', [
['column' => 'fecha_baja', 'value' => '', 'operator' => 'IS NULL'],
]);
// Limit con offset
$records = CocoDB::get('productos', [], 'num DESC', ['limit' => 10, 'offset' => 20]);
Opciones de get()
| Option | Type | Default | Description |
|---|---|---|---|
uploads |
bool | true |
Incluir datos de upload fields |
relations |
bool/array | true |
Resolver foreign keys. Array para limitar: ['category'] |
relationsDepth |
int | 2 | Profundidad de relaciones anidadas |
translates |
string | current lang | Código de idioma |
groupBy |
string | null | GROUP BY clause |
aggregates |
array | [] |
Funciones de agregación |
onlyFields |
array | null | Seleccionar solo campos específicos |
debug |
bool | false | Mostrar SQL query |
redis |
bool | null | Forzar cache Redis |
redis_expire |
int | 60 | TTL de cache Redis (segundos) |
CocoDB::insertRecords($table, $records, $functions, $options)
// Un registro
$count = CocoDB::insertRecords('contacto', [
'nombre' => 'John',
'email' => 'john@example.com',
]);
// Usar mysql_insert_id() para obtener el nuevo num
// Múltiples
$count = CocoDB::insertRecords('productos', [
['nombre' => 'Product A', 'precio' => 10],
['nombre' => 'Product B', 'precio' => 20],
]);
Opciones de insert/update
| Option | Description |
|---|---|
forceNum |
Permite setear el campo num manualmente |
ignoreSchema |
Saltar validación de schema |
ignoreFields |
Array de campos a ignorar |
CocoDB::updateRecords($table, $records, $where, $functions, $options)
CocoDB::updateRecords('productos',
['precio' => 29.99, 'activo' => 1],
['num' => 42]
);
// Con operador en where
CocoDB::updateRecords('productos',
['activo' => 0],
[['column' => 'stock', 'value' => 0, 'operator' => '<=']]
);
CocoDB::deleteRecords($table, $where, $options)
CocoDB::deleteRecords('productos', ['num' => 42]);
CocoDB::deleteRecords('logs', [
['column' => 'fecha', 'value' => '2024-01-01', 'operator' => '<']
]);
Parámetro $functions
Permite aplicar funciones MySQL a valores durante insert/update:
CocoDB::insertRecords('logs', [
'mensaje' => 'Login exitoso',
'fecha' => '',
], [
'fecha' => 'NOW()',
]);
Where Clause — Formatos
Simple (key-value):
['campo' => 'valor'] // campo = 'valor'
Avanzado (array de condiciones):
[
'column' => 'field_name',
'value' => 'match_value',
'operator' => '=', // =, !=, <, >, <=, >=, LIKE, IN, IS NULL
'or' => false, // OR en vez de AND
'not' => false, // Negar la condición
'raw_key' => false, // Saltar check de existencia de columna
]
Creación y Actualización de Registros
Flujo correcto
- Consultar el esquema de la tabla (leer
cms/data/schema/{tabla}.ini.php) - Revisar los tipos de campo
- Rellenar según el tipo de dato
- Enviar con la estructura correcta
Tipos de campo y formato
| Tipo | Formato | Ejemplo |
|---|---|---|
| Text field | String | "Texto" |
| Text box | String multilínea | "Línea 1\nLínea 2" |
| Date/time | YYYY-MM-DD HH:mm:ss |
"2025-12-03 10:30:00" |
| Wysiwyg | String HTML | "<p class=\"font-bold\">Texto</p>" |
| List | String o número | "activo" o "1" (num si es foreign key) |
| Checkbox | Número 1/0 | 1 o 0 |
| Multivalores | String JSON | "[{\"producto\":\"1\"}]" |
| Upload | NO enviar — usar upload_record_image después de crear el registro |
Table Schemas
Los schemas están en cms/data/schema/ como archivos .ini.php. Definen:
- Nombres y tipos de campo
- Reglas de validación
- Relaciones (foreign keys)
- Configuración de display
Ejemplos Prácticos
Hook de Cálculo de Precio
<?php
// hook.php del módulo "calcular_precio"
$precioUnitario = 50;
if ($tipo === 'mayoreo' && $cantidad > 10) {
$precioUnitario *= 0.85; // 15% descuento
}
return [
"success" => true,
"precioUnitario" => round($precioUnitario, 2),
"total" => round($precioUnitario * $cantidad, 2),
"descuento" => $tipo === 'mayoreo' ? 15 : 0
];
?>
<hook result="precio" endpoint="/hooks/calcular_precio/" :cantidad="10" :tipo="'mayoreo'"></hook>
<p>Total: ${{ precio.total }}</p>
En este ejemplo, el endpoint usa calcular_precio porque el archivo vive en:
template/estandar/modulos/calcular_precio/hook.php
Hook con Operaciones de BD
<?php
// hook.php del módulo "procesar_compra"
$producto = CmsApi::get('productos', "num=$producto_id");
if (empty($producto)) {
return ["success" => false, "message" => "Producto no encontrado"];
}
$total = $producto[0]['precio'] * $cantidad;
// Crear venta
CmsApi::insert('ventas', [[
"usuario_num" => $usuario_id,
"producto_num" => $producto_id,
"cantidad" => $cantidad,
"total" => $total,
"fecha" => date('Y-m-d H:i:s')
]], [], ['return_last_id' => true]);
// Actualizar stock
$stock = CmsApi::get('stocks', "producto_num=$producto_id");
if (!empty($stock)) {
CmsApi::update('stocks',
["cantidad" => $stock[0]['cantidad'] - $cantidad],
"producto_num=$producto_id"
);
}
return ["success" => true, "total" => $total];
?>