From 67719189190945309827339bd6df484c7ccf42ad Mon Sep 17 00:00:00 2001 From: Dmielgo Date: Mon, 14 Sep 2026 14:51:44 +0100 Subject: [PATCH 1/2] =?UTF-8?q?docs:=20corrige=20API=20de=20hooks=20(CocoD?= =?UTF-8?q?B::getInstance=20no=20existe)=20y=20documenta=20modelo=20de=20e?= =?UTF-8?q?jecuci=C3=B3n,=20escape()=20y=20aviso=20de=20fiabilidad=20de=20?= =?UTF-8?q?write-ops=20del=20MCP?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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// 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ó --- docs/hooks-and-api.md | 45 ++++++++++++++++++++++++++++++++++++- docs/mcp-tools-reference.md | 16 +++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/docs/hooks-and-api.md b/docs/hooks-and-api.md index 3e94aec..1a12152 100644 --- a/docs/hooks-and-api.md +++ b/docs/hooks-and-api.md @@ -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//` 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 ``/`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}) %} +
{{ d|json_encode(1) }}
+ ``` +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) diff --git a/docs/mcp-tools-reference.md b/docs/mcp-tools-reference.md index 3c71c11..91f9901 100644 --- a/docs/mcp-tools-reference.md +++ b/docs/mcp-tools-reference.md @@ -1,5 +1,21 @@ # MCP Tools Reference +## ⚠️ `success:true` no siempre significa que la operación se aplicó + +Las write-ops/DDL (`create_field`, `update_field`, `acai-write`, `acai-line-replace`, +`set_layout_field`, `update_table_metadata`, `create_table`…) pueden devolver `success:true` / +`writeCompleted:true` **aunque el cambio real (ALTER de MySQL, insert, compilación Twig) no se haya +aplicado**. `get_table_schema` refleja el registro/`.ini`, no necesariamente el estado real de MySQL. + +**Verifica siempre con una operación real posterior**, nunca te fíes solo de la respuesta del tool: +- Tras escribir/editar un módulo: `compile_module` o recargar la página y comprobar que renderiza. +- Tras crear/editar un campo: hacer un insert/update real en esa tabla y confirmar que no falla. +- Tras un insert, si necesitas el id, no te fíes ciegamente de `lastId`/`insert_id` — si algo no cuadra, + relee por un campo único ya conocido. + +Es más lento que confiar en la respuesta, pero el coste de no hacerlo es peor: este es el fallo más +repetido del equipo (bloqueos de insert silenciosos, módulos servidos rotos, registros fantasma). + ## Quick Reference | Tool | Categoría | Acción | -- 2.54.0 From 9565b4410b8c232f429452c83fe563922831be87 Mon Sep 17 00:00:00 2001 From: Dmielgo Date: Mon, 14 Sep 2026 14:52:58 +0100 Subject: [PATCH 2/2] docs: documenta el contrato de plugins CMS personalizados (checkPoints en cms/lib/plugins/index.php) No estaba documentado en absoluto (solo se mencionaba builder_saas para color). Evita tener que explorar el servidor por SSH para entender addPlugins/checkPoints/gating/filtros. --- CLAUDE.md | 2 +- docs/hooks-and-api.md | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/CLAUDE.md b/CLAUDE.md index 8007f15..4c6a970 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -148,7 +148,7 @@ Key workflows: - [docs/modular-system.md](docs/modular-system.md) — Modules, general sections, global variables - [docs/builder-fields.md](docs/builder-fields.md) — Builder field types, Acai attributes, c-form, components - [docs/twig-filters.md](docs/twig-filters.md) — Twig filters reference (get, hook, module, queryDB, etc.) -- [docs/hooks-and-api.md](docs/hooks-and-api.md) — PHP hooks, CmsApi, CocoDB, record creation +- [docs/hooks-and-api.md](docs/hooks-and-api.md) — PHP hooks, CmsApi, CocoDB, record creation, custom CMS plugins (checkPoints) - [docs/css-js-conventions.md](docs/css-js-conventions.md) — CSS/JS/Vue 3, Tailwind, BEM, native components - [docs/quick-reference.md](docs/quick-reference.md) — Cheat sheet: domain rules, field types, filters - [docs/production-patterns.md](docs/production-patterns.md) — Real production patterns (header, zigzag, FAQ, forms) diff --git a/docs/hooks-and-api.md b/docs/hooks-and-api.md index 1a12152..c50e551 100644 --- a/docs/hooks-and-api.md +++ b/docs/hooks-and-api.md @@ -152,6 +152,22 @@ datos correctos y sin error visible). --- +## Plugins CMS personalizados (checkPoints) + +Para entender **cómo despacha el CMS un plugin custom** (no confundir con hooks de módulo), lee primero +`cms/lib/plugins/index.php` **de ese proyecto** — no hace falta explorar el servidor por SSH ni el +código de `web-base`, el contrato completo está ahí: + +- `addPlugins($type, &$var, $fieldtype)` despacha los checkPoints con `include` (no clases/autoload). +- Gating nativo por `adminOnly` + `isAdmin` — un checkPoint puede declararse solo-admin. +- `$var` se pasa **por referencia** — un plugin puede mutar el valor que ven los siguientes. +- Filtros `menuFilter`/`actionFilter` por tabla/action, para que un plugin solo se dispare donde aplica. +- `newFieldType` para registrar tipos de campo nuevos del builder. +- La instalación de un plugin va vía `install.sql` + `schemaPresets` — **no** `schemas/` (ese es el + sistema viejo de `seo_toolkit`, con el `install.php` comentado; no lo repliques). + +--- + ## CmsApi (PHP) API server-side para operaciones de base de datos. Disponible en todos los hooks. -- 2.54.0