Add import/export and update system

Implement CSV/JSON import and export for domains, notification groups and tags (with masking for sensitive channel data), including size/format validation, in-memory CSV building, and logging. Add tag transfer and bulk transfer actions (admin-only). Introduce a new update system: Add UpdateController and UpdateService, migration 025_add_update_system_v1.1.3.sql, and installer changes to include the new migration and version handling; provide endpoints to check, apply, rollback and configure updates. Update helpers and UI bits: add getUpdateBadgeInfo in LayoutHelper, update notification icons/redirects, and add getMaxUploadSize in ViewHelper. Misc: add NotificationGroup::findByName, tweak .gitignore backups path, and update related views and routes.
This commit is contained in:
Hosteroid
2026-02-11 17:43:23 +02:00
parent 0c759cdd1d
commit 3688c8b71b
32 changed files with 4268 additions and 350 deletions

View File

@@ -19,6 +19,7 @@ class ErrorHandler
private Logger $logger;
private ?ErrorLog $errorLogModel = null;
private bool $isDevelopment;
private bool $handling = false; // Recursion guard
public function __construct()
{
@@ -29,9 +30,8 @@ class ErrorHandler
// Initialize ErrorLog model if database is available
try {
$this->errorLogModel = new ErrorLog();
} catch (\Exception $e) {
} catch (\Throwable $e) {
// Database not available, will only use file logging
// Don't use error_log as it might fail too
}
}
@@ -40,6 +40,22 @@ class ErrorHandler
*/
public function handleException(\Throwable $exception): void
{
// Prevent infinite recursion if error handling itself triggers an error
if ($this->handling) {
// Fallback: just log to file and stop
try {
$this->logger->critical('Recursive error detected', [
'message' => $exception->getMessage(),
'file' => $exception->getFile(),
'line' => $exception->getLine()
]);
} catch (\Throwable $e) {
// Last resort
}
return;
}
$this->handling = true;
$errorData = $this->captureError($exception);
// Log to file
@@ -62,8 +78,8 @@ class ErrorHandler
return false;
}
// Ignore certain non-critical errors during error handling itself
if (error_reporting() === 0) {
// Prevent recursive handling (e.g. if logToDatabase triggers a warning)
if ($this->handling) {
return false;
}
@@ -114,7 +130,7 @@ class ErrorHandler
'error_message' => $exception->getMessage(),
'error_file' => $exception->getFile(),
'error_line' => $exception->getLine(),
'stack_trace' => json_encode($exception->getTrace()),
'stack_trace' => json_encode($exception->getTrace(), JSON_PARTIAL_OUTPUT_ON_ERROR) ?: '[]',
'request_method' => $_SERVER['REQUEST_METHOD'] ?? 'CLI',
'request_uri' => $_SERVER['REQUEST_URI'] ?? 'N/A',
'request_data' => json_encode($requestData),
@@ -228,9 +244,19 @@ class ErrorHandler
try {
return $this->errorLogModel->logError($errorData);
} catch (\Exception $e) {
// Database logging failed, continue with file logging only
error_log("Failed to log error to database: " . $e->getMessage());
} catch (\Throwable $e) {
// Database logging failed — log to file so it's visible in the app's /logs folder
try {
$this->logger->error('Failed to log error to database', [
'db_error' => $e->getMessage(),
'db_error_file' => $e->getFile(),
'db_error_line' => $e->getLine(),
'original_error_id' => $errorData['error_id'] ?? 'unknown'
]);
} catch (\Throwable $e2) {
// Last resort — use PHP's native error_log
error_log("ErrorHandler: DB log failed: " . $e->getMessage());
}
return null;
}
}