🗄️ 1. Pourquoi SQLite plutôt que MySQL ?
L'original de ce tutoriel utilisait MySQL. Sur mon propre site, j'utilise SQLite — et pour un log DX personnel, c'est le meilleur choix. Voici pourquoi.
Si votre hébergeur impose MySQL ou si vous prévoyez un volume très important de données partagées entre plusieurs utilisateurs, MySQL reste pertinent. Pour un log DX personnel, SQLite est largement suffisant et bien plus simple.
📋 2. Structure de la base de données
Voici la structure SQLite utilisée sur ce site pour le log DX. Elle couvre tous les champs nécessaires à un échange eQSL valide et à un export ADIF complet.
CREATE TABLE IF NOT EXISTS activites (
id INTEGER PRIMARY KEY AUTOINCREMENT,
date_qso TEXT NOT NULL, -- Format : YYYY-MM-DD
heure_qso TEXT NOT NULL, -- Format : HH:MM UTC
indicatif TEXT NOT NULL, -- Ex : 14RC185
frequence TEXT NOT NULL, -- Ex : 27.555
mode TEXT NOT NULL DEFAULT 'USB', -- USB, LSB, AM, FM
report TEXT, -- Ex : 59, 57
pays TEXT, -- Ex : France, Italie
commentaire TEXT,
date_ajout TEXT DEFAULT (datetime('now'))
);
Protection du fichier DB : placez votre fichier .db
dans un dossier non accessible depuis le web (ex: /admin/activites.db)
ou ajoutez une règle .htaccess pour bloquer l'accès direct :
<FilesMatch "\.db$"> Require all denied </FilesMatch>
🔌 3. Connexion PHP à la base SQLite
Deux approches sont possibles en PHP : la classe SQLite3 native
ou PDO avec le driver SQLite. J'utilise SQLite3 natif sur ce site —
plus simple et sans dépendance.
// Connexion en lecture/écriture $db = new SQLite3(__DIR__ . '/admin/activites.db'); $db->enableExceptions(true); // Connexion en lecture seule (pour les pages publiques) $db = new SQLite3(__DIR__ . '/admin/activites.db', SQLITE3_OPEN_READONLY);
Toujours utiliser SQLITE3_OPEN_READONLY sur les pages publiques —
un visiteur ne doit jamais pouvoir déclencher une écriture en base depuis
une page accessible sur le web.
🧰 4. Formulaire d'ajout d'un contact
Ce formulaire HTML sert à saisir un nouveau QSO depuis l'interface d'administration. Il ne doit jamais être accessible depuis l'extérieur.
<form method="post" action="ajout-log.php">
<label>Date (UTC) :
<input type="date" name="date_qso" required>
</label>
<label>Heure UTC :
<input type="time" name="heure_qso" required>
</label>
<label>Indicatif :
<input type="text" name="indicatif" placeholder="14RC185" required>
</label>
<label>Fréquence (MHz) :
<input type="text" name="frequence" placeholder="27.555">
</label>
<label>Mode :
<select name="mode">
<option value="USB">USB</option>
<option value="LSB">LSB</option>
<option value="AM">AM</option>
<option value="FM">FM</option>
</select>
</label>
<label>Report RS :
<input type="text" name="report" placeholder="59">
</label>
<label>Pays :
<input type="text" name="pays" placeholder="France">
</label>
<button type="submit">Enregistrer le QSO</button>
</form>
💾 5. Traitement PHP de l'insertion
Le traitement doit toujours utiliser des requêtes préparées pour éviter les injections SQL — même avec SQLite.
<?php
// Vérification basique d'authentification admin
session_start();
if (empty($_SESSION['admin'])) {
header('Location: /admin/login.php');
exit;
}
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
header('Location: /admin/log.php');
exit;
}
$db = new SQLite3(__DIR__ . '/../admin/activites.db');
$db->enableExceptions(true);
$stmt = $db->prepare("
INSERT INTO activites (date_qso, heure_qso, indicatif, frequence, mode, report, pays, commentaire)
VALUES (:date, :heure, :indicatif, :freq, :mode, :report, :pays, :comment)
");
$stmt->bindValue(':date', trim($_POST['date_qso'] ?? ''), SQLITE3_TEXT);
$stmt->bindValue(':heure', trim($_POST['heure_qso'] ?? ''), SQLITE3_TEXT);
$stmt->bindValue(':indicatif',trim($_POST['indicatif'] ?? ''), SQLITE3_TEXT);
$stmt->bindValue(':freq', trim($_POST['frequence'] ?? ''), SQLITE3_TEXT);
$stmt->bindValue(':mode', trim($_POST['mode'] ?? 'USB'), SQLITE3_TEXT);
$stmt->bindValue(':report', trim($_POST['report'] ?? ''), SQLITE3_TEXT);
$stmt->bindValue(':pays', trim($_POST['pays'] ?? ''), SQLITE3_TEXT);
$stmt->bindValue(':comment', trim($_POST['commentaire']?? ''), SQLITE3_TEXT);
$stmt->execute();
header('Location: /admin/log.php?ajout=ok');
exit;
?>
📄 6. Affichage public du log
Pour la page publique, on lit en lecture seule et on échappe toutes les sorties.
<?php
$db = new SQLite3(__DIR__ . '/admin/activites.db', SQLITE3_OPEN_READONLY);
$res = $db->query("SELECT * FROM activites ORDER BY date_qso DESC, heure_qso DESC LIMIT 50");
while ($row = $res->fetchArray(SQLITE3_ASSOC)):
?>
<tr>
<td><?= htmlspecialchars($row['date_qso']) ?></td>
<td><?= htmlspecialchars($row['heure_qso']) ?> UTC</td>
<td><?= htmlspecialchars($row['indicatif']) ?></td>
<td><?= htmlspecialchars($row['frequence']) ?> MHz</td>
<td><?= htmlspecialchars($row['mode']) ?></td>
<td><?= htmlspecialchars($row['report']) ?></td>
</tr>
<?php endwhile; ?>
📤 7. Export ADIF pour les plateformes eQSL
Le format ADIF (.ADI) est le standard universel d'échange de logs entre logiciels et plateformes. Voici comment générer un fichier ADIF depuis votre base SQLite.
<?php
// Forcer le téléchargement du fichier
header('Content-Type: text/plain; charset=UTF-8');
header('Content-Disposition: attachment; filename="log-dx-' . date('Ymd') . '.adi"');
$db = new SQLite3(__DIR__ . '/admin/activites.db', SQLITE3_OPEN_READONLY);
$res = $db->query("SELECT * FROM activites ORDER BY date_qso ASC");
// En-tête ADIF
echo "<ADIF_VER:5>3.1.0\n";
echo "<PROGRAMID:8>RadioDX\n";
echo "<EOH>\n\n";
// Chaque contact
while ($row = $res->fetchArray(SQLITE3_ASSOC)) {
$call = strtoupper(trim($row['indicatif']));
$date = str_replace('-', '', $row['date_qso']); // YYYYMMDD
$time = str_replace(':', '', $row['heure_qso']); // HHMM
$freq = $row['frequence'];
$mode = strtoupper($row['mode']);
$rst = $row['report'] ?: '59';
echo "<CALL:" . strlen($call) . ">$call ";
echo "<QSO_DATE:" . strlen($date) . ">$date ";
echo "<TIME_ON:" . strlen($time) . ">$time ";
echo "<FREQ:" . strlen($freq) . ">$freq ";
echo "<MODE:" . strlen($mode) . ">$mode ";
echo "<RST_SENT:" . strlen($rst) . ">$rst ";
echo "<EOR>\n";
}
exit;
?>
Ce script est à placer dans votre administration (jamais en public). Il génère un fichier .ADI téléchargeable directement compatible avec Log11DX, ClusterDX et toutes les plateformes eQSL standardisées.
🔒 8. Bonnes pratiques de sécurité
Requêtes préparées obligatoires : n'interpolez jamais
directement les données $_POST dans vos requêtes SQL.
Utilisez toujours bindValue() ou bindParam().
Une injection SQL sur un log DX peut effacer des années d'historique.
Échappement des sorties : toujours utiliser
htmlspecialchars() sur toutes les données affichées
en HTML. Sans ça, un indicatif contenant <script>
peut exécuter du JavaScript arbitraire chez vos visiteurs (XSS).
Administration protégée : les scripts d'ajout, de modification et d'export doivent être dans un dossier admin protégé par session PHP ou .htaccess. Ne jamais exposer ces formulaires sur le web public.
Sauvegarde régulière : copiez votre fichier .db sur un support externe une fois par semaine minimum. Voir le dossier archivage longue durée pour les bonnes pratiques complètes.