Initial import of fitme3070.pt platform

Plain-PHP fleet platform with id.andmore.pt SSO. Wires shared/platform-tools
as a submodule (platform_update, backups, git/migration tools) like the other
platforms.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
andreandClaude Opus 4.8 committed 2026-06-12 10:44:58 +01:00
commit e68d357177
2330 files changed
+215968

No files matched your search

+153
View File
@@ -0,0 +1,153 @@
<?php
// ─── Global error / exception handler ────────────────────────────────────────
//
// Registered early in config/config.php (after APP_ENV is defined).
// Prod: renders a clean branded page, hides all technical detail.
// Dev: renders the same page but expands a debug panel with full trace.
// All errors are written to logs/php_errors.jsonl (max 500 entries, rotated).
// ─────────────────────────────────────────────────────────────────────────────
define('ERROR_LOG_FILE', __DIR__ . '/../logs/php_errors.jsonl');
define('ERROR_LOG_MAX', 500);
function _error_log_write(string $type, string $message, string $file = '', int $line = 0, string $trace = ''): void {
$userId = isset($_SESSION['user_id']) ? (int) $_SESSION['user_id'] : null;
$userName = isset($_SESSION['user']['name']) ? (string) $_SESSION['user']['name'] : null;
$url = (isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '') ;
$ip = $_SERVER['REMOTE_ADDR'] ?? '';
$entry = json_encode([
'ts' => date('c'),
'type' => $type,
'message' => $message,
'file' => $file,
'line' => $line,
'trace' => $trace,
'user_id' => $userId,
'user_name' => $userName,
'url' => $url,
'ip' => $ip,
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
$logFile = ERROR_LOG_FILE;
// Append then rotate if over limit.
file_put_contents($logFile, $entry . "\n", FILE_APPEND | LOCK_EX);
// Trim to last ERROR_LOG_MAX lines when the file grows large.
if (filesize($logFile) > 256 * 1024) {
$lines = file($logFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
if (count($lines) > ERROR_LOG_MAX) {
$lines = array_slice($lines, -ERROR_LOG_MAX);
file_put_contents($logFile, implode("\n", $lines) . "\n", LOCK_EX);
}
}
}
function _error_page(string $title, string $detail, string $trace = ''): void {
$isProd = defined('APP_ENV') && APP_ENV === 'prod';
$cssBase = (defined('APP_BASE_URL') ? rtrim(APP_BASE_URL, '/') : '');
http_response_code(500);
if (ob_get_level()) ob_end_clean();
?>
<!doctype html>
<html lang="pt">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Erro — <?= htmlspecialchars(defined('APP_NAME') ? APP_NAME : 'Plataforma') ?></title>
<?php if ($cssBase):
$_eh_brand = (function_exists('env') ? env('BRAND', 'andmore') : 'andmore') ?: 'andmore';
?>
<link rel="stylesheet" href="<?= htmlspecialchars($cssBase) ?>/assets/css/branding-<?= htmlspecialchars($_eh_brand) ?>.css">
<link rel="stylesheet" href="<?= htmlspecialchars($cssBase) ?>/assets/css/style.css">
<?php endif; ?>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
<style>
body { min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 2rem; }
.err-card { max-width: 540px; width: 100%; text-align: center; }
.err-icon { font-size: 3rem; margin-bottom: 1rem; opacity: .6; }
.err-debug { text-align: left; font-size: .78rem; font-family: monospace;
white-space: pre-wrap; word-break: break-all;
max-height: 320px; overflow-y: auto;
background: rgba(0,0,0,.25); border-radius: 6px; padding: 1rem; margin-top: 1rem; }
</style>
</head>
<body>
<div class="err-card">
<div class="err-icon"><i class="bi bi-exclamation-triangle"></i></div>
<h2 class="mb-2">Algo correu mal</h2>
<?php if ($isProd): ?>
<p class="text-muted">Ocorreu um erro inesperado. Por favor tente novamente ou contacte o suporte.</p>
<?php else: ?>
<p class="text-muted"><?= htmlspecialchars($title) ?></p>
<?php if ($detail): ?>
<div class="err-debug"><?= htmlspecialchars($detail) ?></div>
<?php endif; ?>
<?php if ($trace): ?>
<div class="err-debug" style="margin-top:.5rem; opacity:.7;"><?= htmlspecialchars($trace) ?></div>
<?php endif; ?>
<?php endif; ?>
<div class="mt-4 d-flex gap-2 justify-content-center flex-wrap">
<button onclick="history.back()" class="btn btn-outline-secondary"><i class="bi bi-arrow-left me-1"></i>Voltar</button>
<a href="javascript:location.reload()" class="btn btn-outline-secondary"><i class="bi bi-arrow-clockwise me-1"></i>Tentar novamente</a>
</div>
</div>
</body>
</html>
<?php
exit(1);
}
// ── Uncaught exceptions ───────────────────────────────────────────────────────
set_exception_handler(function (Throwable $e): void {
_error_log_write(
get_class($e),
$e->getMessage(),
$e->getFile(),
$e->getLine(),
$e->getTraceAsString()
);
_error_page(
get_class($e) . ': ' . $e->getMessage(),
$e->getMessage() . "\n\nFicheiro: " . $e->getFile() . ':' . $e->getLine(),
$e->getTraceAsString()
);
});
// ── PHP errors ────────────────────────────────────────────────────────────────
set_error_handler(function (int $errno, string $errstr, string $errfile, int $errline): bool {
if (!(error_reporting() & $errno)) return false;
$label = match(true) {
(bool)($errno & E_NOTICE) => 'Notice',
(bool)($errno & E_WARNING) => 'Warning',
(bool)($errno & E_DEPRECATED) => 'Deprecated',
(bool)($errno & E_USER_NOTICE) => 'User Notice',
(bool)($errno & E_USER_WARNING) => 'User Warning',
(bool)($errno & E_USER_DEPRECATED)=> 'User Deprecated',
default => "Error ($errno)",
};
_error_log_write($label, $errstr, $errfile, $errline);
// In prod, only show error page for fatal-level user errors.
if (defined('APP_ENV') && APP_ENV === 'prod') {
$fatal = E_ERROR | E_PARSE | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR;
if (!($errno & $fatal)) return false;
}
_error_page("PHP $label: $errstr", "$errstr\n\nFicheiro: $errfile:$errline");
return true;
});
// ── Fatal errors (parse errors, out-of-memory, etc.) ─────────────────────────
register_shutdown_function(function (): void {
$e = error_get_last();
if (!$e) return;
$fatal = E_ERROR | E_PARSE | E_CORE_ERROR | E_COMPILE_ERROR;
if (!($e['type'] & $fatal)) return;
_error_log_write('Fatal', $e['message'], $e['file'], $e['line']);
_error_page("Fatal Error: {$e['message']}", "{$e['message']}\n\nFicheiro: {$e['file']}:{$e['line']}");
});