/views/sysadmin/smtp_status.php must: // 1. Load $pdo 2. Run the sysadmin guard 3. Set $repo_root // Optional overrides, same as backups.php / platform_update.php: // $platform_layout_header / _navbar / _footer, $platform_dashboard_url // // Read-only by default: it reports what the transport WOULD do. The two actions // (connection test, send a test message) are explicit and CSRF-guarded, because // one opens a socket to a third party and the other actually sends mail. require_once __DIR__ . '/functions_mail.php'; require_once __DIR__ . '/functions_csrf.php'; if (!isset($repo_root) || !is_string($repo_root)) { $repo_root = realpath(dirname(__DIR__, 2)) ?: dirname(__DIR__, 2); } $repo_root = rtrim($repo_root, '/'); // ── Actions (POST to self, PRG) ───────────────────────────────────────────── if ($_SERVER['REQUEST_METHOD'] === 'POST') { $action = $_POST['action'] ?? ''; if (!platform_csrf_ok()) { $_SESSION['smtp_flash'] = ['type' => 'danger', 'message' => 'Pedido rejeitado (token CSRF ausente ou inválido).']; } elseif ($action === 'test_connection') { $r = platform_mail_test($repo_root); $_SESSION['smtp_flash'] = $r['ok'] ? ['type' => 'success', 'message' => 'Ligação SMTP estabelecida e fechada com sucesso.', 'log' => $r['log']] : ['type' => 'danger', 'message' => 'Falha na ligação: ' . $r['error'], 'log' => $r['log']]; } elseif ($action === 'save') { // Editar aqui, e não só diagnosticar. // // A página nasceu só de leitura, o que fazia sentido enquanto cada site // tivesse a sua página de Comunicações. Não têm: no hub2, no silver e no // quack esta é a única página de SMTP que existe, e a única forma de // mudar o servidor era o setup.php -- uma página chamada "Configuração // inicial", que fecha assim que houver um sysadmin. Uma página que // mostra "Servidor: — vazio —" sem dar maneira de o preencher é um beco. // // Os sites que TÊM Comunicações próprias redirigem daqui para lá, para // não haver dois editores da mesma coisa. if (!isset($pdo) || !$pdo instanceof PDO) { // Sem base não há onde gravar. O stub deste site só forneceu // $repo_root, por isso a página fica de leitura. $_SESSION['smtp_flash'] = ['type' => 'danger', 'message' => 'Esta instalação não expõe a base de dados a esta página; configure pelo .env.']; header('Location: ' . $_SERVER['REQUEST_URI'], true, 303); exit; } $uid = isset($_SESSION['user']['id']) ? (int) $_SESSION['user']['id'] : null; $port = (int) ($_POST['smtp_port'] ?? 587); $secure = strtolower(trim((string) ($_POST['smtp_secure'] ?? 'tls'))); if (!in_array($secure, ['tls', 'ssl', ''], true)) { $secure = 'tls'; } $from = trim((string) ($_POST['email_from'] ?? '')); if ($from !== '' && !filter_var($from, FILTER_VALIDATE_EMAIL)) { // Validado porque já se perdeu tempo com isto: o campo do remetente // ficou uma vez com o NOME do remetente lá dentro, e o erro só // apareceu como "Invalid address (From)" numa fila presa, horas // depois. Um formulário que aceita tudo empurra a falha para longe // de quem a pode corrigir. $_SESSION['smtp_flash'] = ['type' => 'danger', 'message' => 'O remetente tem de ser um endereço de email — o nome vai no campo abaixo.']; header('Location: ' . $_SERVER['REQUEST_URI'], true, 303); exit; } platform_settings_set($pdo, 'smtp_host', trim((string) ($_POST['smtp_host'] ?? '')), $uid); platform_settings_set($pdo, 'smtp_port', (string) ($port > 0 ? $port : 587), $uid); platform_settings_set($pdo, 'smtp_secure', $secure, $uid); platform_settings_set($pdo, 'smtp_username', trim((string) ($_POST['smtp_username'] ?? '')), $uid); platform_settings_set($pdo, 'email_from', $from, $uid); platform_settings_set($pdo, 'email_from_name', trim((string) ($_POST['email_from_name'] ?? '')), $uid); // A password nunca é pré-preenchida, por isso vazio significa "mantém a // que está" e não "apaga". Apagar é uma caixa explícita. if (!empty($_POST['smtp_password_clear'])) { platform_settings_set($pdo, 'smtp_password', '', $uid); } elseif (trim((string) ($_POST['smtp_password'] ?? '')) !== '') { platform_settings_set($pdo, 'smtp_password', trim((string) $_POST['smtp_password']), $uid); } if (function_exists('platform_audit')) { platform_audit($pdo, 'settings.smtp', ['target_type' => 'settings', 'summary' => 'Configuração SMTP guardada', 'changes' => ['smtp_host' => $_POST['smtp_host'] ?? null, 'smtp_port' => $_POST['smtp_port'] ?? null, 'smtp_username' => $_POST['smtp_username'] ?? null, 'email_from' => $_POST['email_from'] ?? null, 'smtp_password' => ($_POST['smtp_password'] ?? '') !== '' ? 'alterada' : null]]); } $_SESSION['smtp_flash'] = ['type' => 'success', 'message' => 'Configuração guardada.']; } elseif ($action === 'send_test') { $to = trim((string) ($_POST['to'] ?? '')); if (!filter_var($to, FILTER_VALIDATE_EMAIL)) { $_SESSION['smtp_flash'] = ['type' => 'danger', 'message' => 'Endereço de destino inválido.']; } else { $r = platform_mail_send([ 'to' => $to, 'to_name' => 'Teste', 'subject' => 'Teste de SMTP — ' . (string) ($_SERVER['HTTP_HOST'] ?? 'plataforma'), 'body' => '

Mensagem de teste enviada a partir do painel de sistema.

', 'alt_body' => 'Mensagem de teste enviada a partir do painel de sistema.', ], $repo_root); if (!empty($r['skipped'])) { $_SESSION['smtp_flash'] = ['type' => 'warning', 'message' => 'Não enviado: EMAIL_DEV_PRINT está activo e APP_ENV não é prod, por isso a mensagem foi apenas registada no log.']; } elseif (!empty($r['ok'])) { $_SESSION['smtp_flash'] = ['type' => 'success', 'message' => 'Mensagem entregue ao servidor SMTP para ' . $to . '.']; } else { $_SESSION['smtp_flash'] = ['type' => 'danger', 'message' => 'Falha no envio: ' . $r['error']]; } } } header('Location: ' . $_SERVER['REQUEST_URI'], true, 303); exit; } // Editar exige base de dados: é lá que as definições ficam. Um stub que só // forneça $repo_root continua a ver a página, em modo de leitura. $canEdit = isset($pdo) && $pdo instanceof PDO; $flash = $_SESSION['smtp_flash'] ?? null; unset($_SESSION['smtp_flash']); // A configuração que realmente envia, e não a do ambiente: com $pdo, é a da // base que ganha. Era daqui que vinha o formulário mostrar campos vazios num // site cujo email funcionava. $cfg = (isset($pdo) && $pdo instanceof PDO && function_exists('platform_smtp_config')) ? platform_mail_config(platform_smtp_config($pdo)) : platform_mail_config(); $hasMailer = platform_mail_available($repo_root); $isProd = ($cfg['env'] === 'prod'); // Veredicto e valores em vigor: uma implementação só, em functions_mail.php. // Estavam aqui em linha, e quando o comms.php passou a mostrar o mesmo passaram // a ser duas cópias da mesma lista de casos -- a que ficasse para trás mentiria // a alguém a meio de uma avaria. $verdict = platform_mail_diagnosis($repo_root, null, $pdo ?? null); $rows = platform_mail_config_rows(null, $pdo ?? null); $layout_header = $platform_layout_header ?? ($repo_root . '/includes/header.php'); $layout_navbar = $platform_layout_navbar ?? ($repo_root . '/includes/navbar.php'); $layout_footer = $platform_layout_footer ?? ($repo_root . '/includes/footer.php'); $dashboard_url = $platform_dashboard_url ?? (function_exists('base_url') ? platform_admin_url('index.php') : 'index.php'); $pageTitle = 'Estado SMTP'; if (is_file($layout_header)) include $layout_header; if (is_file($layout_navbar)) include $layout_navbar; ?>

Estado SMTP

Configuração do transporte de email partilhado e testes de ligação.

Voltar
Configuração PHPMailer
Alterar
No cPanel é tipicamente mail.<dominio>.
Normalmente o endereço de email completo.
Tem de ser a conta autenticada acima, senão SPF/DMARC rejeitam.
Guardado em base de dados, que ganha ao .env. Teste a ligação a seguir.
Testes
Abre e fecha uma ligação. Não envia nada.