# Páginas Estáticas — Arquitetura

## 1. Visão geral

O módulo tem **tabela, controller e rotas próprios** (ao contrário de
Notícias e Publicações, que reaproveitam `contents`). O conteúdo é uma
página institucional sem data, sem categoria e sem listagem, servida por uma
URL na raiz do domínio.

```
   CMS (/cms/...)                              PORTAL (/)
 ┌───────────────────────────┐        ┌──────────────────────────────────────┐
 │ GET  /cms/pagina-estatica │        │ GET /{slug}          (basepro.php:548)│
 │ POST /cms/pagina-estatica │        │ GET /pagina/{slug}   (web.php:227)   │
 │ POST /cms/ajax-static-    │        └──────────────────┬───────────────────┘
 │      upload-files         │                           │
 │ POST /cms/ajax-static-    │                           ▼
 │      delete-files-gallery │        Portal\PortalController@staticShow
 │ POST /cms/pagina-estatica/│                           │
 │      delete-por-lote      │                           ├── is_external -> Redirect::to(link)
 └───────────┬───────────────┘                           ├── slug reservado -> redirect(rota específica)
             │                                           └── default -> view portal.statics.show
             ▼                                                        │
 StaticPageController                                    ┌─────────────┴─────────────┐
   ├── StaticPageRequest                                 │                           │
   ├── StaticPageRepository (upInsert)                   ▼                           ▼
   ├── StaticPage (model) ──hasMany──▶ Archive       static_pages          archives/links
   └── Link (model)                                   (galeria, PDF inline)   (link externo, vídeo)
```

## 2. Classes envolvidas

| Classe | Caminho | Papel |
|---|---|---|
| `StaticPageController` | `v6/app/Http/Controllers/Prefecture/StaticPageController.php` | CRUD do CMS, upload/exclusão de galeria, exclusão em lote |
| `StaticPageRequest` | `v6/app/Http/Requests/StaticPageRequest.php` | Validação: `title`, `slug` condicional, `link` condicional, extensões de anexo |
| `ConditionalRequiredSlug` | `v6/app/Rules/ConditionalRequiredSlug.php` | Exige `slug` apenas quando a página é editável (não é de sistema) |
| `ConditionalRequiredLink` | `v6/app/Rules/ConditionalRequiredLink.php` | Exige `link` quando `is_external` está marcado |
| `StaticPageRepository` | `v6/app/Repositories/Eloquent/Prefecture/StaticPage/StaticPageRepository.php` | `structureUpInsert()` + `upInsert()` |
| `Repository` (base) | `v6/app/Repositories/Repository.php` | `getModel()`, `dataTrait()`, `destroy()`, `destroyAll()` |
| `StaticPage` | `v6/app/Http/Model/Prefecture/StaticPage.php` | Modelo da tabela `static_pages` (`SoftDeletes`) |
| `Archive` | `v6/app/Http/Model/Prefecture/Archive.php` | Anexos e galeria (`static_pages_id`) |
| `Link` | `v6/app/Http/Model/Prefecture/Link.php` | Link externo / vídeo (`static_pages_id`) |
| `MenuItem` | `v6/app/Http/Model/Prefecture/MenuItem.php` | Item de menu do portal que aponta para a página |
| `PortalController` | `v6/app/Http/Controllers/Portal/PortalController.php` | `staticShow()` — renderização pública |
| `DatatableController` | `v6/app/Http/Controllers/Prefecture/DatatableController.php` | `get_paginas_estaticas()` — datatable do CMS |
| `ContentController` | `v6/app/Http/Controllers/Prefecture/ContentController.php` | **Não** é usado por este módulo (apenas pelo `ContentController@genericDestroy` de conteúdo) |

## 3. Fluxo — criação no CMS

1. `GET /cms/pagina-estatica/create` -> `StaticPageController@create`
   (`v6/app/Http/Controllers/Prefecture/StaticPageController.php:85-91`):
   ```php
   public function create()
   {
       $title = 'Página Estática';
       $action = 'Cadastrar';
       menuClicks('pagina-estatica');
       return view('prefecture.static-pages.create', compact('title', 'action'));
   }
   ```
2. `POST /cms/pagina-estatica` -> `store(StaticPageRequest $request)`
   (`v6/app/Http/Controllers/Prefecture/StaticPageController.php:125-180`):
   - gera a slug (`Str::slug($title, '-')`) e faz `merge(['slug' => $slug])`;
   - dentro de `DB::transaction`:
     - `$this->staticPageRepository->upInsert($request)`;
     - `$this->extractedToArchive($request, $staticPage)` (anexos);
     - log `activity()`;
     - criação do `Link` quando há `link` no request;
   - redireciona para `editar-pagina` (com âncora `#galeria`) quando há
     galeria, ou para `pagina-estatica.index`.
3. O `upInsert` delega ao repository:
   ```php
   // v6/app/Repositories/Eloquent/Prefecture/StaticPage/StaticPageRepository.php
   protected function structureUpInsert($request, $update = null): array
   {
       $request['galery'] = data_get($request, 'galery') && $request['galery'] === 'on';
       if (!isset($request['slug'])) {
           $request['slug'] = $this->slugfy(str_limit($request['title'], 50));
       }
       $dataHora = date('dmyHis');
       $dataHoraNumerica = preg_replace('/[^0-9]/', '', $dataHora);
       if ($request['slug']) {
           $static_page = StaticPage::where('slug', $request['slug'])->first();
           $category_contents = CategoryContent::where('slug', $request['slug'])->first();
           // Caminho da URL após o domínio
           $caminho = $_SERVER['REQUEST_URI'];
           $partesDaUrl = explode("/", $caminho);
           $id =  end($partesDaUrl);

           if ((isset($static_page['id']) && $static_page['id'] != null && $static_page['id'] != $id) || isset($category_contents)) {
               $request['slug'] = $request['slug'] . '-' . $dataHoraNumerica;
           }
       }

       return $request;
   }
   ```
4. O `upInsert` faz `updateOrCreate(['id' => $id], $data)` — como `id` não
   está no `$fillable`, o `id` nunca é gravado (o banco usa a sequência).

## 4. Fluxo — renderização pública

`PortalController::staticShow($slug)`
(`v6/app/Http/Controllers/Portal/PortalController.php:276-310`):

```php
public function staticShow($slug)
{
    $null = false;
    $content = StaticPage::where('slug', $slug)->first();

    if (!empty($content)) {
        $galery = ($content->galery == 1) ? Archive::where('static_pages_id', $content->id)->where('highlight', null)->get() : false;
        $archives = Archive::where('static_pages_id', $content->id)->where('highlight', null)->get();
        $view_pdf = Archive::where('static_pages_id', $content->id)->where('view_pdf', true)->first();

        $content->increment('clicks');
        $title = $content->title;

        if ($content->is_external == true) {
            $url = $content->linksUnique()->pluck('link')[0];
            return Redirect::to($url);
        }

        switch ($slug) {
            case 'contato': return redirect(route('contato.index'));
            case 'ouvidoria': return redirect(route('ouvidoria.index'));
            case 'manual-de-navegacao': return redirect(route('acesso-rapido.index', 'manual-de-navegacao'));
            case 'solicitar-informacao': return redirect(route('acesso-rapido.index', 'solicitar-informacao'));
            case 'acompanhe-sua-solicitacao': return redirect(route('acesso-rapido.index', 'acompanhe-sua-solicitacao'));
            case 'relatorio-estatistico': return redirect(route('relatorio-estatistico'));
            default:
                $modelo = $this->getTemplateActived();
                $linkVideo = isset($content->linksUnique) ? embedVideo($content->linksUnique->link) : null;
                return view('portal.statics.show', compact('title', 'content', 'null', 'galery', 'modelo', 'archives', 'view_pdf', 'linkVideo'));
        }
    }
    $title = 'Página não encontrada';
    $null = true;
    return view('portal.statics.show', compact('title', 'null'));
}
```

Pontos de atenção:

- `increment('clicks')` acontece **antes** do `switch` e **antes** do
  redirecionamento externo — ou seja, a visita é contada mesmo quando a
  página é apenas um atalho;
- o `switch` de slugs reservados tem precedência sobre a renderização;
- quando a página não existe, a mesma view é renderizada com `$null = true`
  e status **200** (a view exibe "PÁGINA NÃO ENCONTRADA") — não há 404.

## 5. Fluxo — exclusão

| Caminho | Controller | Regra |
|---|---|---|
| `DELETE /cms/pagina-estatica/{id}` | `destroy($id)` (`:361-398`) | Só exclui se `status_create == 0`; remove `menu_items` |
| `POST /cms/pagina-estatica/delete-por-lote` | `deleteByLote($request)` (`:42-66`) | Filtra `status_create = false OR NULL`; usa `destroyAll()` |

Ambos usam soft delete (`StaticPage` tem `SoftDeletes`).

## 6. Fluxo — galeria

| Operação | Endpoint | Controller |
|---|---|---|
| Upload | `POST /cms/ajax-static-upload-files` | `ajaxUploadFiles` (`:202-232`) |
| Exclusão | `POST /cms/ajax-static-delete-files-gallery` | `ajaxDeleteFilesOfGallery` (`:182-200`) |

```php
public function ajaxUploadFiles(Request $request)
{
    $content = StaticPage::find($request->get('content'));

    $extension = $request->archive_galery->extension();
    $name = "img_" . date('YmdHi') . str_random(4) . "." . $extension;

    $archive = [
        'name' => $name,
        'title' => $name,
        'extension' => $extension,
        'static_pages_id' => $content->id,
    ];

    $insertArchive = Archive::create($archive);
    $insertArchive->path = "storage/statics/$content->slug/galeria/$name";
    $insertArchive->save();
    $request->archive_galery->storeAs("statics/$content->slug/galeria/", $name);

    //Registrando o log
    activity()->performedOn($insertArchive)
        ->causedBy(\Sentinel::getUser())
        ->withProperty('Detalhe', 'Foi criado um arquivo: Id arquivo:' . $insertArchive->id . ',Nome:' . $insertArchive->name . ',Título:' . $insertArchive->title . ',Extensão:' . $insertArchive->extension . ',Tipo:' . $insertArchive->type . ',Criado por:' . Sentinel::getUser()->name)
        ->log('Cadastro de arquivo');
}
```

> A extensão vem de `$request->archive_galery->extension()` (método do
> `UploadedFile`), e o `title` é o nome gerado — não há metadados de
> título/descrição no upload por Ajax.

## 7. Dependências externas

| Pacote | Uso |
|---|---|
| `laravel/framework` (`UploadedFile`, `Str`, `Storage`) | Manipulação de arquivos e slug |
| `spatie/laravel-activitylog` | Trilha `activity()` |
| `cartalyst/sentinel` | Identidade do operador |
| `laracasts/presenter` | Não usado neste módulo (o `StaticPage` não usa `PresentableTrait`) |

## 8. Middleware

| Camada | Middleware |
|---|---|
| Portal (`/pagina/{slug}`) | `check.site.manutention` (`v6/routes/web.php:7`) |
| Portal (`/{slug}`, basepro) | apenas `web` (`v6/app/Providers/RouteServiceProvider.php:85-89`) — **sem** `check.site.manutention` |
| CMS | `check.logged` (grupo `/cms`) |

> A rota `/{slug}` de `basepro.php` é registrada **fora** do grupo `/cms` e
> **fora** do middleware de manutenção: páginas estáticas continuam
> acessíveis mesmo com o portal em manutenção.

## 9. Pontos de extensão

- **Nova página estática**: criar no CMS (slug na raiz) e, se quiser,
  adicioná-la ao menu (`menu_items.static_pages_id`).
- **Novo slug reservado**: acrescentar um `case` no `switch` de
  `PortalController::staticShow`.
- **Nova galeria**: usar o componente
  `prefecture.contents.components.galerias.index` no formulário.
