true]) em config/db.php — já não // há um functions_remember.php local (auditoria 1.3). require_once __DIR__ . '/functions_throttle.php'; // per-IP attempt throttle for the auth endpoints require_once __DIR__ . '/functions_migrations.php';// platform_update.php migrations runner // Outbound delivery happens in this app — PHPMailer over SMTP, no central relay. require_once __DIR__ . '/functions_queue.php'; // message_queue + enqueue_*/flush_message require_once __DIR__ . '/functions_csrf.php'; require_once __DIR__ . '/functions_html.php'; // allowlist HTML sanitiser (clean_richtext/clean_map_embed) require_once __DIR__ . '/ci_compat.php'; // CI4 view-helper shim (script_tag/link_tag/view/esc/session) require_once __DIR__ . '/functions_gyms.php'; // multi-gym: gyms, URL folders, admin gym switcher require_once __DIR__ . '/functions_site.php'; // public-site data (site_info/offer/team/modalidades/horario) require_once __DIR__ . '/functions_images.php'; // upload resize/compress (gallery/team/modalidades caps) require_once __DIR__ . '/functions_team.php'; // team CRUD require_once __DIR__ . '/functions_modalidades.php'; // modalidades CRUD require_once __DIR__ . '/functions_galerias.php'; // galleries + images CRUD require_once __DIR__ . '/functions_work_hours.php'; // work hours CRUD require_once __DIR__ . '/functions_horario.php'; // class schedule CRUD require_once __DIR__ . '/functions_events.php'; // time-limited events + landing pages + leads require_once __DIR__ . '/functions_info.php'; // contactos (fitme3070_info) editor require_once __DIR__ . '/functions_memberships.php'; // mensalidades + pedidos de inscrição require_once __DIR__ . '/functions_reviews.php'; // reviews Google (Places API) + cache require_once __DIR__ . '/functions_trials.php'; // pedidos de aula experimental require_once __DIR__ . '/functions_privacy.php'; // mapa e fotos da Google só a pedido / servidos localmente require_once __DIR__ . '/functions_forms.php'; // anti-spam + bloqueio de IP dos formulários públicos // ─── DB-backed settings → legacy constants ─────────────────────────────────── // Resolved once from the settings cache so call sites read plain constants. define('APP_NAME', (string) setting('app_name', 'FitMe 3070')); // APP_BASE_URL is per-install (deployment topology). .env only — never the DB, // so a reused dump can't drag another install's URL in. // Auto-detect from the request if .env doesn't have it (dev/staging safety net). $_aburl = env('APP_BASE_URL', ''); if ($_aburl === '' && isset($_SERVER['HTTP_HOST'])) { $scriptDir = dirname($_SERVER['SCRIPT_NAME'] ?? ''); $_aburl = (request_is_https() ? 'https' : 'http') . '://' . $_SERVER['HTTP_HOST'] . rtrim($scriptDir, '/') . '/'; } define('APP_BASE_URL', $_aburl); define('EMAIL_FROM', (string) setting('email_from', 'noreply@andmore.pt')); define('EMAIL_FROM_NAME', (string) setting('email_from_name', 'FitMe 3070')); define('EMAIL_SUBJECT_PREFIX', (string) setting('email_subject_prefix', '[FitMe 3070] ')); // ─── SMTP ─────────────────────────────────────────────────────────── // .env wins, then system_settings (smtp_password encrypted at rest). These // constants are what the diagnostics page shows; the actual send resolves // through platform_smtp_config($pdo), which is DB-first — that is the value an // operator can change from the backoffice without shell access. // An empty SMTP_HOST makes PHPMailer fall back to PHP mail(). define('SMTP_HOST', setting_or_env('smtp_host', 'SMTP_HOST')); define('SMTP_PORT', (int) (setting_or_env('smtp_port', 'SMTP_PORT', '587') ?: 587)); define('SMTP_SECURE', setting_or_env('smtp_secure', 'SMTP_SECURE', 'tls')); define('SMTP_USERNAME', setting_or_env('smtp_username', 'SMTP_USERNAME')); define('SMTP_PASSWORD', setting_secret('smtp_password', 'SMTP_PASSWORD')); // ─── Twilio (SMS / WhatsApp) ─────────────────────────────────────── // Optional. Left empty the sms/whatsapp drivers refuse with a clear message // instead of half-sending; email is unaffected. define('TWILIO_SID', setting_or_env('twilio_sid', 'TWILIO_SID')); define('TWILIO_TOKEN', setting_secret('twilio_token', 'TWILIO_TOKEN')); define('TWILIO_SMS_FROM', setting_or_env('twilio_sms_from', 'TWILIO_SMS_FROM')); define('TWILIO_WHATSAPP_FROM', setting_or_env('twilio_whatsapp_from', 'TWILIO_WHATSAPP_FROM')); // ─── Dev safety nets ─────────────────────────────────────────────── // Only consulted when APP_ENV !== 'prod', so a misconfigured production install // can never silently swallow mail. // EMAIL_DEV_PRINT — skip SMTP entirely, write to logs/message_dev.log. // EMAIL_DEV_REDIRECT — send everything to this address instead of the real one. define('EMAIL_DEV_PRINT', filter_var(env('EMAIL_DEV_PRINT', 'false'), FILTER_VALIDATE_BOOLEAN)); define('EMAIL_DEV_REDIRECT', (string) env('EMAIL_DEV_REDIRECT', '')); // ─── URL helper ─────────────────────────────────────────────────────────────── // Always use for Location: redirects and internal links. function base_url(string $path = ''): string { return APP_BASE_URL . ltrim($path, '/'); } // ─── Date helper (PT-PT) ────────────────────────────────────────────────────── function format_date_pt(string $date): string { if (!$date) return ''; return (new DateTime($date))->format('d/m/Y'); } // ─── Email template loader ──────────────────────────────────────────────────── // {{placeholder}} HTML bodies under templates/emails/. Built locally, delivered // by id.andmore.pt via the enqueue_email()/flush_message() façade. function load_email_template(string $template, array $vars): string { $path = __DIR__ . '/../templates/emails/' . $template . '.html'; if (!is_file($path)) { throw new RuntimeException("Email template not found: {$template}"); } $html = file_get_contents($path); $brand = htmlspecialchars(APP_NAME, ENT_QUOTES, 'UTF-8'); $logo = (string) setting('logo_light_filename', ''); $logoTag = $logo !== '' ? '' . $brand . '' : '

' . $brand . '

'; $html = str_replace('{{logo_html}}', $logoTag, $html); foreach ($vars as $key => $value) { $html = str_replace('{{' . $key . '}}', htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8'), $html); } return $html; } // ─── User-agent prettifier (last-login display) ────────────────────────────── // Mirrors fotografia's helper so views/user/index.php can show a short label. function parse_user_agent(string $ua): string { if ($ua === '') return '—'; $browser = 'Navegador'; foreach (['Edg' => 'Edge', 'OPR' => 'Opera', 'Chrome' => 'Chrome', 'Firefox' => 'Firefox', 'Safari' => 'Safari'] as $needle => $label) { if (stripos($ua, $needle) !== false) { $browser = $label; break; } } $os = 'SO'; foreach (['Windows' => 'Windows', 'Android' => 'Android', 'iPhone' => 'iOS', 'iPad' => 'iOS', 'Mac OS' => 'macOS', 'Linux' => 'Linux'] as $needle => $label) { if (stripos($ua, $needle) !== false) { $os = $label; break; } } return "$browser / $os"; }