# Mais Conteúdos — Arquitetura

## 1. Visão geral

"Mais Conteúdos" não é um módulo isolado: é um **recorte semântico** da tabela
`category_contents`materializado em três lugares da aplicação:

1. **Menu do CMS** — `MenuServiceProvider` monta o item "Mais Conteúdos" a partir das
   categorias de topo com `status_create = 0`.
2. **CRUD de categorias** — `CategoryContentController` cria/edita/exclui essas
   categorias (e suas subcategorias).
3. **CRUD de conteúdos + portal** — `ContentController` publica conteúdos nelas e
   `PortalController@contentList` os exibe no portal público.

```
                      ┌──────────────────────────────────────────┐
                      │  category_contents (tabela única)         │
                      │  status_create = 0  →  "Mais Conteúdos"  │
                      │  status_create = 1  →  categorias de      │
                      │                          sistema           │
                      └───────────┬──────────────────────────────┘
                                  │
        ┌─────────────────────────┼─────────────────────────┐
        ▼                         ▼                         ▼
┌───────────────────┐   ┌────────────────────┐   ┌────────────────────────┐
│ MenuServiceProvider│   │CategoryContentController│ │  PortalController@    │
│ (AdminLTE sidebar) │   │ (CRUD de categorias)   │  │  contentList          │
│ :31  (query)       │   │ store/update/destroy  │  │ (rotas portal/{slug})  │
│ :308 (menu item)   │   │ apiStore/apiUpdate    │  │ views generic.*       │
└───────────────────┘   └────────────────────┘   └────────────────────────┘
        ▲                          ▲                          ▲
        │                          │                          │
┌───────┴────────┐      ┌──────────┴──────────┐    ┌──────────┴─────────┐
│ menu_orders    │      │ modulos / resources │    │ contents +         │
│ (slug          │      │ (slug               │    │ archives + links   │
│  'categoria-   │      │  'mais-conteudos')  │    │                    │
│  conteudo')    │      │                     │    │                    │
└────────────────┘      └─────────────────────┘    └────────────────────┘
```

## 2. Classes envolvidas

| Classe | Caminho | Papel |
|---|---|---|
| `MenuServiceProvider` | `v6/app/Providers/MenuServiceProvider.php` | Define o recorte (`:31`) e monta o item de menu "Mais Conteúdos" (`:308-316`) |
| `CategoryContentController` | `v6/app/Http/Controllers/Prefecture/CategoryContentController.php` | CRUD de categorias (inclusive as "Mais Conteúdos") + API interna |
| `CategoryContent` | `v6/app/Http/Model/Prefecture/CategoryContent.php` | Model da hierarquia de categorias (`categoryContents()`, `rightWayToGetCategoryContents()`, `getAllDescendants()`) |
| `CategoryContentPresenter` | `v6/app/Http/Presenters/CategoryContentPresenter.php` | Apresentação do model (via `PresentableTrait`) |
| `CategoryContentRequest` | `v6/app/Http/Requests/CategoryContentRequest.php` | Validação condicional por categoria pai |
| `ApiStoreRequest` / `ApiUpdateRequest` / `ApiDestroyRequest` | `v6/app/Http/Requests/CategoryContent/` | Validação da API interna de categorias |
| `ShowResource` | `v6/app/Http/Resources/CategoryContents/ShowResource.php` | JSON da categoria (`id`, `name`, `order`, `parentId`) |
| `ContentController` | `v6/app/Http/Controllers/Prefecture/ContentController.php` | Publica conteúdos; `create`/`edit` listam as categorias "Mais Conteúdos" |
| `Content` | `v6/app/Http/Model/Prefecture/Content.php` | Conteúdos da categoria |
| `PortalController` | `v6/app/Http/Controllers/Portal/PortalController.php` | Renderização genérica no portal (`contentList`) |
| `MenuItem` | `v6/app/Http/Model/Prefecture/MenuItem.php` | Itens de menu do portal ligados a categorias |
| `Modulos` | `v6/app/Http/Model/Admin/Modulos.php` | Registry de módulos |

## 3. Fluxo: definição do recorte

```php
// v6/app/Providers/MenuServiceProvider.php:31
$mais_conteudos = CategoryContent::where('status_create', 0)->whereNull('category_contents_id')->get();
```

Duas condições:

| Condição | Significado |
|---|---|
| `status_create = 0` | Categoria **criada pelo operador** (as de sistema têm `status_create = 1`) |
| `category_contents_id IS NULL` | Categoria de **topo** (sem pai) — as subcategorias não entram no menu |

O mesmo recorte é repetido em:

| Arquivo:linha | Contexto |
|---|---|
| `v6/app/Providers/MenuServiceProvider.php:31` | Menu do CMS |
| `v6/app/Http/Controllers/Prefecture/CategoryContentController.php:52` | `index()` — lista de categorias |
| `…/CategoryContentController.php:63` | `create()` — select de categoria pai |
| `…/CategoryContentController.php:561` | `edit()` — select de categoria pai |
| `v6/app/Http/Controllers/Prefecture/ContentController.php:341-342` | `create()` — categorias pai e filhas do conteúdo |
| `…/ContentController.php:361-362` | `edit()` — idem |

## 4. Fluxo: menu do CMS

```php
// v6/app/Providers/MenuServiceProvider.php:307-317
if (count($mais_conteudos) > 0) {
    foreach ($mais_conteudos as $item) {
        array_push($sub, ['text' => $item->name, 'url' => '/cms/categoria-conteudo/' . $item->id]);
    }
}
$event->menu->add([
    'text' => 'Mais Conteúdos', 'icon' => 'plus-square',
    'submenu' => $sub
]);
break;
```

O item do menu é exibido apenas quando o `menu_orders.slug` correspondente
(`categoria-conteudo`) tem `has_access("categoria-conteudo.index")` **e** o módulo
(`modulos.status`) está ativo:

```php
// v6/app/Providers/MenuServiceProvider.php:46
if (has_access("$menu->slug.index") && isset($menu->modulo->status) && $menu->modulo->status) {
```

## 5. Fluxo: criação de categoria

```
POST /cms/categoria-conteudo
  → CategoryContentController@store(CategoryContentRequest)
      → DB::transaction
          → slug = str_slug(name)
          → slug duplicado? → 'cat' . (max(id)+1) . '_' . slug
          → rebalanceamento de `order` (order >= novo) [+1]
          → CategoryContent::create([... 'status_create' => 0, ...])
          → upload de `custom_icon` → public/uploads/icons/<timestamp>_<nome>
          → activity()->log('Cadastro de categoria de notícia')
      → redirect: rota 'categoria-conteudo.index' (genérico) ou 'cadastrar-categorias'
```

Trecho real (`v6/app/Http/Controllers/Prefecture/CategoryContentController.php:448-482`):

```php
DB::transaction(function () use ($request) {
    $slug = str_slug($request->get('name'));
    $categoryContent = [
        'name' => $request->get('name'),
        'slug' => $slug,
        'order' => $request->get('order'),
        'quantidade_itens' => $request->get('quantidade_itens'),
        'status_create' => 0,
        'layout' => $request->get('layout'),
        'display_type' => $request->get('display_type') && $request->get('display_type') != '0' ? $request->get('display_type') : null,
        'category_contents_id' => $request->get('category_contents_id') != 0 ? $request->get('category_contents_id') : null,
    ];
    // trecho omitido do código original
```

## 6. Fluxo: publicação de conteúdo

```
GET /cms/conteudo/create
  → ContentController@create()
      → $categories = CategoryContent::where('status_create', 0)->whereNull('category_contents_id')->get()->pluck('id');
      → $categoryContents = CategoryContent::where('status_create', false)->whereIn('category_contents_id', $categories)->get();
      → view('prefecture.contents.create', ['categoryContent' => $categoryContents])
```

Ou seja: o formulário de conteúdo **só** oferece as categorias "Mais Conteúdos" e
suas subcategorias. É essa a dependência funcional que dá sentido ao módulo.

## 7. Fluxo: exibição no portal

```
GET /portal/{slug1}[/{slug2}[/{slug3}]]
  → PortalController@contentList(Request $request, $slug, $slug2 = null, $slug3 = null)
      → $category = CategoryContent::where('slug', $slug)->first()
      → switch ($slug): casos conhecidos redirecionam (noticias, glossario, …)
      → default (categoria "Mais Conteúdos"):
            $title = $category->name
            $categories = CategoryContent::whereHas('categoryContents', fn($q) => $q->where('slug', $slug))->get()
            se $slug3: exibe o conteúdo  → view generic.view
            se $slug2: exibe a lista     → view generic.show
            senão:     exibe as subcategorias → view generic.index
```

Trecho real (`v6/app/Http/Controllers/Portal/PortalController.php:341-368`):

```php
default:
    if ($category == null) return redirect()->route('home');

    $title = $category->name;
    $categoryFather = $category;
    $categories = CategoryContent::whereHas('categoryContents', function ($query) use ($slug) {
        $query->where('slug', '=', $slug);
    })->get();

    if ($slug3 != null) {
        $content = Content::where('slug', $slug3)->first();
        if ($content && $content->status == true && strtotime($content->publication_date) <= time()) {
            // trecho omitido do código original
            return view('portal.layouts.' . $this->getTemplateActived() . '.generic.view', compact(...));
        }
    }

    if ($slug2 != null) {
        $category = CategoryContent::whereHas('categoryContents', function ($query) use ($slug) {
            $query->where('slug', '=', $slug);
        })->where('slug', $slug2)->first();

        $contents = Content::where('category_contents_id', $category->id)->orderBy('publication_date', 'desc')->paginate(12);
        $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;");

        return view('portal.layouts.' . $this->getTemplateActived() . '.generic.show', compact('title', 'category', 'categoryFather', 'categories', 'contents', 'anos'));
    }
    return view('portal.layouts.' . $this->getTemplateActived() . '.generic.index', compact('title', 'category', 'categoryFather', 'categories'));
```

## 8. Dependências

| Pacote / recurso | Uso |
|---|---|
| `laravel/framework` 5.5 | Eloquent, FormRequest, Resources, `DB::transaction` |
| `laracasts/presenter` | `PresentableTrait` em `CategoryContent` |
| `yajra/laravel-datatables-oracle` | Grid genérico de conteúdos |
| `cartalyst/sentinel` | `has_access`, `Sentinel::check()` (API) e `Sentinel::getUser()` (logs) |
| `spatie/laravel-activitylog` | Log de auditoria das categorias |
| `jedreamt/laravel-dynamic-menu` (`JeroenNoten\LaravelAdminLte\Events\BuildingMenu`) | Montagem do menu do AdminLTE |
| Bootstrap 4 | Views do portal |

## 9. Onde o módulo não tem camada

- Sem model, controller, resource, view ou rota **próprios**.
- Sem `Policy`: a autorização usa `has_access('categoria-conteudo.*')`.
- Sem testes automatizados específicos.
- Sem API pública (a API de categorias é **interna**, montada no grupo `/cms`).
