docs: corrige API de hooks (CocoDB::getInstance no existe) y documenta modelo de ejecución, escape() y aviso de fiabilidad de write-ops del MCP
gitleaks / gitleaks (push) Canceled after 0s
gitleaks / gitleaks (push) Canceled after 0s
Hallazgos de fricción real en producción (turismo, onefix-appv1, pisorentado.com): - CocoDB::getInstance() no existe (fatal), sustituido por DB::getInstance() como última opción - El enrutado HTTP directo de /hooks/<id>/ da 404 en Forge; documentado el patrón vía filtro | hook - Modelo de ejecución eval(): __DIR__ vs APP_ROOT_PATH, params por HTTP vs filtro Twig - DB::escape() ya entrecomilla el literal; patrón seguro documentado - Nunca delegar hashing de contraseñas en el CMS (create_field no tiene tipo password; SHA1 inadecuado) - Aviso de que success:true en write-ops/DDL del MCP no garantiza que la operación se aplicó
This commit is contained in:
+44
-1
@@ -33,7 +33,9 @@ Regla práctica:
|
||||
- No uses `exit`
|
||||
- Para leer parámetros, usa `$_REQUEST[...]` o las variables ya inyectadas por el sistema
|
||||
- En hooks, usa `CmsApi::get()` o `CocoDB::get()` como primera opción
|
||||
- No uses `CocoDB::getInstance()` salvo necesidad real muy excepcional
|
||||
- **`CocoDB::getInstance()` no existe** — llamarlo produce un fatal `Call to undefined method`. Si de
|
||||
verdad necesitas BD cruda (fuera de `CmsApi`/`CocoDB`), usa `DB::getInstance()`
|
||||
(`execute`/`executeS`/`fetch`/`escape`/`foundRows`), y solo como última opción
|
||||
- No escribas SQL manual con `prepare()/bind_param()` salvo que no exista forma razonable de resolverlo con `CmsApi` o `CocoDB`
|
||||
|
||||
### Estructura de un Hook
|
||||
@@ -107,6 +109,47 @@ $mensaje = $result["message"];
|
||||
|
||||
**Desde c-form:** Los hooks se ejecutan automáticamente al enviar el formulario si están configurados.
|
||||
|
||||
### Diagnóstico: el enrutado HTTP directo de hooks no funciona en Forge
|
||||
|
||||
`/hooks/<id>/` da **404 incluso para hooks válidos** cuando se pide por HTTP directo en un entorno
|
||||
Forge — el único camino fiable es el filtro Twig `| hook` (o `<hook>`/`CmsApi.hook()`, que pasan por el
|
||||
mismo motor). Si un hook "no responde" y no ves error claro, diagnostica así:
|
||||
|
||||
1. Crea un módulo/página temporal que solo llame al hook con `| hook` y vuelque el resultado:
|
||||
```twig
|
||||
{% set d = 'hooks/mihook/' | hook({param1: 100}) %}
|
||||
<pre>{{ d|json_encode(1) }}</pre>
|
||||
```
|
||||
2. Si eso funciona pero la llamada por HTTP/JS falla, el problema está en cómo se invoca, no en el hook.
|
||||
|
||||
### Modelo de ejecución de los hooks (importante)
|
||||
|
||||
Los hooks corren vía `eval()` dentro del motor (`builder_saas/builder_functions.php`), lo que rompe dos
|
||||
supuestos habituales de PHP:
|
||||
|
||||
- **`__DIR__` no es la carpeta del hook** — apunta al motor. Un `require_once __DIR__.'/x.php'` falla con
|
||||
*"Failed opening required"*. Usa `APP_ROOT_PATH` (con fallback a `$_SERVER['DOCUMENT_ROOT']`) para
|
||||
construir rutas absolutas dentro del proyecto.
|
||||
- **Un hook llamado por HTTP (`CmsApi.hook()` desde JS) no recibe los parámetros como variables PHP
|
||||
sueltas** — eso solo ocurre cuando se invoca por el filtro Twig `'id' | hook({...})`. Por HTTP,
|
||||
`get_defined_vars()` sale vacío: lee `$_REQUEST[...]` en su lugar.
|
||||
|
||||
Cada uno de estos dos puntos, sin saberlo, cuesta una sesión de depuración completa (el hook "falla" con
|
||||
datos correctos y sin error visible).
|
||||
|
||||
### Seguridad en hooks
|
||||
|
||||
- **`DB::escape()` devuelve el literal YA entrecomillado.** Envolverlo a mano
|
||||
(`"campo = '".escape($v)."'"`) produce `''valor''` → error de sintaxis MariaDB. Para `LIKE`, escapa el
|
||||
patrón completo con sus `%` incluidos. Patrón seguro para filtros que vienen del usuario: lista blanca
|
||||
de campos permitidos, `(int)` para ids, y devolver a Twig solo lo necesario (p.ej. `num`), nunca el SQL
|
||||
construido a mano.
|
||||
- **Nunca deleguen el hashing de contraseñas en el CMS.** `create_field` no tiene tipo contraseña;
|
||||
`isPasswordField:1` en un textfield **no hashea** (guarda y lee la cadena literal), y el
|
||||
`editor_password` del CMS usa **SHA1** (inadecuado). En cualquier login propio, hashea en el hook con
|
||||
`password_hash()` (bcrypt/argon2id) + `password_verify()`, y marca el campo `clave_hash` como
|
||||
`adminOnly`.
|
||||
|
||||
---
|
||||
|
||||
## CmsApi (PHP)
|
||||
|
||||
Reference in New Issue
Block a user