# Temas e Cores — Rotas, Controllers e Permissões

> **Slug do módulo:** `temas-e-cores` (tabela `modulos`, id 2)

## 1. Rotas do CMS (`v6/routes/basepro.php`, grupo `prefix => 'cms'`, `before => 'csrf'`)

| Linha | Método | URI | Nome | Controller@ação | Middleware |
|---|---|---|---|---|---|
| 241 | GET | `cms/configuracoes/editar/{slug}` | `edit.configuracoes.by.category` | `Prefecture\ConfigurationController@editByCategory` | `check.logged` |
| 246 | POST | `cms/update/configuration` | `update.configurations.by.category` | `Prefecture\ConfigurationController@updateByCategory` | — (guarda interna por `has_access`) |
| 139 | POST | `cms/ajax/remove-arquivo-configuracao` | `remove.arquivo.configuracao` | `Prefecture\ConfigurationController@removeArquivo` | grupo `ajax` |
| 358 | POST | `cms/update/modelo-personalizavel` | `save.layout` | `Prefecture\LayoutController@save_layout` | — |
| 359 | POST | `cms/order/modelo-personalizavel` | *(sem nome)* | `Prefecture\LayoutController@orderLayout` | — |
| 360 | POST | `cms/upload/modelo-personalizavel` | `upload.css` | `Prefecture\LayoutController@uploadFile` | — |
| 361 | GET | `cms/download/modelo-personalizavel` | `download.css` | `Prefecture\LayoutController@downloadFile` | — |
| 362 | GET | `cms/editar/modelo-personalizavel` | `edit.layout` | `Prefecture\LayoutController@edit` | — |
| 363 | POST | `cms/update-module-status` | *(sem nome)* | `Prefecture\LayoutController@updateModuleStatus` | — |
| 451 | GET | `cms/datatable/configuracoes` | `datatable.configuracoes` | `Prefecture\DatatableController@get_configuracoes` | — |
| 37 | GET | `cms/modulos` | `modulos.index` | `Admin\ModulosController@index` | `check.logged` |
| 38 | POST | `cms/modulos` | `modulos.update` | `Admin\ModulosController@update` | `check.logged` |

Trecho real (`v6/routes/basepro.php`):

```php
Route::get('configuracoes/editar/{slug}', ['uses' => 'Prefecture\ConfigurationController@editByCategory'])->name('edit.configuracoes.by.category')->middleware('check.logged');
Route::post('update/configuration', 'Prefecture\ConfigurationController@updateByCategory')->name('update.configurations.by.category');

//Modelo5 personalizavel
Route::post('update/modelo-personalizavel', 'Prefecture\LayoutController@save_layout')->name('save.layout');
Route::post('order/modelo-personalizavel', 'Prefecture\LayoutController@orderLayout');
Route::post('upload/modelo-personalizavel', 'Prefecture\LayoutController@uploadFile')->name('upload.css');
Route::get('download/modelo-personalizavel', 'Prefecture\LayoutController@downloadFile')->name('download.css');
Route::get('editar/modelo-personalizavel', 'Prefecture\LayoutController@edit')->name('edit.layout');
Route::post('update-module-status', 'Prefecture\LayoutController@updateModuleStatus');
```

> **Risco:** as 6 rotas do modelo 5 **não têm `check.logged` nem verificação de permissão**
> no controller (`LayoutController` não chama `has_access()`). A rota `edit.layout` é a única
> protegida por UI (botão visível apenas com `has_access('modelo-personalizavel.*')`).

## 2. Rotas públicas do portal (`v6/routes/web.php`)

| Linha | Método | URI | Nome | Controller@ação | Retorno |
|---|---|---|---|---|---|
| 207 | GET | `/getColors` | `get-colors` | `Portal\HelperController@getColors` | JSON com `cor_1`..`cor_4` (`key`, `value`). |
| 208 | GET | `/getPrivacyBar` | `get-privacy-bar` | `Portal\HelperController@getPrivacyBar` | JSON com `barra-privacidade-expiracao` (módulo `lgpd`). |
| — | GET | `/` | — | `Portal\PortalController@index` | Renderiza `portal.layouts.{template}.home.index`. |

```php
// v6/app/Http/Controllers/Portal/HelperController.php
public function getColors()
{
    return Configuration::whereIn('key', ['cor_1', 'cor_2', 'cor_3', 'cor_4'])->orderBy('id')->get(['key', 'value']);
}
```

## 3. Controller de configuração — `case 'temas'`

`v6/app/Http/Controllers/Prefecture/ConfigurationController.php`, `editByCategory()`:

```php
$reference = $acess = $slug;

if ($slug == 'configuracoes')
    $reference = "site";
if ($slug == 'temas')
    $acess = 'tema-do-portal';

$configurations = $this->getConfiguration($reference);

$category = CategoryConfiguration::where('slug', $reference)->first();
$states = DB::table('states')->get();

if (!has_access(["$acess.edit"])) {
    flash('Você não tem permissão para acessar este conteúdo')->warning();
    return redirect()->route('dashboard.index');
}

switch ($slug) {
    /* trecho omitido no arquivo original */
    case 'temas':
        $temas_lte = [
            ['key' => 'blue', 'value' => 'Azul'],
            ['key' => 'black', 'value' => 'Preto'],
            ['key' => 'purple', 'value' => 'Roxo'],
            ['key' => 'green', 'value' => 'Verde'],
            ['key' => 'red', 'value' => 'Vermelho'],
            ['key' => 'yellow', 'value' => 'Amarelo'],
            ['key' => 'blue-light', 'value' => 'Azul Light'],
            ['key' => 'black-light', 'value' => 'Preto Light'],
            ['key' => 'green-light', 'value' => 'Verde Light'],
            ['key' => 'red-light', 'value' => 'Vermelho Light'],
            ['key' => 'yellow-light', 'value' => 'Amarelo Light'],

        ];

        $templatesPortal = [
            ['key' => 'modelo1', 'value' => 'Modelo 1', 'img' => '/assets/images/modelo1-min.jpg', 'modal' => '/assets/images/modelo01-modal.jpg'],
            ['key' => 'modelo2', 'value' => 'Modelo 2', 'img' => '/assets/images/modelo2-min.jpg', 'modal' => '/assets/images/modelo02-modal.jpg'],
            ['key' => 'modelo3', 'value' => 'Modelo 3', 'img' => '/assets/images/modelo3-min.jpg', 'modal' => '/assets/images/modelo03-modal.jpg'],
            ['key' => 'modelo4', 'value' => 'Modelo 4', 'img' => '/assets/images/modelo4-min.jpg', 'modal' => '/assets/images/modelo04-modal.jpg'],
            ['key' => 'modelo5', 'value' => 'Modelo Personalizável', 'img' => '/assets/images/modelo05-min.jpg', 'modal' => '/assets/images/modelo05-modal.jpg'],
            ['key' => 'modelo6', 'value' => 'Modelo 6', 'img' => '/assets/images/modelo6-min.png', 'modal' => '/assets/images/modelo6.jpg'],
        ];

        $title = 'Configurações';

        $this->configurationLog('Acessou edição de configuração de temas');
        return view("prefecture.configurations.{$slug}.edit", compact("configurations", 'title', 'category', 'temas_lte', 'templatesPortal'));
```

## 4. Controller de configuração — gravação

`updateByCategory()` (mesmo arquivo, ≈ linhas 233-420):

```php
$category = CategoryConfiguration::find($request->get('category_configurations_id'));
$dataForm = $request->except('_token', 'category_configurations_id', 'display');
    /* trecho omitido no arquivo original */
$size = count($dataForm);
$keys = array_keys($dataForm);
if (isset($keys[0]) && $keys[0] == 'layout') {
    $templates = ['modelo1', 'modelo2', 'modelo3', 'modelo4', 'modelo5', 'modelo6'];
    if (!in_array($dataForm['layout'], $templates)) {
        flash('Por favor, selecione um layout entre as opções exibidas.')->error();
        return redirect()->back();
    }
}
    /* trecho omitido no arquivo original */
foreach ($dataForm as $key => $value) {
    $config = Configuration::where('key', $key)->first();
    if (!$config) continue;
    if ($request->hasFile($key)) {
        $file = $request->file($key);
        $name = $this->getFileName($file, "logo");
    /* trecho omitido no arquivo original */
        if ($key != 'imagem-acesso-rapido' && $key !== 'background'){
            // redimensionamento para 275x75 (Intervention)
        }
        $image->save(storage_path('app/public/configuracoes/' . $name), 100);
        $config->value = 'configuracoes/' . $name;
    } else {
        $config->value = $value;
    }
    $config->save();
    activity()->performedOn($config)->causedBy(Sentinel::getUser())
        ->withProperty('Detalhe', 'Foi atualizada uma configuração: Id:' . $config->id . ...)
        ->log('Edição de configuração');
    Cache::pull('structureType');
}
flash('Configurações salvas com sucesso')->success();
```

Regras relevantes:

- A **chave `layout` só é validada se for o primeiro campo enviado** (`$keys[0] == 'layout'`).
  Como o formulário de temas a_envia primeiro, funciona na prática; mas um POST com `cor_1`
  antes de `layout` escaparia da validação.
- `background` **não é redimensionado** (exceção explícita no `if`), é gravado em
  `storage/app/public/configuracoes/` e referenciado como `configuracoes/<nome>`.
- Gravação por chave: `Configuration::where('key',$key)->first()` — chaves duplicadas
  (não há índice único) fariam apenas a primeira ser gravada.
- O `echo "JSON file created successfully..."` do bloco `site` **não** afeta `temas`
  (o `if ($category->slug == 'site')` não entra), mas o `updateByCategory` é compartilhado.

## 5. Controller do modelo 5 — `LayoutController`

`v6/app/Http/Controllers/Prefecture/LayoutController.php`:

| Método | Entrada | Efeito |
|---|---|---|
| `edit()` | — | Lê `Layout::whereIn('key',['preview','tags'])` (HTML) e `Layout::where('type','element')->where('style','!=',null)` (estilos). Resolve `secretarias-grid` (com `legacyMap` de valores antigos `0..3`), busca a categoria raiz `transparencia-fiscal` em `category_contents` e os `menus`. Devolve a view `prefecture.configurations.modelo5.edit`. |
| `save_layout()` | `html`, `htmlTags`, campos `input-*` | Grava `preview`/`tags` na coluna `value`; `input-title-ultimas-noticias` → `title` de `ultimas-noticias`; `input-title-more-popular` → `title` de `more-popular`; `input-title-carousel-noticias` → `title` de `carousel-noticias`; `input-columnsTransparenciaFiscalGrid` → `columns` de `transparencia-fiscal-grid`; `input-columnsTransparenciaFiscal` → `columns` de `transparencia-fiscal`; demais `input-*` → `style` da chave homônima. |
| `orderLayout()` | agrupamento `{grupo: [chaves]}` | Reescreve `order`, `status` (`0` para o grupo `tags`, `1` nos demais) e `section`. |
| `updateModuleStatus()` | `module`, `subId` | `updateOrInsert` em `layouts` com `key='secretarias-grid'`, `value="{module}:{subId}"`, `type='config'`, `status=0`, `section=''`, `order=0`. |
| `uploadFile()` | `file_css` | Salva como `css/modelo5.<ext>` (disco `local`, isto é `storage/app/css/`). |
| `downloadFile()` | — | Baixa `storage/app/public/css/modelo5.css`; se ausente, `modelo5-example.css`. |

Trecho real de `orderLayout()`:

```php
foreach ($request->all() as $key => $value) {
    foreach ($value as $key2 => $value2) {
        Layout::where(['key' => str_replace('-preview', '', $value2)])->update([
            'order' => $key2,
            'status' => ($key === 'tags') ? 0 : 1,
            'section' => $key,
        ]);
    }
}
```

## 6. Form Request / validação

`v6/app/Http/Requests/ConfigurationRequest.php` — `authorize()` retorna `true` (sem verificação
de permissão; a guarda real está no controller).

Regras dinâmicas na rota `update.configurations.by.category`:

```php
for ( $i = 0; $i < $size; $i++ ) {
    $config = Configuration::where('key', $keys[$i])->first();
    if (!empty($config)) {
        if ($config->notnull == true) {
            $required = 'required';
            if ($config->max_input != ''){
                $max = '|max:'.$config->max_input."|";
            } else { $max = ''; }
            if ($config->max_input != ''){
                $min = '|min:'.$config->min_input.'';
            } else { $min = ''; }
        } else { $required = ''; $max = ''; $min = ''; }
    }
    $rules += [ $keys[$i] => $required.$max.$min];
}
```

Efeito no módulo `temas`:

| Campo | Regra gerada | Mensagem |
|---|---|---|
| `cor_1`..`cor_4` | `required\|max:50\|min:3` | "O campo Cor Principal é obrigatório." / "não pode execer o limite de 50 caracteres." / "deve ser preenchido com no mínimo 3 caracteres." |
| `layout` | vazia (não há `notnull`) | — (a validação real é a lista de modelos no controller) |
| `cor_cms` | `required` (notnull = true, sem max) | "O campo Tema do CMS é obrigatório." |
| `background` | vazia (`notnull = false`) | — |
| `background_fixed` / `background_repeat` | vazia | — |

`messages()` repete o padrão N+1: uma query por campo enviado.

## 7. Permissões exigidas

| Ação | Permissão | Origem |
|---|---|---|
| Ver o menu "Temas & Cores" | `has_access(['tema-do-portal.index'])` **e** `$modulos['temas-e-cores']` | `v6/resources/views/vendor/adminlte/page.blade.php` |
| Abrir a tela | `has_access(['tema-do-portal.edit'])` | `ConfigurationController@editByCategory` |
| Ver a grade de modelos | `has_access(['layouts'])` | `select_layout.blade.php` |
| Ver o botão de edição do modelo 5 | `has_access('modelo-personalizavel.*')` | `select_layout.blade.php` |
| **Salvar** | `has_access(['configuracoes.edit'])` | `ConfigurationController@updateByCategory` — **permissão de outro módulo** |

`has_access()` (`v6/app/Library/Helper/appHelpers.php:22`): `false` sem login; `true` para o
papel `super-admin`; senão `Sentinel::check()->hasAnyAccess($access)` (array) ou
`hasAccess([$access])` (string).

Middleware `check.route.permission` mapeia verbos (`edit`→`update`, `create`→`store`,
`delete`→`destroy`) — não é aplicado às rotas deste módulo.

## 8. Saída JSON / presenters

| Recurso | Formato |
|---|---|
| `GET /getColors` | Coleção Eloquent serializada: `[{key: "cor_1", value: "#1075ad"}, ...]`, ordenada por `id`. |
| `POST /cms/order/modelo-personalizavel` | `response()->json(true)` / `json(false)` em erro. |
| `POST /cms/update-module-status` | `{"success": true}` ou `{"success": false, "error": "..."}`. |
| `LayoutPresenter` (`v6/app/Http/Presenters/LayoutPresenter.php`) | Apresentador do model `Layout`, usado via `PresentableTrait`. |
