Files
mesreleves-php/public/servercheck.php
T
yann64 2e6ac29e95 Ajout de public/servercheck.php (outil de diagnostic serveur)
Fichier autonome (sans dépendance Laravel) vérifiant :
- Version PHP (≥ 8.2)
- Extensions requises et optionnelles
- Directives php.ini (memory_limit, upload_max_filesize…)
- Répertoires accessibles en écriture (storage/, bootstrap/cache/)
- Présence des fichiers clés (.env, vendor, assets compilés)
- Test de connexion BDD (MySQL / PostgreSQL) via formulaire
Interface HTML auto-adaptative (dark mode OS), sans dépendance externe.

À exclure des archives de production (--exclude public/servercheck.php
dans le rsync de build-release).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 21:35:55 +02:00

413 lines
20 KiB
PHP

<?php
/**
* MesRelevés — Diagnostic de configuration serveur
*
* ATTENTION : ce fichier est réservé aux tests. Il ne doit JAMAIS
* être déployé sur un environnement de production.
* Supprimez-le dès que votre vérification est terminée.
*/
// ── Collecte des informations ──────────────────────────────────────────────
$root = dirname(__DIR__); // répertoire racine du projet
$phpVer = PHP_VERSION;
$reqVer = '8.2.0';
$phpOk = version_compare($phpVer, $reqVer, '>=');
// Extensions requises : [nom => optionnel?]
$extensions = [
'pdo' => false,
'pdo_mysql' => false,
'mbstring' => false,
'openssl' => false,
'tokenizer' => false,
'xml' => false,
'ctype' => false,
'json' => false,
'bcmath' => false,
'fileinfo' => false,
'zip' => false,
'pdo_pgsql' => true, // optionnel : seulement si PostgreSQL
'curl' => true, // optionnel : mises à jour automatiques
'intl' => true, // optionnel : formatage avancé des dates
];
// Répertoires à vérifier en écriture
$dirs = [
'storage/' => $root . '/storage',
'storage/logs/' => $root . '/storage/logs',
'storage/app/' => $root . '/storage/app',
'storage/framework/' => $root . '/storage/framework',
'bootstrap/cache/' => $root . '/bootstrap/cache',
'Racine (écriture .env)' => $root,
];
// Fichiers à vérifier
$files = [
'.env' => [$root . '/.env', false],
'vendor/autoload.php' => [$root . '/vendor/autoload.php', false],
'storage/installed' => [$root . '/storage/installed', true],
'public/build/manifest.json' => [$root . '/public/build/manifest.json', false],
];
// Configuration PHP recommandée : [directive => [valeur_min_bytes, label_min]]
$iniChecks = [
'memory_limit' => [128 * 1024 * 1024, '128 Mo'],
'upload_max_filesize'=> [2 * 1024 * 1024, '2 Mo'],
'post_max_size' => [8 * 1024 * 1024, '8 Mo'],
'max_execution_time' => [30, '30 s'],
];
function iniBytes(string $val): int {
$val = trim($val);
$last = strtolower($val[-1] ?? '');
$n = (int) $val;
return match ($last) {
'g' => $n * 1024 * 1024 * 1024,
'm' => $n * 1024 * 1024,
'k' => $n * 1024,
default => $n,
};
}
// Test de connexion BDD (si formulaire soumis)
$dbResult = null;
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['db_host'])) {
$driver = $_POST['db_driver'] ?? 'mysql';
$host = $_POST['db_host'] ?? '';
$port = (int) ($_POST['db_port'] ?? ($driver === 'pgsql' ? 5432 : 3306));
$dbname = $_POST['db_name'] ?? '';
$user = $_POST['db_user'] ?? '';
$password = $_POST['db_password'] ?? '';
try {
$dsn = $driver === 'pgsql'
? "pgsql:host={$host};port={$port};dbname={$dbname}"
: "mysql:host={$host};port={$port};dbname={$dbname};charset=utf8mb4";
$pdo = new PDO($dsn, $user, $password, [PDO::ATTR_TIMEOUT => 5]);
$ver = $pdo->query('SELECT version()')->fetchColumn();
$dbResult = ['ok' => true, 'msg' => "Connexion réussie — " . htmlspecialchars($ver)];
} catch (Exception $e) {
$dbResult = ['ok' => false, 'msg' => htmlspecialchars($e->getMessage())];
}
}
// ── Compteur global ────────────────────────────────────────────────────────
$totalChecks = 0;
$totalFailed = 0;
?><!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Diagnostic serveur — MesRelevés</title>
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--bg: #f3f4f6; --card: #fff; --border: #e5e7eb;
--text: #111827; --muted: #6b7280;
--ok: #16a34a; --ok-bg: #f0fdf4; --ok-border: #bbf7d0;
--warn: #d97706; --warn-bg: #fffbeb; --warn-border: #fde68a;
--err: #dc2626; --err-bg: #fef2f2; --err-border: #fecaca;
--accent: #4f46e5;
}
@media (prefers-color-scheme: dark) {
:root {
--bg: #111827; --card: #1f2937; --border: #374151;
--text: #f9fafb; --muted: #9ca3af;
--ok: #4ade80; --ok-bg: #052e16; --ok-border: #166534;
--warn: #fbbf24; --warn-bg: #1c1400; --warn-border: #92400e;
--err: #f87171; --err-bg: #1a0000; --err-border: #991b1b;
}
}
body { background: var(--bg); color: var(--text); font: 15px/1.6 system-ui, sans-serif; padding: 24px 16px 60px; }
h1 { font-size: 22px; font-weight: 700; margin-bottom: 4px; }
.warning-banner {
background: #7f1d1d; color: #fef2f2; border: 2px solid #991b1b;
border-radius: 8px; padding: 12px 16px; margin-bottom: 24px;
font-weight: 600; font-size: 14px; display: flex; align-items: center; gap: 10px;
}
.warning-banner svg { flex-shrink: 0; }
.container { max-width: 860px; margin: 0 auto; }
.card { background: var(--card); border: 1px solid var(--border); border-radius: 10px; margin-bottom: 20px; overflow: hidden; }
.card-header { padding: 14px 20px; border-bottom: 1px solid var(--border); font-weight: 700; font-size: 13px; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); display: flex; align-items: center; justify-content: space-between; }
table { width: 100%; border-collapse: collapse; font-size: 14px; }
td, th { padding: 9px 20px; text-align: left; border-bottom: 1px solid var(--border); }
tr:last-child td { border-bottom: none; }
th { font-size: 12px; font-weight: 600; color: var(--muted); text-transform: uppercase; background: var(--bg); }
.badge { display: inline-flex; align-items: center; gap: 5px; padding: 3px 10px; border-radius: 9999px; font-size: 12px; font-weight: 600; white-space: nowrap; }
.badge-ok { background: var(--ok-bg); color: var(--ok); border: 1px solid var(--ok-border); }
.badge-warn { background: var(--warn-bg); color: var(--warn); border: 1px solid var(--warn-border); }
.badge-err { background: var(--err-bg); color: var(--err); border: 1px solid var(--err-border); }
.badge-info { background: var(--bg); color: var(--muted);border: 1px solid var(--border); }
.mono { font-family: 'SF Mono', 'Fira Code', monospace; font-size: 13px; }
.muted { color: var(--muted); font-size: 13px; }
.summary { display: flex; gap: 16px; margin-bottom: 24px; flex-wrap: wrap; }
.summary-item { background: var(--card); border: 1px solid var(--border); border-radius: 8px; padding: 14px 20px; flex: 1; min-width: 140px; }
.summary-item .val { font-size: 28px; font-weight: 800; line-height: 1; }
.summary-item .lbl { font-size: 12px; color: var(--muted); margin-top: 4px; }
.val-ok { color: var(--ok); }
.val-err { color: var(--err); }
form { padding: 16px 20px; }
.form-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px; margin-bottom: 14px; }
label { display: block; font-size: 13px; font-weight: 600; color: var(--muted); margin-bottom: 4px; }
input, select { width: 100%; padding: 7px 10px; border: 1px solid var(--border); border-radius: 6px; background: var(--bg); color: var(--text); font-size: 14px; }
button[type=submit] { background: var(--accent); color: #fff; border: none; padding: 8px 20px; border-radius: 6px; font-size: 14px; font-weight: 600; cursor: pointer; }
button[type=submit]:hover { opacity: .9; }
.db-result { margin-top: 12px; padding: 10px 14px; border-radius: 6px; font-size: 13px; font-weight: 600; }
.db-ok { background: var(--ok-bg); color: var(--ok); border: 1px solid var(--ok-border); }
.db-err { background: var(--err-bg); color: var(--err); border: 1px solid var(--err-border); }
.server-info { padding: 14px 20px; font-size: 13px; display: grid; grid-template-columns: 200px 1fr; gap: 6px 16px; }
.server-info dt { color: var(--muted); }
.server-info dd { font-family: monospace; word-break: break-all; }
</style>
</head>
<body>
<div class="container">
<h1>Diagnostic serveur — MesRelevés</h1>
<p class="muted" style="margin-bottom:20px">Version PHP <?= htmlspecialchars($phpVer) ?> · <?= htmlspecialchars($_SERVER['SERVER_SOFTWARE'] ?? 'Serveur inconnu') ?></p>
<div class="warning-banner">
<svg width="20" height="20" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M12 9v2m0 4h.01M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"/>
</svg>
FICHIER DE TEST UNIQUEMENT — À supprimer immédiatement après diagnostic. Ne jamais laisser en production.
</div>
<?php
// ── Comptage global (première passe) ──────────────────────────────────────
$extFailed = 0; $extMandatoryFailed = 0;
foreach ($extensions as $ext => $optional) {
if (!extension_loaded($ext) && !$optional) $extMandatoryFailed++;
}
$dirFailed = 0;
foreach ($dirs as $path) {
if (!is_writable($path)) $dirFailed++;
}
$fileFailed = 0;
foreach ($files as [$path, $optional]) {
if (!file_exists($path) && !$optional) $fileFailed++;
}
$totalFailed = (!$phpOk ? 1 : 0) + $extMandatoryFailed + $dirFailed + $fileFailed;
$totalOk = ($phpOk ? 1 : 0)
+ count(array_filter(array_keys($extensions), fn($e) => extension_loaded($e) && !$extensions[$e]))
+ count(array_filter(array_keys($dirs), fn($k) => is_writable($dirs[$k])))
+ count(array_filter(array_keys($files), fn($k) => file_exists($files[$k][0])));
?>
<div class="summary">
<div class="summary-item">
<div class="val <?= $totalFailed === 0 ? 'val-ok' : 'val-err' ?>"><?= $totalFailed === 0 ? '✓' : $totalFailed ?></div>
<div class="lbl"><?= $totalFailed === 0 ? 'Tout est OK' : 'Problème(s) bloquant(s)' ?></div>
</div>
<div class="summary-item">
<div class="val"><?= PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION ?></div>
<div class="lbl">Version PHP</div>
</div>
<div class="summary-item">
<div class="val"><?= count(array_filter(array_keys($extensions), fn($e) => extension_loaded($e))) ?>/<?= count($extensions) ?></div>
<div class="lbl">Extensions chargées</div>
</div>
<div class="summary-item">
<div class="val"><?= ini_get('memory_limit') ?></div>
<div class="lbl">memory_limit</div>
</div>
</div>
{{-- ── Informations serveur ────────────────────────────────────────────── --}}
<div class="card">
<div class="card-header">Environnement</div>
<dl class="server-info">
<dt>Serveur</dt> <dd><?= htmlspecialchars($_SERVER['SERVER_SOFTWARE'] ?? '—') ?></dd>
<dt>SAPI</dt> <dd><?= htmlspecialchars(php_sapi_name()) ?></dd>
<dt>Document root</dt><dd><?= htmlspecialchars($_SERVER['DOCUMENT_ROOT'] ?? '—') ?></dd>
<dt>Chemin du script</dt><dd><?= htmlspecialchars(__FILE__) ?></dd>
<dt>Racine projet</dt><dd><?= htmlspecialchars($root) ?></dd>
<dt>OS</dt> <dd><?= htmlspecialchars(PHP_OS_FAMILY) ?></dd>
<dt>Fuseau horaire</dt><dd><?= htmlspecialchars(ini_get('date.timezone') ?: 'non défini') ?></dd>
</dl>
</div>
{{-- ── Version PHP ─────────────────────────────────────────────────────── --}}
<div class="card">
<div class="card-header">Version PHP</div>
<table>
<tr>
<td><strong>PHP <?= htmlspecialchars($phpVer) ?></strong></td>
<td class="muted">Minimum requis : <?= $reqVer ?></td>
<td><span class="badge <?= $phpOk ? 'badge-ok' : 'badge-err' ?>"><?= $phpOk ? '✓ OK' : '✗ Insuffisant' ?></span></td>
</tr>
</table>
</div>
{{-- ── Extensions PHP ──────────────────────────────────────────────────── --}}
<div class="card">
<div class="card-header">
Extensions PHP
<span class="muted" style="font-weight:400;text-transform:none">
<?= count(array_filter(array_keys($extensions), fn($e) => extension_loaded($e))) ?>/<?= count($extensions) ?> chargées
</span>
</div>
<table>
<thead><tr><th>Extension</th><th>Statut</th><th>Note</th></tr></thead>
<tbody>
<?php foreach ($extensions as $ext => $optional):
$loaded = extension_loaded($ext); ?>
<tr>
<td class="mono"><?= htmlspecialchars($ext) ?></td>
<td>
<?php if ($loaded): ?>
<span class="badge badge-ok">✓ Présente</span>
<?php elseif ($optional): ?>
<span class="badge badge-warn">⚠ Absente</span>
<?php else: ?>
<span class="badge badge-err">✗ Manquante</span>
<?php endif; ?>
</td>
<td class="muted">
<?php if ($optional && !$loaded): echo 'Optionnelle'; endif; ?>
<?php if ($ext === 'pdo_pgsql' && !$loaded): echo '— nécessaire uniquement si PostgreSQL'; endif; ?>
<?php if ($ext === 'curl' && !$loaded): echo '— nécessaire pour les mises à jour automatiques'; endif; ?>
<?php if ($ext === 'intl' && !$loaded): echo '— recommandée (formatage dates/nombres)'; endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
{{-- ── Configuration PHP ───────────────────────────────────────────────── --}}
<div class="card">
<div class="card-header">Configuration PHP (php.ini)</div>
<table>
<thead><tr><th>Directive</th><th>Valeur actuelle</th><th>Minimum recommandé</th><th>Statut</th></tr></thead>
<tbody>
<?php foreach ($iniChecks as $key => [$minBytes, $minLabel]):
$raw = ini_get($key);
$bytes = iniBytes($raw);
$ok = $bytes >= $minBytes || $minBytes === 30 && ($bytes === 0 || $bytes >= $minBytes);
// max_execution_time = 0 means unlimited
if ($key === 'max_execution_time') $ok = ($bytes === 0 || $bytes >= $minBytes);
?>
<tr>
<td class="mono"><?= htmlspecialchars($key) ?></td>
<td class="mono"><?= htmlspecialchars($raw) ?></td>
<td class="muted"><?= $minLabel ?></td>
<td><span class="badge <?= $ok ? 'badge-ok' : 'badge-warn' ?>"><?= $ok ? '✓ OK' : '⚠ Faible' ?></span></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
{{-- ── Répertoires ─────────────────────────────────────────────────────── --}}
<div class="card">
<div class="card-header">Répertoires accessibles en écriture</div>
<table>
<thead><tr><th>Répertoire</th><th>Chemin</th><th>Statut</th></tr></thead>
<tbody>
<?php foreach ($dirs as $label => $path):
$exists = is_dir($path);
$writable = $exists && is_writable($path);
?>
<tr>
<td><strong><?= htmlspecialchars($label) ?></strong></td>
<td class="mono muted"><?= htmlspecialchars(str_replace($root, '…', $path)) ?></td>
<td>
<?php if (!$exists): ?>
<span class="badge badge-err">✗ Inexistant</span>
<?php elseif ($writable): ?>
<span class="badge badge-ok">✓ OK</span>
<?php else: ?>
<span class="badge badge-err">✗ Non accessible</span>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
{{-- ── Fichiers clés ───────────────────────────────────────────────────── --}}
<div class="card">
<div class="card-header">Fichiers clés</div>
<table>
<thead><tr><th>Fichier</th><th>Présent</th><th>Note</th></tr></thead>
<tbody>
<?php foreach ($files as $label => [$path, $optional]):
$exists = file_exists($path); ?>
<tr>
<td class="mono"><?= htmlspecialchars($label) ?></td>
<td>
<?php if ($exists): ?>
<span class="badge badge-ok">✓ Présent</span>
<?php elseif ($optional): ?>
<span class="badge badge-info">— Absent</span>
<?php else: ?>
<span class="badge badge-err">✗ Manquant</span>
<?php endif; ?>
</td>
<td class="muted">
<?php if ($label === '.env' && !$exists): echo 'Créé automatiquement par l\'assistant /setup'; endif; ?>
<?php if ($label === 'storage/installed'): echo $exists ? 'Application déjà installée' : 'Installation non encore effectuée → aller sur /setup'; endif; ?>
<?php if ($label === 'vendor/autoload.php' && !$exists): echo 'Exécutez : composer install --no-dev'; endif; ?>
<?php if ($label === 'public/build/manifest.json' && !$exists): echo 'Assets non compilés — npm run build requis'; endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
{{-- ── Test de connexion BDD ───────────────────────────────────────────── --}}
<div class="card">
<div class="card-header">Test de connexion base de données</div>
<form method="POST">
<div class="form-grid">
<div>
<label>Driver</label>
<select name="db_driver">
<option value="mysql" <?= ($_POST['db_driver'] ?? 'mysql') === 'mysql' ? 'selected' : '' ?>>MySQL / MariaDB</option>
<option value="pgsql" <?= ($_POST['db_driver'] ?? '') === 'pgsql' ? 'selected' : '' ?>>PostgreSQL</option>
</select>
</div>
<div>
<label>Hôte</label>
<input type="text" name="db_host" value="<?= htmlspecialchars($_POST['db_host'] ?? 'localhost') ?>" placeholder="localhost">
</div>
<div>
<label>Port</label>
<input type="number" name="db_port" value="<?= htmlspecialchars($_POST['db_port'] ?? '3306') ?>" placeholder="3306">
</div>
<div>
<label>Base de données</label>
<input type="text" name="db_name" value="<?= htmlspecialchars($_POST['db_name'] ?? '') ?>" placeholder="mesreleves">
</div>
<div>
<label>Utilisateur</label>
<input type="text" name="db_user" value="<?= htmlspecialchars($_POST['db_user'] ?? '') ?>" placeholder="mesreleves">
</div>
<div>
<label>Mot de passe</label>
<input type="password" name="db_password" placeholder="••••••••">
</div>
</div>
<button type="submit">Tester la connexion</button>
<?php if ($dbResult !== null): ?>
<div class="db-result <?= $dbResult['ok'] ? 'db-ok' : 'db-err' ?>">
<?= $dbResult['ok'] ? '✓ ' : '✗ ' ?><?= $dbResult['msg'] ?>
</div>
<?php endif; ?>
</form>
</div>
<p class="muted" style="text-align:center;font-size:13px;margin-top:8px">
Généré le <?= date('d/m/Y à H:i:s') ?> · MesRelevés <?= htmlspecialchars(trim(@file_get_contents($root . '/VERSION') ?: '')) ?>
</p>
</div>
</body>
</html>