Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
65295c9fe1 |
@@ -4,9 +4,9 @@ This is an Acai CMS website project. Follow these instructions when working with
|
||||
|
||||
## Environment
|
||||
|
||||
- The site runs in Docker, typically at **http://localhost:8080**
|
||||
- You can make HTTP requests to test pages, APIs, or form submissions
|
||||
- If you need to inspect the live site, use browser tools (Playwright MCP) or HTTP requests to localhost:8080
|
||||
- The site runs on Acai Forge — get the live editable URL with the `get_web_url` MCP tool (never assume a fixed domain)
|
||||
- You can make HTTP requests to test pages, APIs, or form submissions against that URL
|
||||
- If you need to inspect the live site, use browser tools (Playwright MCP) or HTTP requests to the Forge URL
|
||||
|
||||
## Project Structure
|
||||
|
||||
@@ -28,13 +28,7 @@ This is an Acai CMS website project. Follow these instructions when working with
|
||||
│ ├── data/schema/ # Database table schemas (JSON)
|
||||
│ ├── lib/plugins/ # CMS plugins
|
||||
│ └── uploads/ # Uploaded media files
|
||||
├── .acai # Project config (domain, tokens, DB credentials)
|
||||
├── .docker/
|
||||
│ ├── .env # Docker environment (DB credentials)
|
||||
│ ├── docker-compose.yml
|
||||
│ ├── tunnel-url.txt # Public tunnel URL (if active)
|
||||
│ └── bore-db-url.txt # Database tunnel URL (if active)
|
||||
└── database.sql # Database dump
|
||||
└── .acai # Project config (domain, tokens, DB credentials)
|
||||
```
|
||||
|
||||
## Key Concepts
|
||||
@@ -89,28 +83,9 @@ Rule of thumb:
|
||||
|
||||
See [docs/hooks-and-api.md](docs/hooks-and-api.md) for usage.
|
||||
|
||||
## Database Access
|
||||
|
||||
When the site is running in Docker, you can connect to the database:
|
||||
|
||||
- **Host:** `127.0.0.1`
|
||||
- **Port:** Check `.docker/docker-compose.yml` for the mapped port (usually 3307+)
|
||||
- **Credentials:** Read from `.docker/.env`:
|
||||
- `DB_USERNAME`
|
||||
- `DB_PASSWORD`
|
||||
- `DB_DATABASE`
|
||||
|
||||
```bash
|
||||
docker exec -it dw-<project-name>-db mysql -u root -p<password> <database>
|
||||
```
|
||||
|
||||
**Important:** Table names in CmsApi/Twig do NOT use the `cms_` prefix. The primary key is always `num`, never `id`.
|
||||
|
||||
## Acai Core (web-base)
|
||||
|
||||
The project workspace contains only the **customization layer** (modules, hooks, schemas, uploads). The CMS core (routing, rendering engine, admin panel, APIs) lives in a separate directory called **web-base** that is mounted as a Docker volume.
|
||||
|
||||
The web-base path can be obtained via: `GET http://localhost:9090/api/web-base-path`
|
||||
The project workspace contains only the **customization layer** (modules, hooks, schemas, uploads). The CMS core (routing, rendering engine, admin panel, APIs) lives in a separate, shared directory called **web-base**.
|
||||
|
||||
Do NOT modify web-base files — they are shared across all projects.
|
||||
|
||||
@@ -148,7 +123,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, custom CMS plugins (checkPoints)
|
||||
- [docs/hooks-and-api.md](docs/hooks-and-api.md) — PHP hooks, CmsApi, CocoDB, record creation
|
||||
- [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)
|
||||
|
||||
@@ -471,7 +471,7 @@ El nombre del campo de imagen viene de `builder.json` → `vars.NOMBRE.relations
|
||||
- `tableName`: `"builder_custom"` (siempre, sin prefijo cms_)
|
||||
- `recordId`: el `recordNum` del paso 1 (ej: `"778"`)
|
||||
- `fieldName`: el campo de relations del builder.json (ej: `"image1"`)
|
||||
- `imageUrl`: URL completa accesible desde Docker
|
||||
- `imageUrl`: URL completa y públicamente accesible
|
||||
3. `reorder_record_uploads` si es necesario — pasar array de upload IDs en el orden deseado
|
||||
4. `list_record_uploads` para verificar
|
||||
|
||||
|
||||
+6
-63
@@ -33,9 +33,7 @@ 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
|
||||
- **`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 uses `CocoDB::getInstance()` salvo necesidad real muy excepcional
|
||||
- No escribas SQL manual con `prepare()/bind_param()` salvo que no exista forma razonable de resolverlo con `CmsApi` o `CocoDB`
|
||||
|
||||
### Estructura de un Hook
|
||||
@@ -58,13 +56,15 @@ return [
|
||||
|
||||
### Testing Hooks
|
||||
|
||||
El Docker debe estar corriendo. Hacer curl al endpoint del hook:
|
||||
Usa la URL editable de Forge del proyecto (`get_web_url`) — no hace falta ningún servidor local. Haz
|
||||
`curl` al endpoint del hook contra esa URL:
|
||||
|
||||
```bash
|
||||
curl http://localhost:8080/hooks/example_hook/
|
||||
curl https://<tu-url-de-forge>/hooks/example_hook/
|
||||
```
|
||||
|
||||
No usar X-Hooks-Token en desarrollo local.
|
||||
Para diagnosticar sin depender del enrutado HTTP directo (que en Forge puede dar 404 — ver más abajo),
|
||||
usa el patrón vía filtro `| hook` desde un módulo/página temporal.
|
||||
|
||||
### Cómo Llamar Hooks
|
||||
|
||||
@@ -109,63 +109,6 @@ $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`.
|
||||
|
||||
---
|
||||
|
||||
## 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)
|
||||
|
||||
@@ -1,21 +1,5 @@
|
||||
# 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 |
|
||||
|
||||
Reference in New Issue
Block a user