Versão inicial em produção
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
/**
|
||||
* AçãoOpção — autenticação e controle de permissões por página.
|
||||
* Inclua (require_once) depois de config.php e db.php.
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/config.php';
|
||||
require_once __DIR__ . '/db.php';
|
||||
|
||||
if (session_status() !== PHP_SESSION_ACTIVE) {
|
||||
session_start();
|
||||
}
|
||||
|
||||
// Páginas de administração do próprio sistema de acesso. Ficam sempre
|
||||
// restritas a perfis com acesso_total, independente da tabela `paginas`
|
||||
// (que serve para as páginas de negócio, essas sim configuráveis por perfil).
|
||||
const AO_PAGINAS_ADMIN = [
|
||||
'usuarios' => ['arquivo' => 'usuarios.php', 'nome' => 'Usuários'],
|
||||
'perfis' => ['arquivo' => 'perfis.php', 'nome' => 'Perfis'],
|
||||
'paginas_admin' => ['arquivo' => 'paginas_admin.php', 'nome' => 'Páginas'],
|
||||
];
|
||||
|
||||
function ao_usuario_logado() {
|
||||
return $_SESSION['ao_usuario'] ?? null;
|
||||
}
|
||||
|
||||
function ao_exigir_login() {
|
||||
if (!ao_usuario_logado()) {
|
||||
$_SESSION['ao_pos_login'] = $_SERVER['REQUEST_URI'] ?? 'index.php';
|
||||
header('Location: login.php');
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
/** true se o usuário logado pode acessar a página de negócio $chave (tabela `paginas`). */
|
||||
function ao_tem_permissao($chave) {
|
||||
$usuario = ao_usuario_logado();
|
||||
if (!$usuario) {
|
||||
return false;
|
||||
}
|
||||
if (!empty($usuario['acesso_total'])) {
|
||||
return true;
|
||||
}
|
||||
return in_array($chave, $usuario['paginas'] ?? [], true);
|
||||
}
|
||||
|
||||
/** Carrega os dados de sessão do usuário a partir do banco (login e refresh de permissões). */
|
||||
function ao_carregar_sessao_usuario(PDO $pdo, array $usuarioDb, array $perfilDb) {
|
||||
$paginas = [];
|
||||
if (empty($perfilDb['acesso_total'])) {
|
||||
$stmt = $pdo->prepare(
|
||||
'SELECT p.chave FROM paginas p
|
||||
INNER JOIN perfil_paginas pp ON pp.pagina_id = p.id
|
||||
WHERE pp.perfil_id = ? AND p.ativo = 1'
|
||||
);
|
||||
$stmt->execute([$perfilDb['id']]);
|
||||
$paginas = array_column($stmt->fetchAll(), 'chave');
|
||||
}
|
||||
|
||||
$_SESSION['ao_usuario'] = [
|
||||
'id' => (int) $usuarioDb['id'],
|
||||
'nome' => $usuarioDb['nome'],
|
||||
'email' => $usuarioDb['email'],
|
||||
'perfil_id' => (int) $perfilDb['id'],
|
||||
'perfil_nome' => $perfilDb['nome'],
|
||||
'acesso_total' => (bool) $perfilDb['acesso_total'],
|
||||
'paginas' => $paginas,
|
||||
];
|
||||
}
|
||||
|
||||
function ao_pagina_acesso_negado($ao_tema_atual = null) {
|
||||
http_response_code(403);
|
||||
$cor = $ao_tema_atual['cor'] ?? '#7FA653';
|
||||
echo '<!doctype html><html lang="pt-BR"><head><meta charset="UTF-8">'
|
||||
. '<title>Acesso negado — AçãoOpção</title>'
|
||||
. '<meta name="viewport" content="width=device-width, initial-scale=1.0"></head>'
|
||||
. '<body style="font-family: system-ui, sans-serif; display:flex; align-items:center; justify-content:center; '
|
||||
. 'min-height:100vh; margin:0; background:#F4F5F5;">'
|
||||
. '<div style="max-width:420px; text-align:center; padding:32px;">'
|
||||
. '<div style="font-size:42px; margin-bottom:8px;">🔒</div>'
|
||||
. '<h1 style="font-size:1.25rem; margin-bottom:8px;">Acesso negado</h1>'
|
||||
. '<p style="color:#666; margin-bottom:24px;">Seu perfil não tem permissão para acessar esta página. '
|
||||
. 'Fale com um administrador se precisar desse acesso.</p>'
|
||||
. '<a href="index.php" style="display:inline-block; padding:10px 20px; border-radius:8px; '
|
||||
. 'background:' . htmlspecialchars($cor) . '; color:#fff; text-decoration:none; font-weight:600;">Voltar</a>'
|
||||
. '</div></body></html>';
|
||||
exit;
|
||||
}
|
||||
|
||||
/** Exige login e, se $chave for informada, permissão para aquela página de negócio. */
|
||||
function ao_exigir_permissao($chave = null, $ao_tema_atual = null) {
|
||||
ao_exigir_login();
|
||||
if ($chave !== null && !ao_tem_permissao($chave)) {
|
||||
ao_pagina_acesso_negado($ao_tema_atual);
|
||||
}
|
||||
}
|
||||
|
||||
/** Exige login e acesso_total (para as páginas de administração do próprio sistema). */
|
||||
function ao_exigir_acesso_total($ao_tema_atual = null) {
|
||||
ao_exigir_login();
|
||||
$usuario = ao_usuario_logado();
|
||||
if (empty($usuario['acesso_total'])) {
|
||||
ao_pagina_acesso_negado($ao_tema_atual);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
/**
|
||||
* AçãoOpção — configuração de ambiente (desenvolvimento / produção).
|
||||
* Inclua este arquivo antes de includes/theme.php.
|
||||
*/
|
||||
|
||||
define('AO_AMBIENTE_ARQUIVO', __DIR__ . '/ambiente.json');
|
||||
|
||||
const AO_URLS = [
|
||||
'desenvolvimento' => 'http://localhost:8080/claude/AcaoOpcao/',
|
||||
'producao' => 'https://acaoeopcaoderivativos.systems/',
|
||||
];
|
||||
|
||||
function ao_ler_ambiente() {
|
||||
$padrao = 'desenvolvimento';
|
||||
if (!file_exists(AO_AMBIENTE_ARQUIVO)) {
|
||||
return $padrao;
|
||||
}
|
||||
$conteudo = json_decode((string) file_get_contents(AO_AMBIENTE_ARQUIVO), true);
|
||||
$ambiente = is_array($conteudo) ? ($conteudo['ambiente'] ?? $padrao) : $padrao;
|
||||
return array_key_exists($ambiente, AO_URLS) ? $ambiente : $padrao;
|
||||
}
|
||||
|
||||
function ao_salvar_ambiente($ambiente) {
|
||||
if (!array_key_exists($ambiente, AO_URLS)) {
|
||||
return false;
|
||||
}
|
||||
$ok = file_put_contents(
|
||||
AO_AMBIENTE_ARQUIVO,
|
||||
json_encode(['ambiente' => $ambiente], JSON_PRETTY_PRINT)
|
||||
);
|
||||
return $ok !== false;
|
||||
}
|
||||
|
||||
// Constantes disponíveis para o resto do sistema:
|
||||
// AO_AMBIENTE 'desenvolvimento' | 'producao'
|
||||
// AO_BASE_URL URL completa do ambiente ativo
|
||||
// AO_IS_PRODUCAO true quando o ambiente ativo é produção (https)
|
||||
define('AO_AMBIENTE', ao_ler_ambiente());
|
||||
define('AO_BASE_URL', AO_URLS[AO_AMBIENTE]);
|
||||
define('AO_IS_PRODUCAO', AO_AMBIENTE === 'producao');
|
||||
|
||||
// Credenciais do banco de dados MySQL por ambiente. Troque os valores de
|
||||
// "producao" diretamente aqui, editando o arquivo no servidor — por segurança,
|
||||
// evite colar senha de produção em e-mail, chat ou qualquer lugar fora deste
|
||||
// arquivo. O ambiente ativo é escolhido na tela Configurações do sistema.
|
||||
const AO_BANCOS = [
|
||||
'desenvolvimento' => [
|
||||
'host' => 'localhost',
|
||||
'nome' => 'acaoopcao',
|
||||
'usuario' => 'josima70_josimar',
|
||||
'senha' => 'Josim@r230976',
|
||||
],
|
||||
'producao' => [
|
||||
'host' => 'database', // ajuste se o banco de produção estiver em outro host
|
||||
'nome' => 'acaoopcao',
|
||||
'usuario' => 'root',
|
||||
'senha' => 'Josim@r230976',
|
||||
],
|
||||
];
|
||||
|
||||
define('AO_DB_HOST', AO_BANCOS[AO_AMBIENTE]['host']);
|
||||
define('AO_DB_NOME', AO_BANCOS[AO_AMBIENTE]['nome']);
|
||||
define('AO_DB_USUARIO', AO_BANCOS[AO_AMBIENTE]['usuario']);
|
||||
define('AO_DB_SENHA', AO_BANCOS[AO_AMBIENTE]['senha']);
|
||||
|
||||
/**
|
||||
* Monta uma URL absoluta a partir de um caminho relativo ao projeto,
|
||||
* usando a URL base do ambiente ativo. Útil para links que precisam
|
||||
* ser absolutos (ex.: compartilhamento, tag canonical, e-mails).
|
||||
* Os links internos do próprio sistema continuam relativos e não
|
||||
* precisam desta função para funcionar nos dois ambientes.
|
||||
*/
|
||||
function ao_url($caminho = '') {
|
||||
return rtrim(AO_BASE_URL, '/') . '/' . ltrim($caminho, '/');
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
/**
|
||||
* AçãoOpção — conexão com o banco de dados MySQL.
|
||||
* Inclua (require_once) antes de qualquer uso de $pdo.
|
||||
* As credenciais usadas aqui vêm de includes/config.php (AO_BANCOS),
|
||||
* de acordo com o ambiente ativo (desenvolvimento/produção).
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/config.php';
|
||||
|
||||
try {
|
||||
$pdo = new PDO(
|
||||
"mysql:host=" . AO_DB_HOST . ";dbname=" . AO_DB_NOME . ";charset=utf8mb4",
|
||||
AO_DB_USUARIO,
|
||||
AO_DB_SENHA,
|
||||
[
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::ATTR_EMULATE_PREPARES => false,
|
||||
]
|
||||
);
|
||||
} catch (PDOException $e) {
|
||||
http_response_code(500);
|
||||
die(
|
||||
'<div style="font-family: sans-serif; max-width: 640px; margin: 60px auto; padding: 24px; ' .
|
||||
'border: 1px solid #e2b3ab; background: #fdf1ef; color: #7a2c22; border-radius: 8px;">' .
|
||||
'<h2 style="margin-top:0;">Não foi possível conectar ao banco de dados</h2>' .
|
||||
'<p>Ambiente ativo: <strong>' . htmlspecialchars(AO_AMBIENTE) . '</strong>. Verifique se o MySQL está ' .
|
||||
'ativo, se o banco <strong>' . htmlspecialchars(AO_DB_NOME) . '</strong> foi criado a partir de ' .
|
||||
'<code>includes/esquema.sql</code>, e se o usuário/senha em <code>includes/config.php</code> ' .
|
||||
'(bloco <code>AO_BANCOS</code>) estão corretos para este ambiente.</p>' .
|
||||
'<p style="font-size: 0.85rem; opacity: .8;">Detalhe técnico: ' . htmlspecialchars($e->getMessage()) . '</p>' .
|
||||
'</div>'
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
</main>
|
||||
<footer style="text-align:center; padding: var(--ao-space-6) var(--ao-space-4); color: var(--ao-text-muted); font-size: var(--ao-fs-xs);">
|
||||
AçãoOpção — protótipo de identidade visual (tema atual: <?= htmlspecialchars($ao_tema_atual['nome']) ?>)
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,232 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/theme.php';
|
||||
require_once __DIR__ . '/auth.php';
|
||||
|
||||
ao_exigir_login();
|
||||
$ao_usuario = ao_usuario_logado();
|
||||
|
||||
// Página de administração do próprio sistema (usuários/perfis/páginas)?
|
||||
// Essas sempre exigem acesso_total, independente da tabela `paginas`.
|
||||
if (isset($pagina_ativa) && array_key_exists($pagina_ativa, AO_PAGINAS_ADMIN)) {
|
||||
ao_exigir_acesso_total($ao_tema_atual);
|
||||
} elseif (isset($pagina_ativa)) {
|
||||
ao_exigir_permissao($pagina_ativa, $ao_tema_atual);
|
||||
}
|
||||
|
||||
// Páginas de negócio configuráveis, na ordem cadastrada em `paginas`.
|
||||
$ao_nav_paginas = $pdo->query('SELECT chave, nome, arquivo FROM paginas WHERE ativo = 1 ORDER BY ordem, nome')->fetchAll();
|
||||
|
||||
// "Contas" não fica mais no menu principal — mora no submenu do usuário.
|
||||
// Buscamos os dados dela aqui (nome/arquivo, caso alguém renomeie em
|
||||
// Páginas) para montar o link lá embaixo, já checando a permissão normal.
|
||||
$ao_pagina_contas = null;
|
||||
foreach ($ao_nav_paginas as $p) {
|
||||
if ($p['chave'] === 'contas') {
|
||||
$ao_pagina_contas = $p;
|
||||
break;
|
||||
}
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="pt-BR" data-ao-mode="<?= htmlspecialchars($ao_modo) ?>">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>AçãoOpção<?= isset($titulo_pagina) ? ' — ' . htmlspecialchars($titulo_pagina) : '' ?></title>
|
||||
<link rel="icon" href="<?= htmlspecialchars($ao_tema_atual['favicon']) ?>">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@500;600;700&family=Inter:wght@400;500;600;700&family=IBM+Plex+Mono:wght@500;600&display=swap" rel="stylesheet">
|
||||
<!-- A identidade visual inteira do sistema muda ao trocar apenas este arquivo CSS -->
|
||||
<link rel="stylesheet" href="<?= htmlspecialchars($ao_tema_atual['css']) ?>">
|
||||
<style>
|
||||
* { box-sizing: border-box; }
|
||||
body { margin: 0; }
|
||||
.ao-shell { max-width: 1180px; margin: 0 auto; padding: var(--ao-space-6) var(--ao-space-5); }
|
||||
.ao-topbar { display: flex; align-items: center; justify-content: space-between; gap: var(--ao-space-4); }
|
||||
.ao-topbar nav { display: flex; gap: var(--ao-space-4); flex-wrap: wrap; }
|
||||
.ao-topbar nav a { font-size: var(--ao-fs-sm); font-weight: 600; padding: 6px 2px; border-bottom: 2px solid transparent; text-decoration: none; white-space: nowrap; }
|
||||
.ao-topbar nav a.ativo { border-bottom-color: currentColor; }
|
||||
.ao-nav-separador { width: 1px; align-self: stretch; background: rgba(255,255,255,.25); margin: 0 2px; }
|
||||
.ao-brand-icon { width: 28px; height: 28px; border-radius: 8px; display: block; }
|
||||
.ao-topbar-direita { display: flex; align-items: center; gap: var(--ao-space-4); flex-wrap: wrap; }
|
||||
|
||||
/* Menu do usuário (nome no cabeçalho -> submenu) */
|
||||
.ao-user-menu { position: relative; }
|
||||
.ao-user-menu-trigger {
|
||||
display: flex; align-items: center; gap: 8px;
|
||||
background: none; border: none; cursor: pointer; padding: 4px 2px;
|
||||
font-family: var(--ao-font-body); color: #fff;
|
||||
}
|
||||
.ao-user-menu-trigger strong { font-size: var(--ao-fs-sm); color: #fff; }
|
||||
.ao-user-menu-caret { font-size: 10px; color: rgba(255,255,255,.75); transition: transform .15s ease; }
|
||||
.ao-user-menu.aberto .ao-user-menu-caret { transform: rotate(180deg); }
|
||||
.ao-user-menu-dropdown {
|
||||
display: none;
|
||||
position: absolute; top: calc(100% + 12px); right: 0;
|
||||
min-width: 230px;
|
||||
background: var(--ao-bg-surface);
|
||||
border: 1px solid var(--ao-border);
|
||||
border-radius: var(--ao-radius-md);
|
||||
box-shadow: var(--ao-shadow-lg);
|
||||
padding: var(--ao-space-2);
|
||||
z-index: 1000;
|
||||
}
|
||||
.ao-user-menu.aberto .ao-user-menu-dropdown { display: block; }
|
||||
.ao-user-menu-dropdown a {
|
||||
display: block; padding: 8px 10px; border-radius: var(--ao-radius-sm);
|
||||
font-size: var(--ao-fs-sm); font-weight: 600;
|
||||
color: var(--ao-text-body); text-decoration: none;
|
||||
}
|
||||
.ao-user-menu-dropdown a:hover, .ao-user-menu-dropdown a.ativo { background: var(--ao-bg-page); }
|
||||
.ao-user-menu-separador { height: 1px; background: var(--ao-border); margin: var(--ao-space-2) 6px; }
|
||||
.ao-user-menu-secao-titulo {
|
||||
font-size: var(--ao-fs-xs); text-transform: uppercase; letter-spacing: .04em;
|
||||
color: var(--ao-text-muted); padding: 6px 10px 4px;
|
||||
}
|
||||
.ao-theme-switcher { display: flex; align-items: center; gap: 8px; padding: 2px 10px 10px; }
|
||||
.ao-theme-dot {
|
||||
width: 20px; height: 20px; border-radius: 50%;
|
||||
border: 2px solid var(--ao-border);
|
||||
display: inline-block; transition: transform .15s ease;
|
||||
}
|
||||
.ao-theme-dot:hover { transform: scale(1.15); }
|
||||
.ao-theme-dot.ativo { border-color: var(--ao-text-strong); box-shadow: 0 0 0 2px var(--ao-focus-ring); }
|
||||
.ao-mode-toggle {
|
||||
display: inline-flex; align-items: center; justify-content: center;
|
||||
width: 28px; height: 28px; border-radius: 50%;
|
||||
background: var(--ao-bg-page);
|
||||
border: 1px solid var(--ao-border);
|
||||
font-size: 14px; line-height: 1; text-decoration: none;
|
||||
transition: background-color .15s ease, transform .15s ease;
|
||||
}
|
||||
.ao-mode-toggle:hover { background: var(--ao-primary-soft); transform: scale(1.08); }
|
||||
.ao-page-title { margin-bottom: var(--ao-space-5); }
|
||||
|
||||
/* Utilitário de layout usado em vários formulários (acoes.php, usuarios.php,
|
||||
operacoes.php, paginas_admin.php, index.php) para colocar campos lado a
|
||||
lado. Cada uso já define seu próprio "gap" inline. */
|
||||
.ao-row { display: flex; flex-wrap: wrap; }
|
||||
|
||||
/* Popup de cadastro/edição — compartilhado por qualquer página que use um
|
||||
<div class="ao-modal-backdrop" id="...">, com a classe "is-open" para
|
||||
controlar se aparece (o PHP já pode renderizar aberto, ex: ao editar). */
|
||||
.ao-modal-backdrop {
|
||||
display: none;
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, .5);
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: var(--ao-space-4);
|
||||
z-index: 1000;
|
||||
}
|
||||
.ao-modal-backdrop.is-open { display: flex; }
|
||||
.ao-modal {
|
||||
background: var(--ao-bg-surface);
|
||||
border-radius: var(--ao-radius-lg);
|
||||
box-shadow: var(--ao-shadow-lg);
|
||||
padding: var(--ao-space-5);
|
||||
width: 100%;
|
||||
max-width: 560px;
|
||||
max-height: 90vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.ao-modal-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: var(--ao-space-2);
|
||||
}
|
||||
.ao-modal-close {
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 1.5rem;
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
color: var(--ao-text-muted);
|
||||
padding: 0 var(--ao-space-1);
|
||||
}
|
||||
.ao-modal-close:hover { color: var(--ao-text-strong); }
|
||||
body, .ao-card, .ao-table, .ao-field input, .ao-field select, .ao-field textarea, .ao-btn {
|
||||
transition: background-color .2s ease, color .2s ease, border-color .2s ease;
|
||||
}
|
||||
@media (max-width: 900px) {
|
||||
.ao-topbar { flex-wrap: wrap; gap: 12px; padding: var(--ao-space-3); }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header class="ao-topbar">
|
||||
<div class="ao-brand">
|
||||
<img src="<?= htmlspecialchars($ao_tema_atual['icone']) ?>" alt="" class="ao-brand-icon">
|
||||
<span>AçãoOpção</span>
|
||||
</div>
|
||||
<nav>
|
||||
<?php foreach ($ao_nav_paginas as $p): ?>
|
||||
<?php if ($p['chave'] === 'contas') continue; // fica no submenu do usuário ?>
|
||||
<?php if (!empty($ao_usuario['acesso_total']) || in_array($p['chave'], $ao_usuario['paginas'] ?? [], true)): ?>
|
||||
<a href="<?= htmlspecialchars($p['arquivo']) ?>" class="<?= ($pagina_ativa ?? '') === $p['chave'] ? 'ativo' : '' ?>"><?= htmlspecialchars($p['nome']) ?></a>
|
||||
<?php endif; ?>
|
||||
<?php endforeach; ?>
|
||||
<?php if (!empty($ao_usuario['acesso_total'])): ?>
|
||||
<span class="ao-nav-separador"></span>
|
||||
<?php foreach (AO_PAGINAS_ADMIN as $chave => $info): ?>
|
||||
<a href="<?= htmlspecialchars($info['arquivo']) ?>" class="<?= ($pagina_ativa ?? '') === $chave ? 'ativo' : '' ?>"><?= htmlspecialchars($info['nome']) ?></a>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</nav>
|
||||
<div class="ao-topbar-direita">
|
||||
<div class="ao-user-menu" id="ao-user-menu">
|
||||
<button type="button" class="ao-user-menu-trigger" onclick="aoAlternarMenuUsuario()">
|
||||
<strong><?= htmlspecialchars($ao_usuario['nome']) ?></strong>
|
||||
<span class="ao-user-menu-caret">▾</span>
|
||||
</button>
|
||||
<div class="ao-user-menu-dropdown">
|
||||
<?php if ($ao_pagina_contas && (!empty($ao_usuario['acesso_total']) || in_array('contas', $ao_usuario['paginas'] ?? [], true))): ?>
|
||||
<a href="<?= htmlspecialchars($ao_pagina_contas['arquivo']) ?>" class="<?= ($pagina_ativa ?? '') === 'contas' ? 'ativo' : '' ?>"><?= htmlspecialchars($ao_pagina_contas['nome']) ?></a>
|
||||
<?php endif; ?>
|
||||
<a href="minha-conta.php">Minha conta</a>
|
||||
|
||||
<div class="ao-user-menu-separador"></div>
|
||||
<div class="ao-user-menu-secao-titulo">Identidade visual</div>
|
||||
<div class="ao-theme-switcher">
|
||||
<?php foreach ($ao_temas_info as $chave => $info): ?>
|
||||
<a href="?tema=<?= urlencode($chave) ?>"
|
||||
class="ao-theme-dot <?= $ao_tema === $chave ? 'ativo' : '' ?>"
|
||||
style="background: <?= htmlspecialchars($info['cor']) ?>;"
|
||||
title="Trocar para <?= htmlspecialchars($info['nome']) ?>"></a>
|
||||
<?php endforeach; ?>
|
||||
<a href="?modo=<?= urlencode($ao_proximo_modo) ?>"
|
||||
class="ao-mode-toggle"
|
||||
title="Trocar para modo <?= htmlspecialchars($ao_modos_info[$ao_proximo_modo]['nome']) ?>">
|
||||
<?= $ao_modo_atual['icone'] ?>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="ao-user-menu-separador"></div>
|
||||
<a href="logout.php">Sair</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<script>
|
||||
function aoAlternarMenuUsuario(forcarAberto) {
|
||||
var menu = document.getElementById('ao-user-menu');
|
||||
if (typeof forcarAberto === 'boolean') {
|
||||
menu.classList.toggle('aberto', forcarAberto);
|
||||
} else {
|
||||
menu.classList.toggle('aberto');
|
||||
}
|
||||
}
|
||||
document.addEventListener('click', function (evento) {
|
||||
var menu = document.getElementById('ao-user-menu');
|
||||
if (menu && !menu.contains(evento.target)) {
|
||||
aoAlternarMenuUsuario(false);
|
||||
}
|
||||
});
|
||||
document.addEventListener('keydown', function (evento) {
|
||||
if (evento.key === 'Escape') aoAlternarMenuUsuario(false);
|
||||
});
|
||||
</script>
|
||||
<main class="ao-shell">
|
||||
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
/**
|
||||
* AçãoOpção — controle de identidade visual (tema) do sistema.
|
||||
* Inclua este arquivo no topo de header.php, antes de qualquer saída HTML,
|
||||
* sempre depois de includes/config.php (usamos AO_IS_PRODUCAO aqui).
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/config.php';
|
||||
|
||||
$ao_temas_info = [
|
||||
'verde' => [
|
||||
'nome' => 'Verde',
|
||||
'css' => 'assets/css/identidade-visual-verde.css',
|
||||
'favicon' => 'assets/img/logos/verde/favicon.ico',
|
||||
'icone' => 'assets/img/logos/verde/svg/icone-cor.svg',
|
||||
'cor' => '#7FA653',
|
||||
],
|
||||
'marrom' => [
|
||||
'nome' => 'Marrom',
|
||||
'css' => 'assets/css/identidade-visual-marrom.css',
|
||||
'favicon' => 'assets/img/logos/marrom/favicon.ico',
|
||||
'icone' => 'assets/img/logos/marrom/svg/icone-cor.svg',
|
||||
'cor' => '#A6754C',
|
||||
],
|
||||
'cinza' => [
|
||||
'nome' => 'Cinza',
|
||||
'css' => 'assets/css/identidade-visual-cinza.css',
|
||||
'favicon' => 'assets/img/logos/cinza/favicon.ico',
|
||||
'icone' => 'assets/img/logos/cinza/svg/icone-cor.svg',
|
||||
'cor' => '#6B6F77',
|
||||
],
|
||||
];
|
||||
|
||||
$ao_modos_info = [
|
||||
'claro' => ['nome' => 'Claro', 'icone' => '☀️'],
|
||||
'escuro' => ['nome' => 'Escuro', 'icone' => '🌙'],
|
||||
];
|
||||
|
||||
// Duração do cookie de identidade: 1 ano. Guardamos a escolha do usuário
|
||||
// em cookie (não em sessão) justamente para que ela sobreviva ao fechar
|
||||
// o navegador — é isso que "lembra" o tema/modo na próxima visita.
|
||||
const AO_COOKIE_DURACAO = 60 * 60 * 24 * 365;
|
||||
|
||||
function ao_definir_cookie($nome, $valor) {
|
||||
setcookie($nome, $valor, [
|
||||
'expires' => time() + AO_COOKIE_DURACAO,
|
||||
'path' => '/',
|
||||
'secure' => AO_IS_PRODUCAO,
|
||||
'httponly' => true,
|
||||
'samesite' => 'Lax',
|
||||
]);
|
||||
}
|
||||
|
||||
// Troca de tema/modo via link "?tema=marrom" e/ou "?modo=escuro" — grava
|
||||
// em cookie e redireciona para a mesma URL sem o parâmetro, para manter
|
||||
// o endereço limpo. Os dois podem vir juntos na mesma requisição.
|
||||
$ao_precisa_redirecionar = false;
|
||||
|
||||
if (isset($_GET['tema']) && array_key_exists($_GET['tema'], $ao_temas_info)) {
|
||||
ao_definir_cookie('ao_tema', $_GET['tema']);
|
||||
$_COOKIE['ao_tema'] = $_GET['tema'];
|
||||
$ao_precisa_redirecionar = true;
|
||||
}
|
||||
|
||||
if (isset($_GET['modo']) && array_key_exists($_GET['modo'], $ao_modos_info)) {
|
||||
ao_definir_cookie('ao_modo', $_GET['modo']);
|
||||
$_COOKIE['ao_modo'] = $_GET['modo'];
|
||||
$ao_precisa_redirecionar = true;
|
||||
}
|
||||
|
||||
if ($ao_precisa_redirecionar) {
|
||||
$destino = strtok($_SERVER['REQUEST_URI'], '?');
|
||||
header('Location: ' . $destino);
|
||||
exit;
|
||||
}
|
||||
|
||||
$ao_tema = $_COOKIE['ao_tema'] ?? 'verde';
|
||||
if (!array_key_exists($ao_tema, $ao_temas_info)) {
|
||||
$ao_tema = 'verde';
|
||||
}
|
||||
|
||||
$ao_modo = $_COOKIE['ao_modo'] ?? 'claro';
|
||||
if (!array_key_exists($ao_modo, $ao_modos_info)) {
|
||||
$ao_modo = 'claro';
|
||||
}
|
||||
|
||||
$ao_tema_atual = $ao_temas_info[$ao_tema];
|
||||
$ao_modo_atual = $ao_modos_info[$ao_modo];
|
||||
$ao_proximo_modo = $ao_modo === 'claro' ? 'escuro' : 'claro';
|
||||
Reference in New Issue
Block a user