update
This commit is contained in:
parent
fe06e618d4
commit
06c852731f
518
index.php
518
index.php
@ -1,6 +1,154 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
$GLOBALS['app_runtime_debug_stage'] = 'boot:script_loaded';
|
||||
$GLOBALS['app_runtime_debug_timeline'] = [
|
||||
['time' => date('H:i:s'), 'stage' => 'boot:script_loaded'],
|
||||
];
|
||||
$GLOBALS['app_runtime_debug_rendered'] = false;
|
||||
|
||||
if (!function_exists('runtime_debug_boot_mark')) {
|
||||
function runtime_debug_boot_mark(string $stage, array $context = []): void {
|
||||
$GLOBALS['app_runtime_debug_stage'] = $stage;
|
||||
$timeline = $GLOBALS['app_runtime_debug_timeline'] ?? [];
|
||||
$entry = [
|
||||
'time' => date('H:i:s'),
|
||||
'stage' => $stage,
|
||||
];
|
||||
|
||||
if ($context !== []) {
|
||||
$entry['context'] = $context;
|
||||
}
|
||||
|
||||
$timeline[] = $entry;
|
||||
if (count($timeline) > 20) {
|
||||
$timeline = array_slice($timeline, -20);
|
||||
}
|
||||
|
||||
$GLOBALS['app_runtime_debug_timeline'] = $timeline;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('runtime_debug_boot_force_details')) {
|
||||
function runtime_debug_boot_force_details(): bool {
|
||||
$candidates = [
|
||||
$_GET['debug'] ?? null,
|
||||
$_GET['app_debug'] ?? null,
|
||||
getenv('APP_RUNTIME_DEBUG'),
|
||||
$_ENV['APP_RUNTIME_DEBUG'] ?? null,
|
||||
$_SERVER['APP_RUNTIME_DEBUG'] ?? null,
|
||||
];
|
||||
|
||||
foreach ($candidates as $candidate) {
|
||||
if ($candidate === false || $candidate === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$value = strtolower(trim((string)$candidate));
|
||||
if (in_array($value, ['1', 'true', 'yes', 'on'], true)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!defined('APP_RUNTIME_DEBUG_BOOTSTRAP_SHUTDOWN_REGISTERED')) {
|
||||
define('APP_RUNTIME_DEBUG_BOOTSTRAP_SHUTDOWN_REGISTERED', true);
|
||||
register_shutdown_function(static function (): void {
|
||||
if (!empty($GLOBALS['app_runtime_debug_rendered']) || defined('APP_RUNTIME_DEBUG_FATAL_HANDLER_REGISTERED')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$error = error_get_last();
|
||||
if (!is_array($error)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$fatalTypes = [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR, E_RECOVERABLE_ERROR];
|
||||
if (!in_array((int)($error['type'] ?? 0), $fatalTypes, true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$stage = (string)($GLOBALS['app_runtime_debug_stage'] ?? 'unknown');
|
||||
$timeline = $GLOBALS['app_runtime_debug_timeline'] ?? [];
|
||||
$parts = [
|
||||
date('Y-m-d H:i:s'),
|
||||
'[FatalError]',
|
||||
(string)($error['message'] ?? 'Fatal error'),
|
||||
'file=' . (string)($error['file'] ?? 'unknown') . ':' . (int)($error['line'] ?? 0),
|
||||
'page=' . (string)($_GET['page'] ?? 'dashboard'),
|
||||
'uri=' . (string)($_SERVER['REQUEST_URI'] ?? 'cli'),
|
||||
'stage=' . $stage,
|
||||
];
|
||||
|
||||
if ($timeline !== []) {
|
||||
$parts[] = 'timeline=' . json_encode(array_slice($timeline, -8), JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
|
||||
@file_put_contents(__DIR__ . '/runtime_debug.log', implode(' || ', $parts) . PHP_EOL, FILE_APPEND);
|
||||
|
||||
if (!headers_sent()) {
|
||||
http_response_code(500);
|
||||
header('Content-Type: text/html; charset=UTF-8');
|
||||
header('X-Robots-Tag: noindex, nofollow');
|
||||
}
|
||||
|
||||
$roleName = (string)($_SESSION['user_role_name'] ?? '');
|
||||
$showDetails = runtime_debug_boot_force_details()
|
||||
|| PHP_SAPI === 'cli'
|
||||
|| strcasecmp($roleName, 'Administrator') === 0
|
||||
|| (int)($_SESSION['user_id'] ?? 0) === 1;
|
||||
|
||||
$message = (string)($error['message'] ?? 'Fatal error');
|
||||
$safeMessage = $showDetails
|
||||
? $message
|
||||
: 'A fatal application error occurred before the page finished loading.';
|
||||
$timelineText = json_encode(array_slice($timeline, -8), JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
|
||||
|
||||
$GLOBALS['app_runtime_debug_rendered'] = true;
|
||||
?>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
<title>Application Boot Error</title>
|
||||
<style>
|
||||
body { margin: 0; font-family: Inter, Arial, sans-serif; background: #f6f8fb; color: #1f2937; }
|
||||
.wrap { max-width: 860px; margin: 48px auto; padding: 0 20px; }
|
||||
.card { background: #fff; border-radius: 18px; box-shadow: 0 18px 60px rgba(15, 23, 42, 0.08); padding: 28px; }
|
||||
h1 { margin: 0 0 12px; font-size: 28px; }
|
||||
.meta { margin: 12px 0; color: #374151; }
|
||||
.meta strong { color: #111827; }
|
||||
.hint { margin-top: 18px; padding: 14px 16px; border-radius: 14px; background: #fff7ed; color: #9a3412; border: 1px solid #fed7aa; }
|
||||
pre { margin: 18px 0 0; padding: 16px; background: #0f172a; color: #e2e8f0; border-radius: 14px; overflow: auto; font-size: 12px; line-height: 1.5; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main class="wrap">
|
||||
<section class="card">
|
||||
<h1>Application Boot Error</h1>
|
||||
<p>The request failed before the main page finished loading.</p>
|
||||
<div class="meta"><strong>Stage:</strong> <?= htmlspecialchars($stage) ?></div>
|
||||
<div class="meta"><strong>Message:</strong> <?= htmlspecialchars($safeMessage) ?></div>
|
||||
<div class="hint">A copy of this failure was written to <code>runtime_debug.log</code>.</div>
|
||||
<?php if ($showDetails && $timelineText !== false && $timelineText !== '[]'): ?>
|
||||
<pre><?= htmlspecialchars((string)$timelineText) ?></pre>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
<?php
|
||||
exit;
|
||||
});
|
||||
}
|
||||
|
||||
runtime_debug_boot_mark('boot:session_setup');
|
||||
|
||||
// Sessions setup
|
||||
$sessions_dir = __DIR__ . '/sessions';
|
||||
if (!is_dir($sessions_dir)) {
|
||||
@ -23,6 +171,8 @@ if (!empty($missing_extensions)) {
|
||||
die("Error: The following PHP extensions are required but missing: " . implode(', ', $missing_extensions) . ". Please contact your hosting provider to enable them.");
|
||||
}
|
||||
|
||||
runtime_debug_boot_mark('boot:extensions_ready');
|
||||
|
||||
// Enhanced session security and iframe compatibility
|
||||
if ((isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on') || (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https')) {
|
||||
session_set_cookie_params([
|
||||
@ -35,12 +185,15 @@ if ((isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on') || (isset($_SERVER[
|
||||
}
|
||||
|
||||
session_start();
|
||||
runtime_debug_boot_mark('boot:session_started');
|
||||
|
||||
require_once __DIR__ . '/includes/page_routes.php';
|
||||
if (!defined('APP_ROUTE_BOOTSTRAP')) {
|
||||
page_redirect_legacy_url();
|
||||
}
|
||||
|
||||
runtime_debug_boot_mark('boot:routes_ready');
|
||||
|
||||
if (!function_exists('app_file_debug_logging_enabled')) {
|
||||
function app_file_debug_logging_enabled(): bool {
|
||||
static $enabled = null;
|
||||
@ -104,12 +257,15 @@ if (isset($_GET['action']) && $_GET['action'] === 'download_items_template') {
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
app_debug_file_log('post_debug.log', date('Y-m-d H:i:s') . " - POST: " . json_encode($_POST));
|
||||
}
|
||||
runtime_debug_boot_mark('boot:loading_core_dependencies');
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
require_once __DIR__ . '/includes/SimpleXLSX.php';
|
||||
require_once __DIR__ . '/includes/stock_helper.php';
|
||||
require_once __DIR__ . '/includes/wablas_helper.php';
|
||||
require_once __DIR__ . '/db/BackupService.php';
|
||||
|
||||
runtime_debug_boot_mark('boot:core_dependencies_loaded');
|
||||
|
||||
// Helper for current outlet
|
||||
if (!function_exists('current_outlet_id')) {
|
||||
function current_outlet_id() {
|
||||
@ -362,8 +518,175 @@ if (!function_exists('line_item_vat_amount')) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('runtime_debug_mark')) {
|
||||
function runtime_debug_mark(string $stage, array $context = []): void {
|
||||
runtime_debug_boot_mark($stage, $context);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('runtime_debug_current_stage')) {
|
||||
function runtime_debug_current_stage(): string {
|
||||
return (string)($GLOBALS['app_runtime_debug_stage'] ?? 'unknown');
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('runtime_debug_recent_timeline')) {
|
||||
function runtime_debug_recent_timeline(int $limit = 8): array {
|
||||
$timeline = $GLOBALS['app_runtime_debug_timeline'] ?? [];
|
||||
if (!is_array($timeline)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return array_slice($timeline, -max(1, $limit));
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('runtime_debug_force_details_enabled')) {
|
||||
function runtime_debug_force_details_enabled(): bool {
|
||||
return runtime_debug_boot_force_details();
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('runtime_debug_require')) {
|
||||
function runtime_debug_require(string $file, array $context = []): void {
|
||||
runtime_debug_mark('require:' . basename($file), $context + ['file' => $file]);
|
||||
require $file;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('runtime_debug_extract_table_name')) {
|
||||
function runtime_debug_extract_table_name(Throwable $throwable): ?string {
|
||||
$message = $throwable->getMessage();
|
||||
$patterns = [
|
||||
"/Table '[^']+\.([^']+)' doesn't exist/i",
|
||||
'/(?:INSERT\s+INTO|UPDATE|FROM|JOIN|DELETE\s+FROM|ALTER\s+TABLE)\s+`?([a-zA-Z0-9_]+)`?/i',
|
||||
];
|
||||
|
||||
foreach ($patterns as $pattern) {
|
||||
if (preg_match($pattern, $message, $matches)) {
|
||||
return (string)$matches[1];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('runtime_debug_extract_column_name')) {
|
||||
function runtime_debug_extract_column_name(Throwable $throwable): ?string {
|
||||
$message = $throwable->getMessage();
|
||||
|
||||
if (preg_match("/Unknown column '([^']+)'/i", $message, $matches)) {
|
||||
return (string)$matches[1];
|
||||
}
|
||||
|
||||
if (preg_match('/Column not found: [0-9]+ ([a-zA-Z0-9_]+)/i', $message, $matches)) {
|
||||
return (string)$matches[1];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('runtime_debug_find_related_migrations')) {
|
||||
function runtime_debug_find_related_migrations(?string $tableName, ?string $columnName = null, int $limit = 6): array {
|
||||
$directory = __DIR__ . '/db/migrations';
|
||||
if (!is_dir($directory)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$needles = array_values(array_filter([
|
||||
$tableName ? strtolower($tableName) : null,
|
||||
$columnName ? strtolower($columnName) : null,
|
||||
]));
|
||||
|
||||
if ($needles === []) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$matches = [];
|
||||
foreach (glob($directory . '/*.{sql,php}', GLOB_BRACE) ?: [] as $migrationPath) {
|
||||
if (!is_readable($migrationPath)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$contents = @file_get_contents($migrationPath);
|
||||
if ($contents === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$haystack = strtolower($contents);
|
||||
$matched = true;
|
||||
foreach ($needles as $needle) {
|
||||
if (!str_contains($haystack, $needle)) {
|
||||
$matched = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($matched) {
|
||||
$matches[] = basename($migrationPath);
|
||||
if (count($matches) >= $limit) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $matches;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('runtime_debug_schema_snapshot')) {
|
||||
function runtime_debug_schema_snapshot(Throwable $throwable): array {
|
||||
$tableName = runtime_debug_extract_table_name($throwable);
|
||||
$columnName = runtime_debug_extract_column_name($throwable);
|
||||
$snapshot = [
|
||||
'table' => $tableName,
|
||||
'column' => $columnName,
|
||||
'table_exists' => null,
|
||||
'columns' => [],
|
||||
'database_error' => null,
|
||||
'related_migrations' => runtime_debug_find_related_migrations($tableName, $columnName),
|
||||
];
|
||||
|
||||
if ($tableName === null) {
|
||||
return $snapshot;
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
$snapshot['table_exists'] = db_table_exists($tableName);
|
||||
if ($snapshot['table_exists']) {
|
||||
$stmt = $pdo->prepare(
|
||||
"SELECT COLUMN_NAME FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? ORDER BY ORDINAL_POSITION"
|
||||
);
|
||||
$stmt->execute([$tableName]);
|
||||
$snapshot['columns'] = array_map('strval', $stmt->fetchAll(PDO::FETCH_COLUMN) ?: []);
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
$snapshot['database_error'] = $e->getMessage();
|
||||
}
|
||||
|
||||
return $snapshot;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('runtime_debug_is_fatal_error')) {
|
||||
function runtime_debug_is_fatal_error($error): bool {
|
||||
if (!is_array($error)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return in_array((int)($error['type'] ?? 0), [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR, E_RECOVERABLE_ERROR], true);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('runtime_debug_can_render_details')) {
|
||||
function runtime_debug_can_render_details(): bool {
|
||||
if (runtime_debug_force_details_enabled()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (PHP_SAPI === 'cli') {
|
||||
return true;
|
||||
}
|
||||
@ -412,6 +735,7 @@ if (!function_exists('runtime_debug_log')) {
|
||||
'page=' . ($_GET['page'] ?? 'dashboard'),
|
||||
'uri=' . ($_SERVER['REQUEST_URI'] ?? 'cli'),
|
||||
'user_id=' . (string)($_SESSION['user_id'] ?? 0),
|
||||
'stage=' . runtime_debug_current_stage(),
|
||||
];
|
||||
|
||||
$hint = runtime_debug_infer_schema_hint($throwable);
|
||||
@ -423,8 +747,12 @@ if (!function_exists('runtime_debug_log')) {
|
||||
$parts[] = 'error_info=' . json_encode($throwable->errorInfo, JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
|
||||
$trace = explode("
|
||||
", $throwable->getTraceAsString());
|
||||
$timeline = runtime_debug_recent_timeline(10);
|
||||
if ($timeline !== []) {
|
||||
$parts[] = 'timeline=' . json_encode($timeline, JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
|
||||
$trace = explode("\n", $throwable->getTraceAsString());
|
||||
if ($trace !== []) {
|
||||
$parts[] = 'trace=' . implode(' | ', array_slice($trace, 0, 5));
|
||||
}
|
||||
@ -435,6 +763,17 @@ if (!function_exists('runtime_debug_log')) {
|
||||
|
||||
if (!function_exists('runtime_debug_render_exception')) {
|
||||
function runtime_debug_render_exception(Throwable $throwable): void {
|
||||
if (!empty($GLOBALS['app_runtime_debug_rendered'])) {
|
||||
if (!headers_sent()) {
|
||||
http_response_code(500);
|
||||
header('Content-Type: text/plain; charset=UTF-8');
|
||||
}
|
||||
|
||||
echo 'Application Error';
|
||||
exit;
|
||||
}
|
||||
|
||||
$GLOBALS['app_runtime_debug_rendered'] = true;
|
||||
runtime_debug_log($throwable);
|
||||
$showDetails = runtime_debug_can_render_details();
|
||||
|
||||
@ -451,14 +790,19 @@ if (!function_exists('runtime_debug_render_exception')) {
|
||||
$hint = runtime_debug_infer_schema_hint($throwable);
|
||||
$requestUri = (string)($_SERVER['REQUEST_URI'] ?? 'cli');
|
||||
$page = (string)($_GET['page'] ?? 'dashboard');
|
||||
$tracePreview = array_slice(explode("
|
||||
", $throwable->getTraceAsString()), 0, 8);
|
||||
$traceText = implode("
|
||||
", $tracePreview);
|
||||
$currentStage = runtime_debug_current_stage();
|
||||
$timeline = runtime_debug_recent_timeline(10);
|
||||
$timelineText = json_encode($timeline, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
|
||||
$schemaSnapshot = runtime_debug_schema_snapshot($throwable);
|
||||
$tableExistsText = $schemaSnapshot['table_exists'] === null ? 'Unknown' : ($schemaSnapshot['table_exists'] ? 'Yes' : 'No');
|
||||
$schemaColumnsText = !empty($schemaSnapshot['columns']) ? implode(', ', $schemaSnapshot['columns']) : '—';
|
||||
$migrationText = !empty($schemaSnapshot['related_migrations']) ? implode(', ', $schemaSnapshot['related_migrations']) : '—';
|
||||
$tracePreview = array_slice(explode("\n", $throwable->getTraceAsString()), 0, 8);
|
||||
$traceText = implode("\n", $tracePreview);
|
||||
$title = $showDetails ? 'Application Debug' : 'Application Error';
|
||||
$summary = $showDetails
|
||||
? 'The request failed. The details below should help identify the missing table or column.'
|
||||
: 'An unexpected error occurred while loading this page.';
|
||||
? 'The request failed. The details below should help identify the missing table, column, or view file.'
|
||||
: 'An unexpected error occurred while loading this page. The diagnostic snapshot below may still point to what is missing.';
|
||||
?>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
@ -469,9 +813,10 @@ if (!function_exists('runtime_debug_render_exception')) {
|
||||
<title><?= htmlspecialchars($title) ?></title>
|
||||
<style>
|
||||
body { margin: 0; font-family: Inter, Arial, sans-serif; background: #f6f8fb; color: #1f2937; }
|
||||
.wrap { max-width: 900px; margin: 48px auto; padding: 0 20px; }
|
||||
.wrap { max-width: 980px; margin: 48px auto; padding: 0 20px; }
|
||||
.card { background: #fff; border-radius: 18px; box-shadow: 0 18px 60px rgba(15, 23, 42, 0.08); padding: 28px; }
|
||||
h1 { margin: 0 0 10px; font-size: 28px; }
|
||||
h2 { margin: 28px 0 10px; font-size: 18px; color: #111827; }
|
||||
p { color: #4b5563; line-height: 1.6; }
|
||||
.badge { display: inline-block; padding: 6px 10px; border-radius: 999px; background: #fee2e2; color: #991b1b; font-weight: 600; font-size: 13px; }
|
||||
.grid { display: grid; grid-template-columns: 180px 1fr; gap: 12px 16px; margin-top: 22px; }
|
||||
@ -493,7 +838,43 @@ if (!function_exists('runtime_debug_render_exception')) {
|
||||
<h1><?= htmlspecialchars($title) ?></h1>
|
||||
<p><?= htmlspecialchars($summary) ?></p>
|
||||
|
||||
<?php if ($hint !== null): ?>
|
||||
<div class="hint"><strong>Possible issue:</strong> <?= htmlspecialchars($hint) ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="grid">
|
||||
<div class="label">Last boot step</div>
|
||||
<div class="value"><?= htmlspecialchars($currentStage) ?></div>
|
||||
<div class="label">Page</div>
|
||||
<div class="value"><?= htmlspecialchars($page) ?></div>
|
||||
<div class="label">Request URI</div>
|
||||
<div class="value"><?= htmlspecialchars($requestUri) ?></div>
|
||||
<?php if (!empty($schemaSnapshot['table'])): ?>
|
||||
<div class="label">Detected table</div>
|
||||
<div class="value"><?= htmlspecialchars((string)$schemaSnapshot['table']) ?></div>
|
||||
<div class="label">Table exists</div>
|
||||
<div class="value"><?= htmlspecialchars($tableExistsText) ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($schemaSnapshot['column'])): ?>
|
||||
<div class="label">Detected column</div>
|
||||
<div class="value"><?= htmlspecialchars((string)$schemaSnapshot['column']) ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($schemaSnapshot['columns'])): ?>
|
||||
<div class="label">Existing columns</div>
|
||||
<div class="value"><?= htmlspecialchars($schemaColumnsText) ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($schemaSnapshot['related_migrations'])): ?>
|
||||
<div class="label">Likely migration</div>
|
||||
<div class="value"><?= htmlspecialchars($migrationText) ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($schemaSnapshot['database_error'])): ?>
|
||||
<div class="label">DB snapshot error</div>
|
||||
<div class="value"><?= htmlspecialchars((string)$schemaSnapshot['database_error']) ?></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php if ($showDetails): ?>
|
||||
<h2>Full exception</h2>
|
||||
<div class="grid">
|
||||
<div class="label">Exception</div>
|
||||
<div class="value"><?= htmlspecialchars(get_class($throwable)) ?></div>
|
||||
@ -503,31 +884,24 @@ if (!function_exists('runtime_debug_render_exception')) {
|
||||
<div class="value"><?= htmlspecialchars($throwable->getFile()) ?></div>
|
||||
<div class="label">Line</div>
|
||||
<div class="value"><?= (int)$throwable->getLine() ?></div>
|
||||
<div class="label">Page</div>
|
||||
<div class="value"><?= htmlspecialchars($page) ?></div>
|
||||
<div class="label">Request URI</div>
|
||||
<div class="value"><?= htmlspecialchars($requestUri) ?></div>
|
||||
</div>
|
||||
|
||||
<?php if ($hint !== null): ?>
|
||||
<div class="hint"><strong>Schema hint:</strong> <?= htmlspecialchars($hint) ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($traceText !== ''): ?>
|
||||
<pre><?= htmlspecialchars($traceText) ?></pre>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="actions">
|
||||
<a class="btn btn-primary" href="schema_debug.php">Open schema debug report</a>
|
||||
<a class="btn btn-secondary" href="index.php">Retry dashboard</a>
|
||||
</div>
|
||||
|
||||
<p class="note">A copy of this failure was written to <code>runtime_debug.log</code>.</p>
|
||||
<?php else: ?>
|
||||
<div class="actions">
|
||||
<a class="btn btn-secondary" href="index.php">Back to home</a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($timelineText !== false && $timelineText !== '[]'): ?>
|
||||
<h2>Boot timeline</h2>
|
||||
<pre><?= htmlspecialchars((string)$timelineText) ?></pre>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="actions">
|
||||
<a class="btn btn-primary" href="schema_debug.php">Open schema debug report</a>
|
||||
<a class="btn btn-secondary" href="index.php">Retry dashboard</a>
|
||||
</div>
|
||||
|
||||
<p class="note">A copy of this failure was written to <code>runtime_debug.log</code>. For private troubleshooting you can also add <code>?debug=1</code> to the URL to force expanded details.</p>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
@ -544,6 +918,32 @@ if (!defined('APP_RUNTIME_DEBUG_HANDLER_REGISTERED')) {
|
||||
});
|
||||
}
|
||||
|
||||
if (!defined('APP_RUNTIME_DEBUG_FATAL_HANDLER_REGISTERED')) {
|
||||
define('APP_RUNTIME_DEBUG_FATAL_HANDLER_REGISTERED', true);
|
||||
register_shutdown_function(static function (): void {
|
||||
if (!empty($GLOBALS['app_runtime_debug_rendered'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$error = error_get_last();
|
||||
if (!runtime_debug_is_fatal_error($error)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$throwable = new ErrorException(
|
||||
(string)($error['message'] ?? 'Fatal error'),
|
||||
0,
|
||||
(int)($error['type'] ?? E_ERROR),
|
||||
(string)($error['file'] ?? __FILE__),
|
||||
(int)($error['line'] ?? 0)
|
||||
);
|
||||
|
||||
runtime_debug_render_exception($throwable);
|
||||
});
|
||||
}
|
||||
|
||||
runtime_debug_mark('boot:runtime_debug_ready');
|
||||
|
||||
// Handle Outlet Switch
|
||||
if (isset($_GET['action']) && $_GET['action'] === 'switch_outlet' && isset($_GET['id'])) {
|
||||
$target_id = (int)$_GET['id'];
|
||||
@ -579,23 +979,31 @@ try {
|
||||
// Ignore if DB not ready
|
||||
}
|
||||
|
||||
runtime_debug_mark('boot:loading_database_installer');
|
||||
require_once 'includes/DatabaseInstaller.php';
|
||||
runtime_debug_mark('boot:database_installer_loaded');
|
||||
|
||||
// Auto-install database if not installed, then ensure pending migrations are applied.
|
||||
try {
|
||||
if (!DatabaseInstaller::isInstalled()) {
|
||||
runtime_debug_mark('boot:database_installing');
|
||||
DatabaseInstaller::install();
|
||||
} elseif (method_exists('DatabaseInstaller', 'ensureCurrentSchema')) {
|
||||
runtime_debug_mark('boot:database_schema_sync');
|
||||
DatabaseInstaller::ensureCurrentSchema();
|
||||
} else {
|
||||
error_log('Skipping DatabaseInstaller::ensureCurrentSchema() because the loaded DatabaseInstaller class does not define it.');
|
||||
}
|
||||
runtime_debug_mark('boot:database_ready');
|
||||
} catch (Throwable $e) {
|
||||
die("Installation Error: " . $e->getMessage());
|
||||
runtime_debug_mark('boot:database_installer_failed');
|
||||
runtime_debug_render_exception($e);
|
||||
}
|
||||
|
||||
runtime_debug_mark('boot:loading_license_dependencies');
|
||||
require_once 'lib/LicenseService.php';
|
||||
require_once 'includes/lang.php';
|
||||
runtime_debug_mark('boot:license_dependencies_loaded');
|
||||
|
||||
// Language Setup
|
||||
if (isset($_GET['lang'])) {
|
||||
@ -632,12 +1040,16 @@ try {
|
||||
$is_activated = LicenseService::isActivated();
|
||||
$trial_days = LicenseService::getTrialRemainingDays();
|
||||
$can_access = LicenseService::canAccess();
|
||||
runtime_debug_mark('boot:license_validated');
|
||||
} catch (PDOException $e) {
|
||||
die("Database Connection Error: " . $e->getMessage() . "<br><br>Please check your <b>db/config.php</b> settings.");
|
||||
runtime_debug_mark('boot:license_validation_failed', ['reason' => 'pdo']);
|
||||
runtime_debug_render_exception($e);
|
||||
} catch (Exception $e) {
|
||||
die("Application Error: " . $e->getMessage());
|
||||
runtime_debug_mark('boot:license_validation_failed', ['reason' => 'application']);
|
||||
runtime_debug_render_exception($e);
|
||||
}
|
||||
$page = $_GET['page'] ?? 'dashboard';
|
||||
runtime_debug_mark('page:selected', ['page' => (string)$page, 'phase' => 'activation_gate']);
|
||||
|
||||
if (!$can_access && $page !== 'activate') {
|
||||
header("Location: " . page_url("activate"));
|
||||
@ -3357,7 +3769,7 @@ if (isset($_POST['add_hr_department'])) {
|
||||
}
|
||||
}
|
||||
|
||||
require 'pages/settings_save_logic.php';
|
||||
runtime_debug_require('pages/settings_save_logic.php', ['phase' => 'save_logic', 'page' => (string)$page]);
|
||||
|
||||
// --- Backup Handlers ---
|
||||
if (isset($_POST['create_backup'])) {
|
||||
@ -3604,6 +4016,7 @@ if (isset($_POST['add_hr_department'])) {
|
||||
|
||||
// Routing & Data Fetching
|
||||
$page = $_GET['page'] ?? 'dashboard';
|
||||
runtime_debug_mark('page:selected', ['page' => (string)$page, 'phase' => 'data']);
|
||||
|
||||
// Permission map for pages
|
||||
$page_permissions = [
|
||||
@ -3962,6 +4375,8 @@ if ($page === 'export') {
|
||||
exit;
|
||||
}
|
||||
|
||||
runtime_debug_mark('page:shared_data_loading', ['page' => (string)$page]);
|
||||
|
||||
// Global data for modals
|
||||
$current_oid = current_outlet_id();
|
||||
$stmt = db()->prepare("SELECT * FROM stock_categories WHERE outlet_id = ? ORDER BY name_en ASC");
|
||||
@ -3998,6 +4413,7 @@ $limit = isset($_GET["limit"]) ? max(5, (int)$_GET["limit"]) : 20;
|
||||
$page_num = isset($_GET["p"]) ? (int)$_GET["p"] : 1;
|
||||
if ($page_num < 1) $page_num = 1;
|
||||
$offset = ($page_num - 1) * $limit;
|
||||
runtime_debug_mark('page:data_switch_loading', ['page' => (string)$page]);
|
||||
switch ($page) {
|
||||
case 'suppliers':
|
||||
$supplierTaxColumn = entity_tax_column('suppliers');
|
||||
@ -4244,7 +4660,7 @@ switch ($page) {
|
||||
$stmt->execute();
|
||||
$data['outlets'] = $stmt->fetchAll();
|
||||
break;
|
||||
case 'copy_outlet_data': require 'pages/copy_outlet_data_logic.php'; break;
|
||||
case 'copy_outlet_data': runtime_debug_require('pages/copy_outlet_data_logic.php', ['phase' => 'logic', 'page' => (string)$page]); break;
|
||||
case 'settings':
|
||||
// Already fetched globally
|
||||
break;
|
||||
@ -4255,7 +4671,7 @@ switch ($page) {
|
||||
break;
|
||||
case 'sales':
|
||||
case 'purchases':
|
||||
require 'pages/sales_purchases_logic.php';
|
||||
runtime_debug_require('pages/sales_purchases_logic.php', ['phase' => 'logic', 'page' => (string)$page]);
|
||||
break;
|
||||
|
||||
case 'sales_returns':
|
||||
@ -4394,7 +4810,7 @@ switch ($page) {
|
||||
$data['role_groups'] = db()->query("SELECT * FROM role_groups ORDER BY name ASC")->fetchAll();
|
||||
break;
|
||||
case 'users':
|
||||
require 'pages/users_logic.php';
|
||||
runtime_debug_require('pages/users_logic.php', ['phase' => 'logic', 'page' => (string)$page]);
|
||||
break;
|
||||
case 'backups':
|
||||
$data['backups'] = BackupService::getBackups();
|
||||
@ -4403,7 +4819,7 @@ switch ($page) {
|
||||
$data['backup_settings'] = $stmt->fetchAll(PDO::FETCH_KEY_PAIR);
|
||||
break;
|
||||
case 'accounting':
|
||||
require 'pages/accounting_logic.php';
|
||||
runtime_debug_require('pages/accounting_logic.php', ['phase' => 'logic', 'page' => (string)$page]);
|
||||
break;
|
||||
case 'expense_report':
|
||||
$start_date = $_GET['start_date'] ?? date('Y-m-01');
|
||||
@ -4691,7 +5107,9 @@ switch ($page) {
|
||||
break;
|
||||
}
|
||||
|
||||
runtime_debug_mark('page:data_loaded', ['page' => (string)$page]);
|
||||
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
|
||||
runtime_debug_mark('page:rendering', ['page' => (string)$page]);
|
||||
?>
|
||||
<!doctype html>
|
||||
<html lang="<?= $lang ?>" dir="<?= $dir ?>">
|
||||
@ -8077,7 +8495,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
|
||||
</div>
|
||||
|
||||
<?php elseif ($page === 'sales' || $page === 'purchases'): ?>
|
||||
<?php require 'pages/sales_purchases_view.php'; ?>
|
||||
<?php runtime_debug_require('pages/sales_purchases_view.php', ['phase' => 'view', 'page' => (string)$page]); ?>
|
||||
|
||||
<?php elseif ($page === 'customer_statement' || $page === 'supplier_statement'): ?>
|
||||
<div class="card p-4">
|
||||
@ -8579,7 +8997,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
|
||||
</div>
|
||||
|
||||
<?php elseif ($page === 'accounting'): ?>
|
||||
<?php require 'pages/accounting_view.php'; ?>
|
||||
<?php runtime_debug_require('pages/accounting_view.php', ['phase' => 'view', 'page' => (string)$page]); ?>
|
||||
<?php elseif ($page === 'expenses'): ?>
|
||||
<?php $expenseCategories = $data['expense_categories'] ?? []; ?>
|
||||
<div class="card p-4">
|
||||
@ -9844,9 +10262,9 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
|
||||
<?php elseif ($page === "outlets" && ($_SESSION["user_role_name"] ?? "") === "Administrator"): ?>
|
||||
<?php require "outlets_html.php"; ?>
|
||||
|
||||
<?php elseif ($page === 'copy_outlet_data'): require 'pages/copy_outlet_data_view.php'; ?>
|
||||
<?php elseif ($page === 'settings'): require 'pages/settings_view.php'; ?>
|
||||
<?php elseif ($page === 'role_groups'): require 'pages/role_groups_view.php'; ?>
|
||||
<?php elseif ($page === 'copy_outlet_data'): runtime_debug_require('pages/copy_outlet_data_view.php', ['phase' => 'view', 'page' => (string)$page]); ?>
|
||||
<?php elseif ($page === 'settings'): runtime_debug_require('pages/settings_view.php', ['phase' => 'view', 'page' => (string)$page]); ?>
|
||||
<?php elseif ($page === 'role_groups'): runtime_debug_require('pages/role_groups_view.php', ['phase' => 'view', 'page' => (string)$page]); ?>
|
||||
|
||||
<?php elseif ($page === 'customer_display_settings'): ?>
|
||||
<div class="card p-4">
|
||||
@ -10003,7 +10421,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
|
||||
</div>
|
||||
|
||||
<?php elseif ($page === 'users'): ?>
|
||||
<?php require 'pages/users_view.php'; ?>
|
||||
<?php runtime_debug_require('pages/users_view.php', ['phase' => 'view', 'page' => (string)$page]); ?>
|
||||
<?php elseif ($page === 'cash_registers'): ?>
|
||||
<div class="card p-4 rounded-4 shadow-sm border-0">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
@ -10294,7 +10712,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php require 'pages/register_session_report_script.php'; ?>
|
||||
<?php runtime_debug_require('pages/register_session_report_script.php', ['phase' => 'script', 'page' => (string)$page]); ?>
|
||||
|
||||
<!-- Open Register Modal -->
|
||||
<div class="modal fade" id="openRegisterModal" tabindex="-1">
|
||||
@ -10464,7 +10882,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
|
||||
<?php endif; ?>
|
||||
|
||||
<?php elseif ($page === 'logs'): ?>
|
||||
<?php require 'pages/logs_view.php'; ?>
|
||||
<?php runtime_debug_require('pages/logs_view.php', ['phase' => 'view', 'page' => (string)$page]); ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
@ -11108,17 +11526,17 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
});
|
||||
});
|
||||
|
||||
<?php require 'pages/lpo_quotation_script.php'; ?>
|
||||
<?php runtime_debug_require('pages/lpo_quotation_script.php', ['phase' => 'script', 'page' => (string)$page]); ?>
|
||||
|
||||
<?php if ($page === 'sales' || $page === 'purchases'): ?>
|
||||
<?php require 'pages/sales_purchases_page_script.php'; ?>
|
||||
<?php runtime_debug_require('pages/sales_purchases_page_script.php', ['phase' => 'script', 'page' => (string)$page]); ?>
|
||||
<?php endif; ?>
|
||||
|
||||
} catch (e) { console.error("JS Error in DOMContentLoaded:", e); }
|
||||
});
|
||||
</script>
|
||||
<?php if ($page === 'sales' || $page === 'purchases'): ?>
|
||||
<?php require 'pages/sales_purchases_modals.php'; ?>
|
||||
<?php runtime_debug_require('pages/sales_purchases_modals.php', ['phase' => 'view', 'page' => (string)$page]); ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<style>
|
||||
@ -11457,12 +11875,12 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
}
|
||||
</style>
|
||||
|
||||
<?php require 'pages/avery_label_script.php'; ?>
|
||||
<?php runtime_debug_require('pages/avery_label_script.php', ['phase' => 'script', 'page' => (string)$page]); ?>
|
||||
|
||||
<script>
|
||||
<?php require 'pages/barcode_pos_script.php'; ?>
|
||||
<?php runtime_debug_require('pages/barcode_pos_script.php', ['phase' => 'script', 'page' => (string)$page]); ?>
|
||||
|
||||
<?php require 'pages/language_dashboard_script.php'; ?>
|
||||
<?php runtime_debug_require('pages/language_dashboard_script.php', ['phase' => 'script', 'page' => (string)$page]); ?>
|
||||
|
||||
</script>
|
||||
</body>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user