# Menus do Site — Rotas, Controllers, Requests e Presenters

> Todas as rotas do CMS vivem em `v6/routes/basepro.php`, dentro do grupo
> `Route::group(['prefix' => 'cms', 'before' => 'csrf'], function () { /* corpo do grupo */ })`
> iniciado em `v6/routes/basepro.php:35`. O prefixo `cms` produz URLs
> comecando por `/cms/`.

---

## 1. Rotas do CMS

| Método | URI | Nome da rota | Controller@ação | Autorização |
|---|---|---|---|---|
| GET | `/cms/menu` | `menu.index` | `Prefecture\MenuController@index` | `check.route.permission` -> `menu.index` |
| GET | `/cms/menu/create` | `menu.create` | `Prefecture\MenuController@create` | aberta (ver §5) |
| POST | `/cms/menu` | `menu.store` | `Prefecture\MenuController@store` | aberta (ver §5) |
| GET | `/cms/menu/{menu}` | `menu.show` | `Prefecture\MenuController@show` | `menu.show` |
| GET | `/cms/menu/{menu}/edit` | `menu.edit` | `Prefecture\MenuController@edit` | aberta (ver §5) |
| PUT/PATCH | `/cms/menu/{menu}` | `menu.update` | `Prefecture\MenuController@update` | aberta (ver §5) |
| DELETE | `/cms/menu/{menu}` | `menu.destroy` | `Prefecture\MenuController@destroy` | aberta (ver §5) |
| GET | `/cms/menu-item` | `menu-item.index` | `Prefecture\MenuItemController@index` | **metodo inexistente** |
| GET | `/cms/menu-item/create` | `menu-item.create` | `Prefecture\MenuItemController@create` | **metodo inexistente** |
| POST | `/cms/menu-item` | `menu-item.store` | `Prefecture\MenuItemController@store` | aberta (ver §5) |
| GET | `/cms/menu-item/{menu_item}` | `menu-item.show` | `Prefecture\MenuItemController@show` | `menu.show` |
| GET | `/cms/menu-item/{menu_item}/edit` | `menu-item.edit` | `Prefecture\MenuItemController@edit` | aberta (ver §5) |
| PUT/PATCH | `/cms/menu-item/{menu_item}` | `menu-item.update` | `Prefecture\MenuItemController@update` | aberta (ver §5) |
| DELETE | `/cms/menu-item/{menu_item}` | `menu-item.destroy` | `Prefecture\MenuItemController@destroy` | aberta (ver §5) |
| GET | `/cms/itens-do-menu/{menuID}/` | `menu-item-by-menu` | `Prefecture\MenuItemController@createByMenu` | **metodo inexistente** |
| POST | `/cms/get/qtd-subitems/by/item` | `ajax.get.qtd.sub.items` | `Prefecture\MenuItemController@getQtdSubItemsByItems` | `check.route.permission` |
| POST | `/cms/ajust-items-lvl-1` | `ajust.items.lvl.1` | `Prefecture\MenuItemController@ajustOrder` | `check.route.permission` |
| GET | `/cms/datatable/menus` | `datatable.menus` | `Prefecture\DatatableController@get_menus_site` | `check.route.permission` |

Referencias:
- `v6/routes/basepro.php:170` — `Route::resource('menu', 'Prefecture\MenuController');`
- `v6/routes/basepro.php:171` — `Route::resource('menu-item', 'Prefecture\MenuItemController');`
- `v6/routes/basepro.php:121` — `Route::post('get/qtd-subitems/by/item', 'Prefecture\MenuItemController@getQtdSubItemsByItems')`
- `v6/routes/basepro.php:122` — `Route::post('ajust-items-lvl-1', 'Prefecture\MenuItemController@ajustOrder')`
- `v6/routes/basepro.php:234` — `Route::get('itens-do-menu/{menuID}/', 'Prefecture\MenuItemController@createByMenu')`
- `v6/routes/basepro.php:396` — inicio do grupo `prefix => 'datatable'`
- `v6/routes/basepro.php:445` — `Route::get('/menus', 'Prefecture\DatatableController@get_menus_site');`

### Observacoes criticas

1. **`menu-item.index` e `menu-item.create` nao tem metodo correspondente.**
   `MenuItemController` implementa apenas `__construct`, `getQtdSubItemsByItems`,
   `ajustOrder`, `store`, `show`, `edit`, `update`, `destroy` e `toggle`
   (`v6/app/Http/Controllers/Prefecture/MenuItemController.php:36-325`).
   As rotas `menu-item.index` e `menu-item.create` resultam em erro 500.
   A view `v6/resources/views/prefecture/menus-itens/index.blade.php` (77 linhas)
   esta **orfa** — nenhuma acao a alcanca; ela ainda usa a variavel `$menusItens`,
   que nao existe em lugar nenhum do codigo.
2. **`menu-item-by-menu` esta quebrada**: aponta para
   `MenuItemController@createByMenu`, metodo que nao existe. E a propria view
   `menus-itens/create.blade.php` existe, mas nao e referenciada por nenhuma
   acao.
3. **`menu.show` (`MenuController@show`)** tem corpo vazio (apenas `//`), e a view
   `prefecture.menus.show` existe mas nao e usada por essa acao.
4. **`MenuController@create`** nao renderiza formulario: faz
   `redirect('/cms/menu')` (`v6/app/Http/Controllers/Prefecture/MenuController.php:63-77`).
   O formulario real esta embutido em `prefecture.menus.create` /
   `prefecture.menus.edit`, e o `menus/create.blade.php` abre o form com
   `route => ['menu.index']` (destino errado).
5. **`MenuItemController@show`** usa o parametro `$menuID` na rota
   `menu-item.show` (parametro `{menu_item}`), que resolve para a **string** do
   id do item, nao do menu. A acao espera o id do menu. Na pratica o CMS
   navega para `/cms/menu-item/{idDoMenu}` (o botao "Ajustar itens" em
   `v6/public/assets/js/custom/contents/menus/datatable.js:29`), e por isso
   funciona — mas o contrato da rota esta incorreto.

---

## 2. Rota publica (portal) de toggle

| Metodo | URI | Nome | Controller@acao | Middleware |
|---|---|---|---|---|
| POST | `/menu-item/{id}/toggle` | `menu-item.toggle` | `Prefecture\MenuItemController@toggle` | somente `check.site.manutention` |

- Declarada em `v6/routes/web.php:232`, dentro do grupo
  `Route::middleware(['check.site.manutention'])` iniciado em
  `v6/routes/web.php:7`.
- **Nao exige login** e **nao exige permissao**: qualquer visitante que conheca
  o id de um item pode alternar `is_active`. Risco real de seguranca — ver
  `05-REGRAS-E-PORTABILIDADE.md` §4.
- O corpo da requisicao (`is_active`) e **ignorado**: a acao apenas inverte o
  valor atual (`v6/app/Http/Controllers/Prefecture/MenuItemController.php:319-325`).

---

## 3. Rotas adjacentes (nao pertencem a este modulo)

| Metodo | URI | Controller | Observacao |
|---|---|---|---|
| GET | `/cms/conteudo/secretarias/menus/{id}` | `Prefecture\MenuSecretaryController@index` (`v6/routes/basepro.php:371`) | Menu de **categorias das secretarias**; homonimo, sem relacao com `menus`/`menu_items` |
| GET | `/cms/get_categorias/secretarias/{id}` | `Prefecture\MenuSecretaryController@returnSecretarias` | idem |
| POST | `/cms/order_categorias/secretarias` | `Prefecture\MenuSecretaryController@orderCategory` | idem |

---

## 4. Controller `MenuController`

**Arquivo:** `v6/app/Http/Controllers/Prefecture/MenuController.php` (190 linhas)

| Metodo | Linha | Comportamento |
|---|---|---|
| `__construct(Menu $table, Menu $contentLog, IMenuRepository $repository)` | `:37` | Executa `menuClicks('menu')` (incrementa `menu_orders.clicks` do slug `menu`) e registra o middleware `check.route.permission`. `$contentLog` e a propria `Menu` (nao ha um model de log dedicado) |
| `index()` | `:51` | Log `Menus`; devolve `prefecture.menus.index` com `Menu::all()` (variavel **nao usada** pela view) |
| `create()` | `:63` | Log "Acesso cadastro de menu"; `redirect('/cms/menu')` |
| `store(Request $request)` | `:80` | `repository->upInsert($request)`; log "Cadastro de menu"; `flash('Registro cadastrado com sucesso.')`; redirect para `menu.index`. Em excecao: `flash($exception->getMessage())->error()` e `redirect()->back()` |
| `show(Menu $menu)` | `:110` | Corpo vazio (`//`) |
| `edit(int $id)` | `:121` | `Menu::findOrFail($id)`; log `Menus.edit`; devolve `prefecture.menus.edit` |
| `update(Request $request, int $id)` | `:137` | `repository->upInsert($request, $id)`; log "Edicao de menu"; redirect `menu.index` |
| `destroy(int $id)` | `:166` | `DB::transaction`: `Menu::find($id)` -> `delete()` (**soft delete**); log "Exclusao de menu"; redirect `menu.index`. Nao ha verificacao de itens filhos: os `menu_items` ficam orfaos |

Nao existe FormRequest para `Menu`: o `store`/`update` usam `Illuminate\Http\Request`
cru, sem validacao de `name` (o campo e `NOT NULL` no banco, logo um POST sem
`name` estoura a constraint e cai no `catch (\Exception)`).

---

## 5. Controller `MenuItemController`

**Arquivo:** `v6/app/Http/Controllers/Prefecture/MenuItemController.php` (327 linhas)

| Metodo | Linha | Autenticacao | Comportamento |
|---|---|---|---|
| `__construct` | `:36` | `check.route.permission` | `menuClicks('menu-items')` — **nao existe** linha com esse slug em `menu_orders`, logo o contador nunca e incrementado |
| `getQtdSubItemsByItems(Request)` | `:49` | JSON | `MenuItem::where('menus_id', $request->get('menus_id'))->where('menus_items_id', $request->get('menus_items_id'))->pluck('name', 'id')`; devolve a **quantidade** de subitems. O JS que chama este endpoint apenas habilita/desabilita o botao — nao ha uso do valor |
| `ajustOrder(Request)` | `:62` | JSON | Recebe `sortable_data` (array de ids), renumera `order` de 1 em 1 com um `save()` por item (sem transacao); log "Edicao da posicao dos itens de menu"; responde `json(1)` |
| `store(MenuItemRequest)` | `:91` | redirect | injeta `is_mega_menu`; `repository->upInsert`; log detalhado; redirect para `menu-item.show` com `menus_id` |
| `show(int $menuID)` | `:122` | view | Carrega menu, itens raiz, todos os itens, paginas estaticas, categorias raiz, `order = max(order)+1`; log "Listou itens do menu"; devolve `prefecture.menus-itens.show` |
| `edit(int $id)` | `:149` | view | Reconstroi todo o estado do formulario (categoria selecionada, subcategoria, conteudos, tipo do link) e devolve `prefecture.menus-itens.edit` |
| `update(MenuItemRequest, int $id)` | `:244` | redirect | Igual ao `store`, com log "Edicao de item do menu" |
| `destroy(int $id)` | `:275` | redirect | Se houver subitens (`menus_items_id = $id`), **nao apaga** e informa "O item nao pode ser deletado, pois existem dependencias". Sem filhos: `DB::transaction` com `$menuItem->delete()` (**fisico**, sem `SoftDeletes`) |
| `toggle($id)` | `:319` | JSON | `MenuItem::findOrFail($id)`; inverte `is_active`; `save()`; responde `['success' => true]` |

### Regra de dependencia no `destroy`

```php
$subMenus = MenuItem::where('menus_items_id', $id)->get();

if (count($subMenus) == 0) {
    DB::transaction(function () use ($menuItem) {
        $menuItem->delete();
        // activity()->performedOn($menuItem)->log('Exclusão de um item de menu')
    });
} else {
    flash('O item não pôde ser deletado, pois existem depêndencias.')->info();
}
```

Fonte: `v6/app/Http/Controllers/Prefecture/MenuItemController.php:282-307`.

---

## 6. Autorizacao (Sentinel + `check.route.permission`)

**Middleware:** `v6/app/Http/Middleware/CheckRoutePermission.php`

Fluxo da checagem:

1. Se nao ha sessao, redireciona para `login`.
2. Se o usuario e `super-admin`, passa direto.
3. Caso contrario, `hasAccess()`:
   - `$access = Route::getCurrentRoute()->getName()`;
   - `$accessArr = explode('.', $access)`;
   - `$openRoutes = ['login','logout','datatable','dashboard','error','ajax','info-request-status-update']`;
   - `$resourceRoutes = ['edit' => 'update', 'create' => 'store', 'delete' => 'destroy']`;
   - se **qualquer** segmento do nome da rota estiver em `$openRoutes` **ou** em
     `array_values($resourceRoutes)` (`update`, `store`, `destroy`), a rota e
     considerada **aberta**;
   - em seguida, `if (str_contains($access, 'menu-item')) $access = str_replace('menu-item', 'menu', $access);`
     — todas as rotas `menu-item.*` passam a exigir as permissoes `menu.*`;
   - o acesso e concedido se `Sentinel::check()->hasAnyAccess([$access, $accessSegment])`.

### Permissoes do modulo

Definidas em `v6/database/seeds/RolesTableSeeder.php:86`:

```
"menu.index":false,"menu.create":false,"menu.edit":false,"menu.delete":false,
"menu.inactive":false,"menu.inactivate":false,"menu.restore":false,"menu.show":false
```

O papel base (linha 86) tem todas as permissoes de menu como `false`; o
`super-admin` contorna a checagem. **Nao existe `menu-item.*`** no catalogo de
permissoes — o mapeamento para `menu.*` e feito em tempo de execucao.

### Consequencia pratica (risco)

Como `store`, `update` e `destroy` sao tratados como "rotas abertas" pelo
middleware, **qualquer usuario logado** pode criar, alterar e excluir itens de
menu, mesmo sem a permissao `menu.edit`/`menu.delete`. As flags `edit`/`delete`
que o `struct_views` anexa a cada linha do DataTable apenas controlam a
**exibicao** dos botoes na tela — nao a autorizacao real.

---

## 7. Form Requests

**Arquivo:** `v6/app/Http/Requests/MenuItemRequest.php`

| Cenário | Regras |
|---|---|
| `type == 0` | `menus_id` required, `name` required, `link` required; se `qtdItems >= 6` e a rota atual e `menu-item.store`, adiciona `menus_items_id` required |
| `type == 1` | `name` required, `menus_id` required, `static_pages_id` required + `SelectRule`; mesma condicao de 6 itens |
| `type == 2` | `name` required, `menus_id` required, `category_contents_id` required; mesma condicao de 6 itens |
| demais tipos | `type` required, `name` required |

Observacoes:

- `authorize()` retorna `true` sempre — nao ha checagem de papel no FormRequest.
- `rules()` remove `order` do request (`$this->request->remove('order')`): a
  ordem **sempre** e recalculada; o valor enviado pelo form (`$qtdMenus` ou
  `MenuItem::max('order') + 1`) e descartado.
- `qtdItems` conta **apenas itens raiz** (`whereNull('menus_items_id')`).
- A regra do limite de 6 so se aplica na rota `menu-item.store` — na edicao,
  um item existente pode ser gravado sem pai mesmo com mais de 6 itens raiz.
- Mensagens em portugues em `messages()` (`v6/app/Http/Requests/MenuItemRequest.php:85-95`).

**Nao ha Form Request para `Menu`** — `MenuController` usa `Request` cru.

---

## 8. Repositories e contratos

| Contrato | Assinatura | Implementacao |
|---|---|---|
| `App\Repositories\Contracts\IMenuRepository` | **vazia** (sem metodos) | `v6/app/Repositories/Eloquent/Prefecture/Menu/MenuRepository.php` |
| `App\Repositories\Contracts\IMenuItemRepository` | **vazia** (sem metodos) | `v6/app/Repositories/Eloquent/Prefecture/MenuItem/MenuItemRepository.php` |

Os contratos nao declaram nada: nao ha garantia de contrato entre controller e
repository. A interface existe apenas para satisfazer a injecao de dependencia
no construtor dos controllers.

`MenuRepository::structureUpInsert`:

```php
protected function structureUpInsert($request): array
{
    $request['slug'] = str_slug($request['name']);
    $fields = array_keys($request);
    return $this->filter_request($request, $fields);
}
```

`MenuItemRepository::structureUpInsert` (codigo real, `v6/app/Repositories/Eloquent/Prefecture/MenuItem/MenuItemRepository.php:18-44`):

```php
protected function structureUpInsert($request): array
{
    $request['isBlank'] = $request['isBlank'] ?? 0;
    $request['is_mega_menu'] = $request['is_mega_menu'] ?? 0;

    if ($request['type'] === '0'){
        $link = Link::create([
            'title' => $request['name'],
            'name' => $request['name'],
            'alt' => $request['name'],
            'link' => $request['link'],
            'isBlank' => $request['isBlank']
        ]);
        $request['links_id'] = $link->id;
        unset($request['link']);
    } else {
        unset($request['link']);
    }

    if (isset($request['sub_category_contents_id']) && !empty($request['sub_category_contents_id'])) {
        $request['category_contents_id'] = $request['sub_category_contents_id'];
    }

    $fields = array_keys($request);
    return $this->filter_request($request, $fields);
}
```

> Risco: `Link::create` acontece **antes** de qualquer validacao do model e
> **fora** da transacao aberta por `Repository::upInsert` (que so envolve o
> `structureUpInsert`, mas o `Link::create` esta dentro dele — porem o
> `updateOrCreate` do `menu_item` ocorre **fora** da transacao, em
> `v6/app/Repositories/Repository.php:57-60`). Se o `updateOrCreate` falhar,
> o `links` ja foi gravado: orfao.

---

## 9. Presenter (formato de saida do item)

**Arquivo:** `v6/app/Http/Presenters/MenuItemPresenter.php` (herda de
`v6/app/Http/Presenters/DefaultPresenter.php`)

| Metodo | Regra |
|---|---|
| `getLink()` | `type = 0` -> `$this->links->link` (bruto, sem `url()`); `type = 1` -> `url($slug)` se `static_pages_id == 4`, senao `url('acesso-rapido/' . $slug)`; `type = 2` -> depende do `category_contents_id`: `3` -> `url('publicacoes/' . $slug . $content_slug)`, `2` -> `url('legislacao/' . $slug . $content_slug)`, `7` e `32` -> `url('a-instituicao/' . $slug . $content_slug)`, `12` -> `url('acesso-rapido/' . $slug . $content_slug)`, categoria com pai cujo slug e `conselhos` -> `url('portal/' . $slugPai . '/' . $slug)`, senao `url('/' . $slug)`; sem `categoryContent` -> retorna `"#"` |
| `getShortLink()` | Mesma logica, sem `url()` (prefixo relativo) |
| `targetLink()` | `type != 0`: devolve `"target=_blank"` se `isBlank == 1`; `type == 0`: consulta `$this->links->isBlank`; no fim devolve `$this->isBlank` (booleano) |

> O presenter importa `App\Http\Presenters\SectionPresenter`
> (`v6/app/Http/Model/Prefecture/MenuItem.php:5`) — **essa classe nao existe**
> em `v6/app/Http/Presenters/` nem em `v5/app/Http/Presenters/`. O `use` e
> inerte (apenas uma importacao nao resolvida), mas denota codigo morto.

---

## 10. Rotas do portal (leitura) — sem controller proprio

A leitura do menu pelo portal **nao passa por controller**: e feita por *view
composer* registrado em `v6/app/Providers/MenuServiceProvider.php` e
`v6/app/Providers/FooterServiceProvider.php`.

| Composicao | Variavel injetada | Origem |
|---|---|---|
| `portal.layouts.modelo1.layout.*` .. `modelo6.layout.*` | `menuHeader` | `menu_items` de `menus_id = 1`, raiz + 2 niveis |
| `portal.layouts.modelo1.*` .. `modelo6.*` | `menuRapido` | `menu_items` de `menus_id = 3`, `is_active = true` |
| `portal.layouts.modelo1.layout.header` .. `modelo6.layout.header.accessibility` | `atalhos`, `contact_link` | `menu_items` de `menus_id = 4` + `Configuration('contact_link')` |
| `portal.layouts.modelo1.*` .. `modelo5.*`, `components.*` | `menus` (rodape) | `menu_items` de `menus_id = 2` + parte de `menus_id = 1` |

### Evento `BuildingMenu` (menu lateral do CMS)

`v6/app/Providers/MenuServiceProvider.php:24-427` — o `switch` sobre
`$menu->slug` tem 41 casos. O deste modulo e:

```php
case 'menu':
    $event->menu->add(['text' => 'Menus do site', 'url' => '/cms/menu', 'icon' => 'navicon']);
    break;
```

Pre-condicoes para o item aparecer (`v6/app/Providers/MenuServiceProvider.php:45`):

```php
if (has_access("$menu->slug.index") && isset($menu->modulo->status) && $menu->modulo->status) {
```

isto e: a permissao `menu.index` deve existir para o papel, o vinculo
`modulo_id` deve estar preenchido e o modulo deve estar **ativo** na tabela
`modulos`. A ordem do menu lateral vem de `menu_orders` (por `clicks` quando a
configuracao `menu_interativo` e verdadeira, senao por `id`).
