8.0 KiB
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
{{ 'table_name' | get(where, order, limit) }}
table_name: sin prefijocms_where: string SQL u objeto (opcional)order: string de orden (opcional)limit: entero (opcional)
{# 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:
{% for producto in 'productos' | get('activo=1', 'num DESC', 10) %}
<h3>{{ producto.titulo }}</h3>
{% endfor %}
Concatenar valor dinámico en WHERE — usa el operador ~:
{% 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).
{% 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
{# 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
{{ '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
{# 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:
- Traducción: cada fila guarda la versión del texto por cada idioma habilitado.
- Edición de contenidos: es el canal oficial para que el usuario final modifique esos textos sin tocar código.
| translateno es solo i18n — es el mecanismo por el que un texto "hardcodeado" se vuelve editable desde el CMS.
{{ 'Bienvenido' | translate }}
{{ variable | translate }}
{{ 'Contáctanos' | translate | raw }}
Cómo funciona:
- Los strings envueltos en
| translatese buscan entextos_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 sobretextos_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 tablatextos_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
| translateen el código; el sistema lo recoge y el usuario lo puede editar desde el admin.
Filtros estándar
raw — Renderizar HTML sin escapar
{{ record.description | raw }}
Imprescindible para wysiwyg y para HTML construido en variables.
truncate — Truncar texto
{{ record.description | truncate(150) }}
json_decode — Parsear JSON
{% set data = jsonString | json_decode %}
{{ data.key }}
split, filter, length, default, lower, upper, trim, replace
Funcionan igual que en Twig estándar.
{{ title | default('Sin título') }}
{{ items | length }}
{{ name | upper }}
Operadores y sintaxis
Concatenación con ~
Twig usa ~ (no . ni +):
{{ 'Hello ' ~ name ~ '!' }}
{% set url = '/products/' ~ product.slug ~ '/' %}
En filtros:
{% set stock = 'stocks' | get('producto_num=' ~ producto.num) %}
Ternario
{{ isActive ? 'active' : 'inactive' }}
{{ title | default('Default Title') }}
Comparaciones
| Contexto | Igualdad |
|---|---|
c-if |
= (un solo igual) |
{% if %} |
== (doble igual) |
{# 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
{% 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
{% 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
<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
- Solo filtros, nunca funciones.
'tabla' | get(), noget('tabla'). - Tablas sin prefijo
cms_enget(). Con prefijocms_enqueryDB(). - Upload fields son arrays.
record.imagen[0].urlPath, norecord.imagen. - Concatenación con
~, no con.ni+. c-ifusa=,{% if %}usa==.- Foreign keys con sufijo
_num:categoria_num, nocategoria_id. enlaceya tiene barras — no las añadas.- PK siempre es
num, nuncaid. | translatepara textos editables — nunca crees JSONs de i18n.- Usa
imagec(width)para imágenes en producción.