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:
commit
e68d357177
2330 files changed
+215968
No files matched your search
@@ -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']}");
|
||||
});
|
||||
Reference in new issue
Block a user