['/views/admin/', '/views/sysadmin/'], // ]); require_once __DIR__ . '/functions_settings.php'; require_once __DIR__ . '/functions_mail.php'; // Deliberately NOT requiring functions_auth.php. hub2 already defines its own // auth_login() in config/functions_user.php, so pulling the shared auth in here // would fatal on a redeclare for any site that has not migrated yet -- and the // setup gate has to work on exactly those sites. setup.php needs two column // names, not the whole module. /** * The users table and role column, for the setup page's access check. * * Uses auth_schema() when the site has wired the shared auth, and the same * defaults otherwise. Kept separate from auth_schema() so this file has no * dependency on it: a missing helper must never be the reason a page that * writes SMTP credentials decides nobody is an admin. */ function platform_setup_user_schema(): array { if (function_exists('auth_schema')) { $s = auth_schema(); return ['table' => $s['table'], 'role' => $s['role']]; } return ['table' => 'users', 'role' => 'role']; } /** * Is outbound mail actually configured? * * Deliberately checks *resolved* values, not whether a key exists. Every SMTP * key on three of the four sites existed in .env with an empty value, which a * presence check reads as configured and a value check reads as broken -- that * distinction is what hid the estate-wide outage. * * A host and a from-address are the minimum: without the first nothing can be * sent, and without the second nothing that is sent has a sender. */ function platform_setup_smtp_ok(PDO $pdo): bool { $c = platform_smtp_config($pdo); return trim((string) ($c['host'] ?? '')) !== '' && trim((string) ($c['from'] ?? '')) !== ''; } /** Per-field detail, for rendering the setup form and its warnings. */ function platform_setup_state(PDO $pdo): array { $c = platform_smtp_config($pdo); return [ 'smtp_ok' => platform_setup_smtp_ok($pdo), 'config' => $c, 'secrets_ok' => platform_settings_secret_available(), 'mailer_present' => function_exists('platform_mail_available'), 'missing' => array_values(array_filter([ trim((string) ($c['host'] ?? '')) === '' ? 'smtp_host' : null, trim((string) ($c['from'] ?? '')) === '' ? 'email_from' : null, ])), ]; } /** * Hold the backoffice on the setup page until SMTP is configured. * * `only` lists the path prefixes the gate applies to, and it is the option to * use: the gate belongs on the backoffice, not the public site. A portfolio, a * class timetable or a public tool has no reason to go dark because outbound * mail is unconfigured, and taking the shopfront down to nag the operator * punishes the wrong person. The backoffice is where the operator already is, * and where the setup page is reachable from. * * With `only` omitted the gate covers everything, minus `exempt`. That is the * right shape only for an install with no public face at all. * * Two exclusions are unconditional. CLI is never gated: a cron worker or a * migration must still run on a half-configured install, and a redirect means * nothing without a browser. And a caller that wants JSON gets a 503, never a * 302 to an HTML page -- anything expecting JSON would parse that as success. */ function platform_setup_guard(PDO $pdo, string $setupUrl, array $opts = []): void { if (PHP_SAPI === 'cli') return; static $checked = false; if ($checked) return; $checked = true; if (platform_setup_smtp_ok($pdo)) return; $path = (string) parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH); // Never redirect the setup page to itself. $setupPath = (string) parse_url($setupUrl, PHP_URL_PATH); if ($setupPath !== '' && str_starts_with($path, $setupPath)) return; // Scoped mode: anything outside the listed prefixes is public, and stays up. $only = (array) ($opts['only'] ?? []); if ($only) { $inScope = false; foreach ($only as $prefix) { if ($prefix !== '' && str_contains($path, (string) $prefix)) { $inScope = true; break; } } if (!$inScope) return; } foreach ((array) ($opts['exempt'] ?? []) as $prefix) { if ($prefix !== '' && str_contains($path, (string) $prefix)) return; } // A machine caller gets a machine answer. A 302 to an HTML page would be // parsed as a successful response by anything expecting JSON. $wantsJson = str_contains((string) ($_SERVER['HTTP_ACCEPT'] ?? ''), 'application/json') || str_contains($path, '/api/'); if ($wantsJson) { http_response_code(503); header('Content-Type: application/json; charset=utf-8'); echo json_encode([ 'status' => 'error', 'error' => 'Plataforma por configurar: falta o SMTP.', ]); exit; } header('Location: ' . $setupUrl); exit; } /** * Save the SMTP settings. * * Writes through platform_settings_set(), so smtp_password is encrypted at * rest by the estate-wide policy rather than by anything decided here. An empty * password means "leave the stored one alone" -- the field is never rendered * with the real value, so blank cannot mean "clear it". */ function platform_setup_save_smtp(PDO $pdo, array $in, ?int $userId = null): array { $host = trim((string) ($in['smtp_host'] ?? '')); $from = trim((string) ($in['email_from'] ?? '')); $port = (int) ($in['smtp_port'] ?? 587); $errors = []; if ($host === '') $errors[] = 'Indique o servidor SMTP.'; if ($from === '' || !filter_var($from, FILTER_VALIDATE_EMAIL)) $errors[] = 'Indique um remetente válido.'; if ($port < 1 || $port > 65535) $errors[] = 'Porta inválida.'; if ($errors) return ['ok' => false, 'errors' => $errors]; platform_settings_set($pdo, 'smtp_host', $host, $userId); platform_settings_set($pdo, 'smtp_port', (string) $port, $userId); platform_settings_set($pdo, 'smtp_secure', (string) ($in['smtp_secure'] ?? 'tls'), $userId); platform_settings_set($pdo, 'smtp_username', trim((string) ($in['smtp_username'] ?? '')), $userId); platform_settings_set($pdo, 'email_from', $from, $userId); platform_settings_set($pdo, 'email_from_name', trim((string) ($in['email_from_name'] ?? '')), $userId); $pw = (string) ($in['smtp_password'] ?? ''); if ($pw !== '') { // platform_settings_is_secret('smtp_password') is true, so this is // encrypted on the way in -- no flag passed, no caller decision. platform_settings_set($pdo, 'smtp_password', $pw, $userId); } return ['ok' => true, 'errors' => []]; } /** Connect-and-authenticate probe against the values just saved. */ function platform_setup_test_smtp(PDO $pdo, string $repoRoot = ''): array { return platform_mail_test($repoRoot, platform_smtp_config($pdo)); }