ajustes
This commit is contained in:
260
docs/02-twig.md
Normal file
260
docs/02-twig.md
Normal file
@@ -0,0 +1,260 @@
|
||||
# Twig — Filtros personalizados de Acai
|
||||
|
||||
Este documento describe los filtros Twig propios de Acai (`get`, `queryDB`, `hook`, `module`, `imagec`, `translate`) y los filtros estándar más usados (`raw`, `truncate`, `json_decode`, `split`, `filter`). Acai usa **filtros con pipe `|`**, nunca funciones. Léelo antes de escribir cualquier expresión Twig dentro de `index-base.tpl` o de una sección general. Cubre también la concatenación con `~`, los ternarios, el operador `default` y la diferencia entre `c-if` (=) y `{% if %}` (==).
|
||||
|
||||
## `get` — Consultar tabla de BD
|
||||
|
||||
```twig
|
||||
{{ 'table_name' | get(where, order, limit) }}
|
||||
```
|
||||
|
||||
- `table_name`: **sin prefijo `cms_`**
|
||||
- `where`: string SQL u objeto (opcional)
|
||||
- `order`: string de orden (opcional)
|
||||
- `limit`: entero (opcional)
|
||||
|
||||
```twig
|
||||
{# Todos los registros #}
|
||||
{% set products = 'productos' | get() %}
|
||||
|
||||
{# Con WHERE string #}
|
||||
{% set active = 'productos' | get('activo=1') %}
|
||||
|
||||
{# Con WHERE objeto #}
|
||||
{% set active = 'productos' | get({activo: 1}) %}
|
||||
|
||||
{# Con WHERE + ORDER + LIMIT #}
|
||||
{% set latest = 'noticias' | get('publicado=1', 'fecha DESC', 6) %}
|
||||
|
||||
{# Single record (primer resultado) #}
|
||||
{% set product = 'productos' | get({num: 42}) %}
|
||||
{{ product[0].nombre }}
|
||||
```
|
||||
|
||||
Iterar resultados:
|
||||
```twig
|
||||
{% for producto in 'productos' | get('activo=1', 'num DESC', 10) %}
|
||||
<h3>{{ producto.titulo }}</h3>
|
||||
{% endfor %}
|
||||
```
|
||||
|
||||
Concatenar valor dinámico en WHERE — usa el operador `~`:
|
||||
```twig
|
||||
{% set stock = 'stocks' | get('producto_num=' ~ producto.num) %}
|
||||
```
|
||||
|
||||
## `queryDB` — SQL directo
|
||||
|
||||
Usa el nombre de tabla **completo CON prefijo `cms_`**. Solo cuando `get` no sea suficiente (JOINs, agregaciones complejas).
|
||||
|
||||
```twig
|
||||
{% set results = 'SELECT * FROM cms_productos WHERE precio > 100 ORDER BY precio ASC' | queryDB() %}
|
||||
|
||||
{# JOIN complejo #}
|
||||
{% set top = 'SELECT p.*, COUNT(v.num) as ventas
|
||||
FROM cms_productos p
|
||||
LEFT JOIN cms_ventas v ON v.producto_num = p.num
|
||||
GROUP BY p.num
|
||||
ORDER BY ventas DESC
|
||||
LIMIT 5' | queryDB() %}
|
||||
```
|
||||
|
||||
## `hook` — Ejecutar PHP hook
|
||||
|
||||
```twig
|
||||
{# Llamar y mostrar resultado #}
|
||||
{{ 'hooks/module_id/' | hook({param1: 'value', param2: variable}) }}
|
||||
|
||||
{# Capturar en variable #}
|
||||
{% set result = 'hooks/calcular_precio/' | hook({cantidad: 5, tipo: 'mayoreo'}) %}
|
||||
<p>Total: ${{ result.total }}</p>
|
||||
```
|
||||
|
||||
El primer argumento es la ruta del endpoint del hook (`hooks/<id>/`). El objeto pasa los parámetros, que el PHP recibe como variables (`$cantidad`, `$tipo`).
|
||||
|
||||
## `module` — Renderizar otro módulo
|
||||
|
||||
```twig
|
||||
{{ 'other_module_id' | module({param1: value1}) }}
|
||||
|
||||
{# Capturar en variable #}
|
||||
{% set carrito = 'carrito_compras' | module({usuario_id: 123}) %}
|
||||
```
|
||||
|
||||
Equivale a `<other_module_id :param1="value1"></other_module_id>`.
|
||||
|
||||
## `imagec` — Optimizar imágenes
|
||||
|
||||
```twig
|
||||
{# Redimensionar a ancho específico #}
|
||||
<img src="{{ record.image[0].urlPath | imagec(400) }}">
|
||||
|
||||
{# Con srcset #}
|
||||
<img src="{{ record.image[0].urlPath | imagec(800) }}"
|
||||
srcset="{{ record.image[0].urlPath | imagec(400) }} 400w,
|
||||
{{ record.image[0].urlPath | imagec(800) }} 800w">
|
||||
```
|
||||
|
||||
Acai genera versiones optimizadas (webp + tamaños) y las cachea. Usa siempre `imagec` para imágenes en producción.
|
||||
|
||||
## `translate` — Texto editable y traducción
|
||||
|
||||
Cualquier string con `| translate` se resuelve contra la tabla `textos_generales` del proyecto. Cumple **dos funciones a la vez**:
|
||||
|
||||
1. **Traducción**: cada fila guarda la versión del texto por cada idioma habilitado.
|
||||
2. **Edición de contenidos**: es el canal oficial para que el usuario final modifique esos textos sin tocar código. `| translate` no es solo i18n — es el mecanismo por el que un texto "hardcodeado" se vuelve editable desde el CMS.
|
||||
|
||||
```twig
|
||||
{{ 'Bienvenido' | translate }}
|
||||
{{ variable | translate }}
|
||||
{{ 'Contáctanos' | translate | raw }}
|
||||
```
|
||||
|
||||
Cómo funciona:
|
||||
- Los strings envueltos en `| translate` se buscan en `textos_generales`.
|
||||
- Si existe la fila, devuelve el valor guardado (en el idioma activo).
|
||||
- Si no existe, devuelve el texto original tal cual (fallback).
|
||||
- Las filas se editan desde el admin del CMS o vía `CmsApi` (update sobre `textos_generales`).
|
||||
|
||||
Reglas críticas:
|
||||
- **NO crees archivos JSON de traducciones, `.po`, ni ningún sistema i18n externo.** El único sistema de textos traducibles/editables es la tabla `textos_generales`.
|
||||
- **NO hardcodees textos en el código del módulo** si el usuario debe poder editarlos. Envuélvelos en `| translate`.
|
||||
- Para **cambiar un texto** (traducir o editar), edita la fila correspondiente en `textos_generales` — nunca modifiques el código.
|
||||
- Para **añadir un texto nuevo editable**, basta con escribir el string con `| translate` en el código; el sistema lo recoge y el usuario lo puede editar desde el admin.
|
||||
|
||||
## Filtros estándar
|
||||
|
||||
### `raw` — Renderizar HTML sin escapar
|
||||
|
||||
```twig
|
||||
{{ record.description | raw }}
|
||||
```
|
||||
|
||||
Imprescindible para `wysiwyg` y para HTML construido en variables.
|
||||
|
||||
### `truncate` — Truncar texto
|
||||
|
||||
```twig
|
||||
{{ record.description | truncate(150) }}
|
||||
```
|
||||
|
||||
### `json_decode` — Parsear JSON
|
||||
|
||||
```twig
|
||||
{% set data = jsonString | json_decode %}
|
||||
{{ data.key }}
|
||||
```
|
||||
|
||||
### `split`, `filter`, `length`, `default`, `lower`, `upper`, `trim`, `replace`
|
||||
|
||||
Funcionan igual que en Twig estándar.
|
||||
|
||||
```twig
|
||||
{{ title | default('Sin título') }}
|
||||
{{ items | length }}
|
||||
{{ name | upper }}
|
||||
```
|
||||
|
||||
## Operadores y sintaxis
|
||||
|
||||
### Concatenación con `~`
|
||||
|
||||
Twig usa `~` (no `.` ni `+`):
|
||||
|
||||
```twig
|
||||
{{ 'Hello ' ~ name ~ '!' }}
|
||||
{% set url = '/products/' ~ product.slug ~ '/' %}
|
||||
```
|
||||
|
||||
En filtros:
|
||||
```twig
|
||||
{% set stock = 'stocks' | get('producto_num=' ~ producto.num) %}
|
||||
```
|
||||
|
||||
### Ternario
|
||||
|
||||
```twig
|
||||
{{ isActive ? 'active' : 'inactive' }}
|
||||
{{ title | default('Default Title') }}
|
||||
```
|
||||
|
||||
### Comparaciones
|
||||
|
||||
| Contexto | Igualdad |
|
||||
|----------|----------|
|
||||
| `c-if` | `=` (un solo igual) |
|
||||
| `{% if %}` | `==` (doble igual) |
|
||||
|
||||
```twig
|
||||
{# Atributo Acai - un igual #}
|
||||
<div c-if="layout = 'grid'">
|
||||
|
||||
{# Twig estándar - doble igual #}
|
||||
{% if type == 'premium' %}
|
||||
{% if items | length > 0 %}
|
||||
{% if name is not empty %}
|
||||
```
|
||||
|
||||
## Ejemplos complejos
|
||||
|
||||
### Galería con productos y stock
|
||||
|
||||
```twig
|
||||
{% for producto in 'productos' | get('destacado=1', 'num DESC', 12) %}
|
||||
<div class="producto-card">
|
||||
<img src="{{ producto.imagen[0].urlPath | imagec(400) }}" alt="{{ producto.titulo }}">
|
||||
<h3>{{ producto.titulo }}</h3>
|
||||
<p>{{ producto.descripcion | truncate(100) }}</p>
|
||||
|
||||
{% set stock = 'stocks' | get('producto_num=' ~ producto.num) %}
|
||||
<span>Stock: {{ stock[0].cantidad }}</span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
```
|
||||
|
||||
### Múltiples filtros combinados
|
||||
|
||||
```twig
|
||||
{% set categorias = 'categorias' | get() %}
|
||||
{% set productos = 'productos' | get('activo=1', 'titulo ASC', 20) %}
|
||||
{% set stats = 'hooks/obtener_stats/' | hook({fecha_inicio: '2024-01-01'}) %}
|
||||
|
||||
<h1>{{ stats.titulo | translate }}</h1>
|
||||
|
||||
<nav>
|
||||
{% for cat in categorias %}
|
||||
<a href="{{ cat.enlace }}">{{ cat.nombre }}</a>
|
||||
{% endfor %}
|
||||
</nav>
|
||||
|
||||
{% for prod in productos %}
|
||||
<div>
|
||||
<img src="{{ prod.imagen[0].urlPath | imagec(300) }}" alt="">
|
||||
<h3>{{ prod.titulo }}</h3>
|
||||
</div>
|
||||
{% endfor %}
|
||||
```
|
||||
|
||||
### Composición con `<set>` y configuración global
|
||||
|
||||
```twig
|
||||
<set :tienda="'configuracion_tienda' | get('num != 0')[0]"></set>
|
||||
{% set logoUrl = tienda.logo.0.urlPath
|
||||
? 'https://' ~ server.HTTP_HOST ~ tienda.logo.0.urlPath
|
||||
: 'https://' ~ server.HTTP_HOST ~ '/template/estandar/images/logo.png' %}
|
||||
|
||||
<img src="{{ logoUrl }}" alt="{{ tienda.nombre }}">
|
||||
```
|
||||
|
||||
## Reglas críticas
|
||||
|
||||
1. **Solo filtros, nunca funciones.** `'tabla' | get()`, no `get('tabla')`.
|
||||
2. **Tablas sin prefijo `cms_`** en `get()`. **Con prefijo `cms_`** en `queryDB()`.
|
||||
3. **Upload fields son arrays.** `record.imagen[0].urlPath`, no `record.imagen`.
|
||||
4. **Concatenación con `~`**, no con `.` ni `+`.
|
||||
5. **`c-if` usa `=`**, **`{% if %}` usa `==`**.
|
||||
6. **Foreign keys con sufijo `_num`**: `categoria_num`, no `categoria_id`.
|
||||
7. **`enlace` ya tiene barras** — no las añadas.
|
||||
8. **PK siempre es `num`**, nunca `id`.
|
||||
9. **`| translate` para textos editables** — nunca crees JSONs de i18n.
|
||||
10. Usa `imagec(width)` para imágenes en producción.
|
||||
Reference in New Issue
Block a user