# Publicações — Arquitetura

## 1. Visão geral

Publicações é um **caso de uso do módulo genérico de conteúdos**, igual ao
módulo Notícias, mas com um controller de portal próprio
(`PublicationController`) e uma camada de **exportação** (`ExportsContentTrait`)
que não existe para Notícias.

```
      CMS (/cms/...)                        PORTAL (/)                API (/api/v1)
 ┌────────────────────────┐        ┌────────────────────────┐   ┌───────────────────────┐
 │ GET /cms/conteudos/    │        │ GET /publicacoes       │   │ GET /api/v1/          │
 │      publicacoes       │        │ GET /publicacoes/      │   │      publicacoes      │
 │ ContentController@     │        │      export            │   │ Swagger\V1\Portal\    │
 │ genericIndex           │        │ GET /publicacoes/{cat} │   │ PublicationController │
 │                        │        │ GET /publicacoes/{c}/{d}│  │                       │
 │ GET /cms/datatable/    │        │ PublicationController  │   └───────────┬───────────┘
 │      publicacoes       │        └───────────┬────────────┘               │
 │ DatatableController@   │                    │                            │
 │      get_publicacoes   │                    │                            │
 └───────────┬────────────┘                    ▼                            ▼
             │                     ExportsContentTrait            PublicationResource
             ▼                            │
   App\Prefecture\Content ──belongsTo──▶ App\Prefecture\CategoryContent (raiz id 3)
             │                                    │
             ├──hasMany──▶ Archive (anexos)        └──hasMany──▶ CategoryContent (filhas 13..21)
             └──hasOne───▶ Link (não usado aqui)
```

## 2. Classes envolvidas

| Classe | Caminho | Papel |
|---|---|---|
| `PublicationController` | `v6/app/Http/Controllers/Portal/PublicationController.php` | Controller de portal: `getIndex`, `showPublication`, `export`, `viewPublication` |
| `ContentController` | `v6/app/Http/Controllers/Prefecture/ContentController.php` | CRUD genérico do CMS; o caso `publicacoes` monta as subcategorias no formulário |
| `CategoryContentController` | `v6/app/Http/Controllers/Prefecture/CategoryContentController.php` | CRUD das subcategorias de Publicações |
| `DatatableController` | `v6/app/Http/Controllers/Prefecture/DatatableController.php` | `get_publicacoes()` monta a datatable do CMS em PHP |
| `Content` | `v6/app/Http/Model/Prefecture/Content.php` | Modelo da tabela `contents` |
| `CategoryContent` | `v6/app/Http/Model/Prefecture/CategoryContent.php` | `getAllDescendants()` — árvore de subcategorias da raiz 3 |
| `Archive` | `v6/app/Http/Model/Prefecture/Archive.php` | Anexos do documento; `view_pdf` marca o PDF inline |
| `ExportsContentTrait` | `v6/app/Http/Traits/ExportsContentTrait.php` | Gera a exportação em PDF (DomPDF) e XLS/CSV/TXT (Maatwebsite) |
| `ContentsExport` | `v6/app/Exports/ContentsExport.php` | `FromCollection` usado por `Maatwebsite\Excel` |
| `ContentRequest` | `v6/app/Http/Requests/ContentRequest.php` | Regras do caso `publicacoes` |
| `PublicationController` (Swagger) | `v6/app/Http/Controllers/Swagger/V1/Portal/PublicationController.php` | API de dados abertos |
| `PublicationResource` | `v6/app/Http/Resources/Swagger/V1/Portal/PublicationResource.php` | Formato de saída da API |
| `LegislationController` | `v6/app/Http/Controllers/Portal/LegislationController.php` | Módulo irmão (Legislação) — mesma estrutura, raiz 2 |

## 3. Fluxo da requisição — listagem no portal

1. `GET /publicacoes` -> rota `publicacoes.index`
   (`v6/routes/web.php:38`) -> `PublicationController@getIndex`
   (`v6/app/Http/Controllers/Portal/PublicationController.php:19-27`):

   ```php
   public function getIndex()
   {
       $categoryFirst = CategoryContent::whereHas('categoryContents', function ($query) {
           $query->where('slug', '=', 'publicacoes');
       })->orderBy('order')->first();

       return redirect()->route('publicacoes.show', $categoryFirst->slug);
   }
   ```

   Redireciona para a **primeira subcategoria** ordenada por `order`. Se
   nenhuma existir, `$categoryFirst` é `null` e a chamada a `->slug` gera
   erro 500.

2. `GET /publicacoes/{slug}` -> `showPublication`
   (`v6/app/Http/Controllers/Portal/PublicationController.php:28-113`).
3. A categoria alvo é resolvida a partir da **descendência** da raiz:

   ```php
   $getAllDescendants = CategoryContent::query()->where('slug', '=', 'publicacoes')->first()->getAllDescendants();
   $publication_category = CategoryContent::where('slug', 'publicacoes')->where('category_contents_id', null)->first()->id;
   $categories = CategoryContent::allDescendants()->whereHas('categoryContents', function ($query) {
       $query->where('slug', '=', 'publicacoes');
   })->orderBy('order')->get();
   $category = CategoryContent::query()->find(data_get($getAllDescendants->where('slug', $slug)->first(), 'id'));

   if (empty($category) && !isset($category))
       return redirect('/publicacoes');
   ```

4. A listagem paginada (12 por página):

   ```php
   $contents = Content::where('publication_date', '<=', $current_date)
       ->select('title', 'text', 'publication_date', 'slug', 'category_contents_id', 'updated_at', 'created_at')
       ->where('category_contents_id', $category->id)
       ->orderBy('publication_date', 'desc')->orderBy('id', 'desc')->paginate(12);
   ```

5. Os anos disponíveis vêm de um `DB::select` com SQL montado por
   concatenação (`PublicationController.php:46`):

   ```php
   $anos = DB::select("select extract(year from publication_date) as ano from contents WHERE category_contents_id = {$category->id} AND deleted_at IS NULL GROUP BY ano order by ano desc;");
   ```

## 4. Fluxo da requisição — documento

1. `GET /publicacoes/{slug1}/{slug2}` -> `viewPublication`
   (`v6/app/Http/Controllers/Portal/PublicationController.php:153-171`):

   ```php
   public function viewPublication($slug, $slug2)
   {
       $null = false;
       $current_date = date('Y-m-d H:i:s');
       $content = Content::where('slug', $slug2)->first();
       if ($content->publication_date > $current_date) {
           return redirect('/publicacoes');
       } else {
           $archives = Archive::where('contents_id', $content->id)->where('highlight', null)->get();
           $view_pdf = Archive::where('contents_id', $content->id)->where('view_pdf', true)->first();

           $title = $content->title . ' | ' . $content->categoryContents->name;
           $clicks = $content->clicks + 1;
           $content->update(['clicks' => $clicks]);
       }

       return view('portal.layouts.' . $this->getTemplateActived() . '.publicacoes.view', compact('title', 'content', 'null', 'archives', 'view_pdf'));
   }
   ```

   Observações: a busca por `slug2` **não** filtra por
   `category_contents_id` (a mesma slug em outra categoria abriria o
   documento errado); `status` não é verificado (documento em rascunho é
   acessível por URL); `$content` pode ser `null` -> erro 500.

2. O template ativo vem de `GetInformationsPrefectureTrait::getTemplateActived()`.

## 5. Fluxo da requisição — exportação

1. `GET /publicacoes/export` -> rota `publicacoes.export`
   (`v6/routes/web.php:39`) -> `PublicationController@export`
   (`v6/app/Http/Controllers/Portal/PublicationController.php:115-152`).
2. A categoria vem de `$request->sub_category`; se não existir na
   descendência da raiz 3 -> `abort(404)`.
3. Filtros opcionais: `ano`, `mes`, `texto` (todos com `filled()`).
4. O `exportContents()` decide o formato:

   ```php
   // v6/app/Http/Traits/ExportsContentTrait.php
   protected function exportContents($data, $type, $filename, $reportTitle)
   {
       if ($type == 'pdf') {
           $primaryColor = Configuration::where('key', 'cor_1')->value('value') ?: '#1075ad';
           $pdf = \PDF::loadView('exports.contents', compact('data', 'reportTitle', 'primaryColor'))->setPaper('a4', 'landscape');
           return $pdf->download($filename . '.pdf');
       }

       $export = new ContentsExport($data);

       if ($type == 'xls') {
           return \Excel::download($export, $filename . '.xlsx');
       } elseif ($type == 'txt') {
           return \Excel::download($export, $filename . '.txt', \Maatwebsite\Excel\Excel::TSV);
       }

       return \Excel::download($export, $filename . '.csv');
   }
   ```

5. Nome do arquivo: `publicacoes_<slug-da-categoria>_<Ymd_His>`.

## 6. Fluxo da requisição — CMS

1. `GET /cms/conteudos/publicacoes` -> `listar-conteudo` ->
   `ContentController@genericIndex` -> `returnIndex('Publicações', 'publicacoes')`
   -> view `prefecture.contents.publicacoes.index`.
2. `GET /cms/conteudos/cadastrar/publicacoes` -> `genericCreate` ->
   `genericGenerate` (`v6/app/Http/Controllers/Prefecture/ContentController.php:249-252`):

   ```php
   if ($slug == 'publicacoes'){
       $id_category = $this->categoryContent->where('slug', 'publicacoes')->first()->id;
       return $this->returnData('Publicações', $slug, null, ["categoryContentSelect" => $this->getAllItemsToCategoryContents($id_category, true)], $id);
   }
   ```

   `getAllItemsToCategoryContents($id, $withParents = true)` monta a lista de
   subcategorias com o nome do pai prefixado
   (`CategoryContent::getAllDescendants($withParents)`).
3. `POST /cms/conteudo` -> `ContentController@store` — o caso `publicacoes`
   do `ContentRequest` exige `layout` numérico e `category_contents_id`
   validado por `SelectRule('categoria')`.
4. Os anexos são gravados em
   `storage/content/publicacoes/{slug-da-categoria}/{id}/arquivos/`.

## 7. Fluxo — datatable do CMS

`DatatableController::get_publicacoes`
(`v6/app/Http/Controllers/Prefecture/DatatableController.php:80-99`):

```php
public function get_publicacoes()
{
    $publicacoes = CategoryContent::query()->where('slug', 'publicacoes')->first();
    $getAllDescendants = $publicacoes->getAllDescendants()->pluck('id');
    $legislacaoContent = Content::whereIn('category_contents_id', $getAllDescendants)->orderByDesc('publication_date')->get();
    $data = [];
    $legislacaoContent->each(function ($item, $index) use(&$data){
        $aux = new stdClass();
        $aux->value = $index + 1;
        $aux->titulo = data_get($item, 'title');
        $aux->data_publicacao = data_get($item, 'publication_date') ? data_get($item, 'publication_date')->format('d/m/Y H:i:s') : '';
        $categoria = CategoryContent::find(data_get($item, 'category_contents_id'));
        $aux->categoria = $categoria ? $categoria->name : '';
        $aux->categoria_slug = $categoria ? $categoria->slug : '';
        $aux->slug = data_get($item, 'slug');
        $aux->id = data_get($item, 'id');
        array_push($data, $aux);
    });
    return DataTables::of($this->struct_views($data, 'publicacoes'))->make(true);
}
```

Observações: não há paginação nem filtro no banco (tudo em memória); a
variável local chama-se `$legislacaoContent` (cópia do método irmão); não há
filtro de `status` nem de `deleted_at` explícito (o `SoftDeletes` do modelo
aplica `deleted_at is null`).

## 8. Dependências externas

| Pacote | Uso |
|---|---|
| `maatwebsite/excel` | `ContentsExport` para XLS/CSV/TXT |
| `barryvdh/laravel-dompdf` | `PDF::loadView('exports.contents')` |
| `yajra/laravel-datatables` | `DatatableController::get_publicacoes` |
| `laracasts/presenter` | `ContentPresenter` nas views do portal |
| `intervention/image` | Compressão de anexos e do highlight |
| `spatie/laravel-activitylog` | Trilha de auditoria das escritas |
| `cartalyst/sentinel` | Identidade do operador no CMS |

## 9. Middleware

| Camada | Middleware |
|---|---|
| Portal | `check.site.manutention` (`v6/routes/web.php:7`) |
| CMS | `check.logged` (grupo `/cms`) + `check.route.permission` (`ContentController.php:60`) |
| API | `api` (`v6/app/Providers/RouteServiceProvider.php:66-72`) |

## 10. Publicações x Legislação

| Aspecto | Publicações (raiz 3) | Legislação (raiz 2) |
|---|---|---|
| Controller de portal | `PublicationController` | `LegislationController` |
| Rotas | `publicacoes.index/show/view/export` | `legislacao.index`, `conteudo.legislacao.list/view/export`, `conteudo.legislacao.fill` |
| Subcategorias | `documentos`, `editais`, `horarios-funcionarios`, `manuais`, `mensario-oficial`, `concurso-publico`, `campanhas`, `diario-oficial`, `portal-do-contribuinte` | `constituicao-federal`, `codigo-tributario`, `decretos` |
| Campos | `layout` (barra lateral), anexos | "Link Permanente" e referência ao ato normativo |
| Filtro por ano/mês/texto | sim | sim (mesma estrutura, controller próprio) |
| Exportação | `GET /publicacoes/export` (v6) | `GET /legislacao/export` (v6) |
| View SQL | `publicacoes` (não usada pelo CMS) | `legislacao` (não usada pelo CMS) |
