# Sintaxe de templates

Templates de rota, blocos reutilizáveis, valores e composição condicional.

Os templates usam shortcodes entre `[#` e `#]`.

**Templates não têm lógica, por princípio.** Um template é markup limpo e simples: ele exibe valores, repete um template para uma lista e, no máximo, escolhe qual template vem a seguir (`[#if … tpl=…#]`). Decisões, cálculos e preparação de dados ficam em bibliotecas, onde podem ser lidos e testados num só lugar. Não há blocos de lógica, laços com condições nem expressões dentro dos templates, como engines como Twig ou Blade permitem; misturar lógica no markup é exatamente o que o Nimbly evita.

```
[#shortcode#]
[#shortcode param#]
[#shortcode key=value key2=value2#]
```

Os shortcodes podem ser aninhados: qualquer valor de parâmetro ou nome de shortcode pode conter um shortcode:

```
[#get data.users.[#get selected-id#]#]
```

Isso também funciona em parâmetros `tpl=` e em nomes de shortcode, o que permite **compor nomes de template dinamicamente**:

```
[#if current_image=(not-empty) tpl=card-image[#if current_image=(empty) echo=-placeholder#]#]
```

Resulta em `tpl=card-image` quando há uma imagem, e em `tpl=card-image-placeholder` quando não há.

Uma aplicação especialmente poderosa é a **escolha por tipo de conteúdo**: coloque um campo de tipo diretamente no nome do shortcode para ir a um template específico do tipo, sem nenhuma ramificação:

```
[#card-[#get item.type#]#]
```

Resulta em `[#card-news#]`, `[#card-event#]`, `[#card-download#]` etc., conforme o tipo do item. Esse padrão elimina cadeias de condições `[#if item.type=X tpl=Y#]` e cresce sem esforço quando novos tipos surgem.

Use `default=` para garantir um nome de shortcode válido quando o valor pode faltar ou não ser reconhecido; um shortcode não resolvido aparece como texto literal na saída:

```
[#card-[#get item.type default=unknown#]#]
```

Implemente `[#card-unknown#]` como alternativa genérica (ou deixe-o vazio para não exibir nada).

Os templates são arquivos simples no formato de saída desejado (HTML, JSON, CSS etc.). Eles têm a extensão `.tpl`.

## Templates de rota

Toda URL corresponde a um arquivo em `ext/uri/`:

```
ext/uri/index.tpl              → /
ext/uri/about/index.tpl        → /about/
ext/uri/blog/(slug)/index.tpl  → /blog/<anything>/
```

Um template de rota gera a página:

```
[#html#]
```

O `main.tpl` na mesma pasta contém o corpo da página, exibido dentro da shell HTML. Ele sempre pertence a essa rota; até `ext/uri/main.tpl` é só o corpo da página inicial (`/`), não um layout global.

## Templates reutilizáveis

Ficam em `ext/tpl/<name>/index.tpl`. São chamados pelo nome como shortcode:

```
[#hero-section#]
```

Os templates **não** recebem parâmetros diretamente. Para passar dados a um template, use `[#set ... overwrite#]` antes de chamá-lo:

```
[#set back_url=/agenda overwrite#]
[#back-button#]
```

Dentro do template, leia a variável com `[#get back_url default=/#]`. Os shortcodes de biblioteca (`ext/lib/<name>/`) são diferentes: eles recebem `$params` e podem ser chamados com parâmetros.

## Convenção de nomes de templates

Dê a cada template reutilizável um nome com **o tipo primeiro**, seguido da rota ou do assunto a que pertence:

```
{type}-{route-or-subject}
```

Tipos e exemplos:

| Tipo | Usado para | Exemplo |
|------|----------|---------|
| `page-` | Template de página inteira (um por rota) | `page-about`, `page-projects` |
| `section-` | Uma seção ou bloco dentro de uma página | `section-home-header`, `section-page-header` |
| `card-` | Um cartão repetível | `card-project`, `card-team-member` |
| `logo-` | Um componente que exibe um logo | `logo-partner`, `logo-partner-image` |
| `icon-` | Um ícone inline | `icon-arrow-right`, `icon-chevron-right` |
| `form-` | Um componente de formulário | `form-registration` |

Os subtemplates dentro de uma pasta de página seguem a mesma regra, com o tipo primeiro:

```
ext/tpl/page-projects/
  section-project-list.tpl
  card-project.tpl
  card-project-image.tpl
```

**Nunca** coloque o tipo no final (`project-card`, `about-page`). Com o tipo primeiro, todos os cartões, todas as seções e todos os templates de página ficam juntos no sistema de arquivos.

## Templates de uma rota

Os templates também podem ficar soltos dentro de uma pasta de URI (não numa subpasta). Eles não são rotas; são templates parciais incluídos a partir do `index.tpl` ou do `main.tpl` dessa rota.

```
ext/uri/new/page-settings-fields.tpl   ← partial, not a route
ext/uri/new/index.tpl                  ← route
ext/uri/new/main.tpl                   ← route body
```

Importante: uma subpasta em `ext/uri/` **sempre** cria uma nova rota. Um arquivo `.tpl` solto numa pasta de URI é um template parcial dessa rota.

## route.inc

O `route.inc` só é usado em **rotas dinâmicas**: rotas com segmentos `(param)` na URL que precisam ser correspondidos, validados e aceitos ou recusados. As rotas estáticas (caminhos simples, sem parâmetros) não usam `route.inc`; o `index.tpl` delas é carregado diretamente.

As rotas dinâmicas **não são descobertas sozinhas**. Toda rota com um segmento `(param)` também precisa de um registro no recurso `.routes`; senão a requisição nunca chega ao `route.inc` e o resultado é um 404. Por exemplo, `ext/uri/en/blog/(slug)/route.inc` precisa de um registro em `.routes` com `route: "en/blog/(slug)"` e um `order` adequado, normalmente `200`. Rode `php core/cli/nimbly.php routes:sync` para percorrer os arquivos `ext/uri/**/route.inc` e criar os registros de `.routes` que faltam.

Um `route.inc` fica ao lado do `index.tpl` e decide se a requisição pertence a esta rota:

```php
<?php
$parts = router_match(__FILE__);
if ($parts === false) return;

$slug = $parts[0];
load_library('data');
load_library('util');

$records = data_read_index('articles', 'title_slug', md5_uuid($slug));
if (empty($records)) return;

set_variable_dot('record', reset($records));
router_accept();
```

### Funções do roteador

| Função | Descrição |
|---|---|
| `router_match(__FILE__)` | Compara a URL atual com os segmentos dinâmicos desta rota. Devolve um array com os valores capturados (um por `(param)` no caminho), ou `false` se a URL não corresponde. Chame sempre primeiro. |
| `router_accept()` | Indica que este route.inc aceita a requisição. A engine de templates segue para o `index.tpl`. |
| `router_deny()` | Indica a recusa; o roteador continua procurando outra rota correspondente. Chamar `return` sem `router_accept()` tem o mesmo efeito. |

O padrão: chame `router_match()`, valide o resultado, carregue e valide os dados e só chame `router_accept()` quando tudo estiver certo. Se algo falhar, apenas `return`; a requisição passa para a próxima rota ou para um 404.

### Valide toda a entrada da rota na fronteira

Não confie em nenhum valor que chega por uma URL. Isso inclui segmentos dinâmicos do caminho, parâmetros GET e de consulta, cabeçalhos usados pela rota e qualquer valor da requisição obtido pelo sistema de variáveis dos templates. Scanners automáticos enviam rotineiramente fragmentos de SQL, sintaxe de shell, sequências de travessia de pastas, números enormes e outras cargas de fuzzing a todo parâmetro público, mesmo que o Nimbly não use SQL.

Valide e normalize esses valores na rota antes de passá-los a uma biblioteca ou de usá-los em cálculos, buscas de recursos, caminhos de arquivo, chaves de cache, redirecionamentos ou comandos:

- Prefira uma allowlist com a sintaxe exata aceita; não tente remover fragmentos sabidamente ruins.
- Imponha tipo, tamanho, intervalo e valores permitidos, conforme o caso.
- Converta a entrada numérica validada em `int` ou `float` antes de calcular.
- Mantenha o texto bruto da requisição fora de caminhos de arquivo e de cache, a menos que toda a sua sintaxe tenha sido validada por allowlist e faça parte de propósito da identidade do cache.
- Recuse a entrada inválida na hora, com a resposta de falha normal da rota. Rotas de assets parecidas com uma API podem devolver um HTTP 400 vazio quando a entrada malformada deve ficar visível para limitação de taxa ou detecção de bots.
- Valide no PHP por conta própria, mesmo quando o Apache, um proxy ou uma CDN já filtram o mesmo parâmetro; o filtro na borda é uma otimização, não a fronteira de confiança.

Por exemplo, um parâmetro `ratio` de miniatura precisa ser validado como um decimal positivo limitado e convertido em float antes dos cálculos da imagem. Um texto como `ratio=1' OR 1=1` nunca deve chegar ao cálculo da miniatura nem à criação do arquivo de cache.

Mantenha o `route.inc` só com lógica de roteamento: corresponder, validar, definir uma ou duas variáveis, aceitar ou recusar. A lógica de negócio pertence a uma biblioteca carregada pelo `index.tpl` por meio de um shortcode.

> **Nunca acrescente um `route.inc` a uma rota estática.** Uma rota estática (como `login/`, `about/`) não tem segmentos `(param)` e é sempre aceita; acrescentar um `route.inc` sem chamar `router_accept()` causa um 404. Se precisar de lógica numa página estática, coloque-a num shortcode chamado a partir do `index.tpl`, não no `route.inc`.

> **`route` é um nome de shortcode reservado.** Não existe um shortcode `[#route#]`, e não é possível criar um. O nome de arquivo `route.inc` pertence exclusivamente ao sistema de rotas.

---
