Stand: SMTP-Test, Admin-Mail-Tab, Notifiable-Fix, Lazy-Quill

- Fix: Notifiable-Trait zum User-Model hinzugefuegt (behebt notify()-500er)
- Installer: SMTP-Verbindungstest mit EsmtpTransport + Ueberspringen-Link
- Admin: Neuer E-Mail-Tab mit SMTP-Konfiguration + Verbindungstest
- Admin: Lazy Quill-Initialisierung (nur sichtbare Locale wird geladen)
- Uebersetzungen: 17 neue Mail-Keys in allen 6 Sprachen

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Rhino
2026-03-02 07:30:37 +01:00
commit 2e24a40d68
9633 changed files with 1300799 additions and 0 deletions

416
public/check.php Normal file
View File

@@ -0,0 +1,416 @@
<?php
/**
* Eigenstaendige Server-Diagnose (kein Laravel noetig).
* Aufruf: https://deine-domain.de/check.php
*
* WICHTIG: Diese Datei nach erfolgreicher Installation loeschen!
*/
error_reporting(E_ALL);
ini_set('display_errors', '1');
$basePath = __DIR__ . '/..';
$results = [];
// ─── PHP ─────────────────────────────────────────────────────
$results[] = [
'group' => 'PHP',
'name' => 'PHP-Version',
'value' => PHP_VERSION,
'ok' => version_compare(PHP_VERSION, '8.2.0', '>='),
'hint' => 'Mindestens PHP 8.2 erforderlich.',
];
$results[] = [
'group' => 'PHP',
'name' => 'Server-API (SAPI)',
'value' => PHP_SAPI,
'ok' => true,
'hint' => '',
];
$extensions = ['pdo', 'pdo_sqlite', 'mbstring', 'openssl', 'tokenizer', 'xml', 'ctype', 'fileinfo', 'dom', 'curl'];
foreach ($extensions as $ext) {
$results[] = [
'group' => 'PHP-Extensions',
'name' => $ext,
'value' => extension_loaded($ext) ? 'Geladen' : 'Fehlt',
'ok' => extension_loaded($ext),
'hint' => $ext === 'pdo_sqlite' ? 'Nur fuer SQLite noetig' : '',
];
}
// Optional
$results[] = [
'group' => 'PHP-Extensions',
'name' => 'pdo_mysql (optional)',
'value' => extension_loaded('pdo_mysql') ? 'Geladen' : 'Fehlt',
'ok' => true, // optional
'hint' => 'Nur fuer MySQL noetig.',
];
// ─── Dateisystem ─────────────────────────────────────────────
$dirs = [
'storage',
'storage/app',
'storage/framework',
'storage/framework/cache',
'storage/framework/cache/data',
'storage/framework/sessions',
'storage/framework/views',
'storage/logs',
'bootstrap/cache',
'database',
];
foreach ($dirs as $dir) {
$full = $basePath . '/' . $dir;
$exists = is_dir($full);
$writable = $exists && is_writable($full);
$perms = $exists ? substr(sprintf('%o', fileperms($full)), -4) : '-';
$owner = $exists ? (function_exists('posix_getpwuid') ? posix_getpwuid(fileowner($full))['name'] ?? fileowner($full) : fileowner($full)) : '-';
$results[] = [
'group' => 'Verzeichnisse',
'name' => $dir . '/',
'value' => ($exists ? 'Existiert' : 'FEHLT') . ' | ' . ($writable ? 'Beschreibbar' : 'NICHT beschreibbar') . ' | ' . $perms . ' | Owner: ' . $owner,
'ok' => $writable,
'hint' => !$writable ? 'Berechtigungen auf 775 setzen' : '',
];
}
// ─── Dateien ─────────────────────────────────────────────────
$files = [
'.env' => 'Konfigurationsdatei (wird automatisch erstellt)',
'.env.example' => 'Vorlage fuer .env',
'vendor/autoload.php' => 'Composer-Abhaengigkeiten',
'bootstrap/app.php' => 'Laravel-Bootstrap',
'storage/installed' => 'Installations-Marker (fehlt bei Erstinstallation)',
];
foreach ($files as $file => $desc) {
$full = $basePath . '/' . $file;
$exists = file_exists($full);
// storage/installed SOLL bei Erstinstallation fehlen
$isOptional = ($file === 'storage/installed' || $file === '.env');
$results[] = [
'group' => 'Dateien',
'name' => $file,
'value' => $exists ? 'Vorhanden' : 'Fehlt',
'ok' => $exists || $isOptional,
'hint' => $desc,
];
}
// ─── Webserver ───────────────────────────────────────────────
$results[] = [
'group' => 'Webserver',
'name' => 'Server-Software',
'value' => $_SERVER['SERVER_SOFTWARE'] ?? 'Unbekannt',
'ok' => true,
'hint' => '',
];
$results[] = [
'group' => 'Webserver',
'name' => 'Document Root',
'value' => $_SERVER['DOCUMENT_ROOT'] ?? 'Unbekannt',
'ok' => true,
'hint' => 'Muss auf den public/-Ordner zeigen.',
];
$results[] = [
'group' => 'Webserver',
'name' => 'Script-Pfad',
'value' => __FILE__,
'ok' => true,
'hint' => '',
];
$results[] = [
'group' => 'Webserver',
'name' => 'PHP-User',
'value' => function_exists('posix_getpwuid') ? (posix_getpwuid(posix_geteuid())['name'] ?? posix_geteuid()) : get_current_user(),
'ok' => true,
'hint' => 'Unter diesem User laeuft PHP.',
];
$results[] = [
'group' => 'Webserver',
'name' => 'mod_rewrite',
'value' => (function_exists('apache_get_modules') && in_array('mod_rewrite', apache_get_modules())) ? 'Aktiv' : 'Nicht pruefbar / Nicht aktiv',
'ok' => true, // Can't reliably detect
'hint' => 'Wird fuer saubere URLs benoetigt.',
];
// ─── index.php Datei-Check ───────────────────────────────────
$indexPath = __DIR__ . '/index.php';
$indexExists = file_exists($indexPath);
$indexSize = $indexExists ? filesize($indexPath) : 0;
$indexMtime = $indexExists ? date('Y-m-d H:i:s', filemtime($indexPath)) : '-';
$indexFirstLines = $indexExists ? implode("\n", array_slice(file($indexPath), 0, 10)) : '';
$hasTryCatch = $indexExists && strpos(file_get_contents($indexPath), 'Throwable') !== false;
$results[] = [
'group' => 'index.php Pruefung',
'name' => 'Dateigroesse',
'value' => $indexSize . ' Bytes',
'ok' => $indexSize > 1000,
'hint' => 'Neue index.php sollte ueber 5000 Bytes sein.',
];
$results[] = [
'group' => 'index.php Pruefung',
'name' => 'Letzte Aenderung',
'value' => $indexMtime,
'ok' => true,
'hint' => '',
];
$results[] = [
'group' => 'index.php Pruefung',
'name' => 'try-catch vorhanden',
'value' => $hasTryCatch ? 'Ja' : 'Nein (ALTE VERSION!)',
'ok' => $hasTryCatch,
'hint' => 'Bitte die aktuelle index.php erneut hochladen.',
];
// ─── Laravel Boot-Test ───────────────────────────────────────
$bootSteps = [];
$bootError = null;
// Schritt 1: Autoloader laden
try {
require_once __DIR__ . '/../vendor/autoload.php';
$bootSteps[] = ['name' => 'vendor/autoload.php laden', 'ok' => true, 'error' => ''];
} catch (\Throwable $e) {
$bootSteps[] = ['name' => 'vendor/autoload.php laden', 'ok' => false, 'error' => $e->getMessage()];
$bootError = $e;
}
// Schritt 2: Bootstrap laden (nur wenn Schritt 1 ok)
if (!$bootError) {
try {
$app = require_once __DIR__ . '/../bootstrap/app.php';
$bootSteps[] = ['name' => 'bootstrap/app.php laden', 'ok' => true, 'error' => ''];
} catch (\Throwable $e) {
$bootSteps[] = ['name' => 'bootstrap/app.php laden', 'ok' => false, 'error' => $e->getMessage()];
$bootError = $e;
}
}
// Schritt 3: Kernel erstellen (nur wenn Schritt 2 ok)
if (!$bootError && isset($app)) {
try {
$kernel = $app->make(\Illuminate\Contracts\Http\Kernel::class);
$bootSteps[] = ['name' => 'HTTP-Kernel erstellen', 'ok' => true, 'error' => ''];
} catch (\Throwable $e) {
$bootSteps[] = ['name' => 'HTTP-Kernel erstellen', 'ok' => false, 'error' => $e->getMessage()];
$bootError = $e;
}
}
// Schritt 4: Request-Handling simulieren (nur wenn Schritt 3 ok)
if (!$bootError && isset($app)) {
try {
// Simuliere einen GET / Request
$testRequest = \Illuminate\Http\Request::create('/', 'GET');
$response = $app->make(\Illuminate\Contracts\Http\Kernel::class)->handle($testRequest);
$statusCode = $response->getStatusCode();
$isRedirect = $statusCode >= 300 && $statusCode < 400;
$redirectTo = $isRedirect ? $response->headers->get('Location', '') : '';
$bootSteps[] = [
'name' => 'Request-Handling (GET /)',
'ok' => true,
'error' => 'Status ' . $statusCode . ($isRedirect ? ' → Redirect nach ' . $redirectTo : ''),
];
} catch (\Throwable $e) {
$bootSteps[] = ['name' => 'Request-Handling (GET /)', 'ok' => false, 'error' => $e->getMessage()];
$bootError = $e;
}
}
// Schritt 5: Auch /install testen (dort laeuft die eigentliche Seite)
if (!$bootError && isset($app)) {
try {
// Log leeren, damit wir nur den Fehler von diesem Request sehen
$logFile = __DIR__ . '/../storage/logs/laravel.log';
@file_put_contents($logFile, '');
// Debug aktivieren, damit die Exception geloggt wird
config(['app.debug' => true]);
$installRequest = \Illuminate\Http\Request::create('/install', 'GET');
$installResponse = $app->make(\Illuminate\Contracts\Http\Kernel::class)->handle($installRequest);
$installStatus = $installResponse->getStatusCode();
if ($installStatus >= 500) {
// Fehler aus dem Log auslesen
$logContent = file_exists($logFile) ? file_get_contents($logFile) : '';
// Nur die erste Fehlermeldung extrahieren (bis zur ersten Leerzeile)
$logLines = explode("\n", $logContent);
$errorMsg = '';
foreach ($logLines as $line) {
if (strlen($errorMsg) > 1500) break;
$errorMsg .= $line . "\n";
}
$errorMsg = trim($errorMsg) ?: '(kein Log-Eintrag gefunden)';
$bootSteps[] = [
'name' => 'Request-Handling (GET /install)',
'ok' => false,
'error' => 'Status ' . $installStatus . ' — Fehler aus laravel.log: ' . $errorMsg,
];
} else {
$bootSteps[] = [
'name' => 'Request-Handling (GET /install)',
'ok' => true,
'error' => 'Status ' . $installStatus,
];
}
} catch (\Throwable $e) {
$bootSteps[] = ['name' => 'Request-Handling (GET /install)', 'ok' => false, 'error' => $e->getMessage()];
$bootError = $e;
}
}
// ─── OPcache ─────────────────────────────────────────────────
$opcacheEnabled = function_exists('opcache_get_status') && opcache_get_status() !== false;
$results[] = [
'group' => 'OPcache',
'name' => 'OPcache aktiv',
'value' => $opcacheEnabled ? 'Ja' : 'Nein',
'ok' => true,
'hint' => '',
];
if ($opcacheEnabled) {
$status = opcache_get_status();
$results[] = [
'group' => 'OPcache',
'name' => 'validate_timestamps',
'value' => ini_get('opcache.validate_timestamps') ? 'Ja' : 'Nein (gefaehrlich!)',
'ok' => (bool) ini_get('opcache.validate_timestamps'),
'hint' => 'Ohne validate_timestamps werden Dateiupdates nicht erkannt.',
];
// OPcache fuer index.php invalidieren
$indexInvalidated = opcache_invalidate(__DIR__ . '/index.php', true);
$results[] = [
'group' => 'OPcache',
'name' => 'index.php Cache invalidiert',
'value' => $indexInvalidated ? 'Ja (erfolgreich)' : 'Nein / nicht noetig',
'ok' => true,
'hint' => '',
];
}
foreach ($bootSteps as $step) {
$results[] = [
'group' => 'Laravel Boot-Test',
'name' => $step['name'],
'value' => $step['ok'] ? 'OK' : 'FEHLER: ' . $step['error'],
'ok' => $step['ok'],
'hint' => $step['error'],
];
}
if ($bootError) {
$results[] = [
'group' => 'Laravel Boot-Test',
'name' => 'Stack-Trace',
'value' => $bootError->getFile() . ':' . $bootError->getLine(),
'ok' => false,
'hint' => $bootError->getTraceAsString(),
];
}
// ─── Zusammenfassung ─────────────────────────────────────────
$allOk = true;
foreach ($results as $r) {
if (!$r['ok']) {
$allOk = false;
break;
}
}
// ─── HTML-Ausgabe ────────────────────────────────────────────
?>
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Server-Diagnose</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="min-h-screen bg-gray-100 py-8 px-4">
<div class="max-w-3xl mx-auto">
<div class="text-center mb-6">
<h1 class="text-xl font-bold text-gray-900">Server-Diagnose</h1>
<p class="text-sm text-gray-500 mt-1">
Handball WebApp &mdash; Systemcheck
</p>
</div>
<?php if ($allOk): ?>
<div class="bg-green-50 border border-green-200 rounded-md p-4 mb-6 text-sm text-green-800">
Alle Voraussetzungen sind erfuellt. Die Installation sollte funktionieren.
<br>Rufe die Hauptseite auf, um den Installer zu starten.
</div>
<?php else: ?>
<div class="bg-red-50 border border-red-200 rounded-md p-4 mb-6 text-sm text-red-800">
Es gibt Probleme, die behoben werden muessen (rot markiert).
</div>
<?php endif; ?>
<?php
$currentGroup = '';
foreach ($results as $r):
if ($r['group'] !== $currentGroup):
if ($currentGroup !== '') echo '</tbody></table></div>';
$currentGroup = $r['group'];
?>
<div class="bg-white rounded-lg shadow-md mb-4 overflow-hidden">
<h2 class="bg-gray-50 px-4 py-2 text-sm font-semibold text-gray-700 border-b"><?php echo htmlspecialchars($currentGroup); ?></h2>
<table class="w-full text-sm">
<tbody>
<?php endif; ?>
<tr class="border-b border-gray-100 <?php echo $r['ok'] ? '' : 'bg-red-50'; ?>">
<td class="px-4 py-2 font-mono text-gray-800 whitespace-nowrap"><?php echo htmlspecialchars($r['name']); ?></td>
<td class="px-4 py-2 text-gray-600"><?php echo htmlspecialchars($r['value']); ?></td>
<td class="px-2 py-2 text-center w-8">
<?php if ($r['ok']): ?>
<span class="text-green-500">&#10003;</span>
<?php else: ?>
<span class="text-red-500 font-bold">&#10007;</span>
<?php endif; ?>
</td>
</tr>
<?php if (!$r['ok'] && $r['hint']): ?>
<tr class="bg-red-50 border-b border-red-100">
<td colspan="3" class="px-4 py-1 text-xs text-red-600">
<pre class="whitespace-pre-wrap break-all">&rarr; <?php echo htmlspecialchars($r['hint']); ?></pre>
</td>
</tr>
<?php endif; ?>
<?php endforeach; ?>
</tbody></table>
</div>
<div class="flex justify-center gap-4 mt-6">
<a href="check.php" class="px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-md hover:bg-blue-700">
Erneut pruefen
</a>
<?php if ($allOk): ?>
<a href="/" class="px-4 py-2 text-sm font-medium text-white bg-green-600 rounded-md hover:bg-green-700">
Installation starten
</a>
<?php endif; ?>
</div>
<p class="text-center text-xs text-gray-400 mt-6">
Diese Datei nach erfolgreicher Installation loeschen (check.php).
</p>
</div>
</body>
</html>