EARFCN par operateur.
*
* Cette version inclut la gestion des plages EARFCN par bande pour chaque
* operateur, car les operateurs utilisent des valeurs differentes meme
* sur la meme bande (ex: POST B20 != Tango B20).
*/
declare(strict_types=1);
require_once __DIR__ . '/admin_auth.php';
require_once __DIR__ . '/../lib/db.php';
require_once __DIR__ . '/_nav.php';
require_admin_auth();
$db = pdo();
$user = current_admin_username();
$successMessage = null;
$errorMessage = null;
function nullable_float(array $input, string $key): ?float
{
$value = trim((string)($input[$key] ?? ''));
if ($value === '') {
return null;
}
if (!is_numeric($value)) {
throw new InvalidArgumentException("$key doit etre un nombre.");
}
return (float)$value;
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
require_csrf_token();
$action = $_POST['action'] ?? '';
try {
if ($action === 'save_country') {
$countryId = (int)($_POST['country_id'] ?? 0);
$mcc = trim((string)($_POST['mcc'] ?? ''));
$iso2 = strtoupper(trim((string)($_POST['iso2'] ?? '')));
$name = trim((string)($_POST['name'] ?? ''));
$lat = nullable_float($_POST, 'default_lat');
$lon = nullable_float($_POST, 'default_lon');
$zoomRaw = trim((string)($_POST['default_zoom'] ?? ''));
$zoom = $zoomRaw === '' ? 9 : (int)$zoomRaw;
$wmsUrl = trim((string)($_POST['cadastre_wms_url'] ?? '')) ?: null;
$wmsLayer = trim((string)($_POST['cadastre_wms_layer'] ?? '')) ?: null;
$attribution = trim((string)($_POST['cadastre_attribution_html'] ?? '')) ?: null;
$srs = trim((string)($_POST['cadastre_srs'] ?? '')) ?: 'EPSG:3857';
if (!preg_match('/^\d{3}$/', $mcc)) {
throw new InvalidArgumentException('MCC : 3 chiffres obligatoires.');
}
if (!preg_match('/^[A-Z]{2}$/', $iso2)) {
throw new InvalidArgumentException('ISO2 : 2 lettres obligatoires.');
}
if ($name === '') {
throw new InvalidArgumentException('Le nom du pays est obligatoire.');
}
if (($lat === null) !== ($lon === null)) {
throw new InvalidArgumentException('Saisissez latitude et longitude ensemble, ou laissez les deux champs vides.');
}
if ($lat !== null && ($lat < -90 || $lat > 90)) {
throw new InvalidArgumentException('La latitude doit etre comprise entre -90 et 90.');
}
if ($lon !== null && ($lon < -180 || $lon > 180)) {
throw new InvalidArgumentException('La longitude doit etre comprise entre -180 et 180.');
}
if ($zoom < 1 || $zoom > 19) {
throw new InvalidArgumentException('Le zoom doit etre compris entre 1 et 19.');
}
if ($countryId > 0) {
$stmt = $db->prepare(
'UPDATE countries
SET mcc = ?, iso2 = ?, name = ?, default_lat = ?, default_lon = ?, default_zoom = ?,
cadastre_wms_url = ?, cadastre_wms_layer = ?,
cadastre_attribution_html = ?, cadastre_srs = ?
WHERE id = ?'
);
$stmt->execute([$mcc, $iso2, $name, $lat, $lon, $zoom, $wmsUrl, $wmsLayer, $attribution, $srs, $countryId]);
if ($stmt->rowCount() === 0) {
$exists = $db->prepare('SELECT 1 FROM countries WHERE id = ?');
$exists->execute([$countryId]);
if (!$exists->fetchColumn()) {
throw new RuntimeException('Pays introuvable.');
}
}
$successMessage = "Pays \"$name\" mis a jour.";
} else {
$stmt = $db->prepare(
'INSERT INTO countries
(mcc, iso2, name, default_lat, default_lon, default_zoom,
cadastre_wms_url, cadastre_wms_layer, cadastre_attribution_html, cadastre_srs)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'
);
$stmt->execute([$mcc, $iso2, $name, $lat, $lon, $zoom, $wmsUrl, $wmsLayer, $attribution, $srs]);
$successMessage = "Pays \"$name\" cree.";
}
} elseif ($action === 'delete_country') {
$countryId = (int)($_POST['country_id'] ?? 0);
if ($countryId <= 0) {
throw new InvalidArgumentException('Pays invalide.');
}
$check = $db->prepare('SELECT COUNT(*) FROM operators WHERE country_id = ?');
$check->execute([$countryId]);
if ((int)$check->fetchColumn() > 0) {
throw new RuntimeException('Des operateurs sont rattaches a ce pays : supprimez-les ou reaffectez-les d\'abord.');
}
$db->prepare('DELETE FROM countries WHERE id = ?')->execute([$countryId]);
$successMessage = 'Pays supprime.';
} elseif ($action === 'save_operator') {
$operatorId = (int)($_POST['operator_id'] ?? 0);
$countryId = (int)($_POST['country_id'] ?? 0);
$mnc = str_pad(trim((string)($_POST['mnc'] ?? '')), 2, '0', STR_PAD_LEFT);
$brand = trim((string)($_POST['brand'] ?? ''));
$fullName = trim((string)($_POST['full_name'] ?? '')) ?: null;
if ($countryId <= 0 || !preg_match('/^\d{2,3}$/', $mnc) || $brand === '') {
throw new InvalidArgumentException('Pays, MNC (2 ou 3 chiffres) et nom commercial sont obligatoires.');
}
$mccStmt = $db->prepare('SELECT mcc FROM countries WHERE id = ?');
$mccStmt->execute([$countryId]);
$mcc = $mccStmt->fetchColumn();
if ($mcc === false) {
throw new InvalidArgumentException('Pays invalide.');
}
if ($operatorId > 0) {
$stmt = $db->prepare('UPDATE operators SET country_id = ?, mcc = ?, mnc = ?, brand = ?, full_name = ? WHERE id = ?');
$stmt->execute([$countryId, $mcc, $mnc, $brand, $fullName, $operatorId]);
$successMessage = "Operateur \"$brand\" mis a jour.";
} else {
$stmt = $db->prepare('INSERT INTO operators (country_id, mcc, mnc, brand, full_name) VALUES (?, ?, ?, ?, ?)');
$stmt->execute([$countryId, $mcc, $mnc, $brand, $fullName]);
$operatorId = (int)$db->lastInsertId();
$successMessage = "Operateur \"$brand\" ajoute.";
}
} elseif ($action === 'delete_operator') {
$operatorId = (int)($_POST['operator_id'] ?? 0);
if ($operatorId <= 0) {
throw new InvalidArgumentException('Operateur invalide.');
}
$check = $db->prepare('SELECT COUNT(*) FROM sites WHERE operator_id = ?');
$check->execute([$operatorId]);
if ((int)$check->fetchColumn() > 0) {
throw new RuntimeException('Des sites valides existent pour cet operateur.');
}
$db->prepare('DELETE FROM operators WHERE id = ?')->execute([$operatorId]);
$successMessage = 'Operateur supprime.';
} elseif ($action === 'save_band_mapping') {
$operatorId = (int)($_POST['operator_id'] ?? 0);
$band = trim((string)($_POST['band'] ?? ''));
$earfcnStart = (int)($_POST['earfcn_start'] ?? 0);
$earfcnEnd = (int)($_POST['earfcn_end'] ?? 0);
if ($operatorId <= 0 || !preg_match('/^B\d+[a-z]?$/i', $band)) {
throw new InvalidArgumentException('Operateur et bande (ex: B20, B3, B7) obligatoires.');
}
if ($earfcnStart <= 0 || $earfcnEnd < $earfcnStart) {
throw new InvalidArgumentException('Plage EARFCN invalide (start > 0, end >= start).');
}
$stmt = $db->prepare(
'INSERT INTO operator_band_mappings (operator_id, band, earfcn_start, earfcn_end)
VALUES (?, ?, ?, ?)
ON DUPLICATE KEY UPDATE earfcn_start = VALUES(earfcn_start), earfcn_end = VALUES(earfcn_end)'
);
$stmt->execute([$operatorId, strtoupper($band), $earfcnStart, $earfcnEnd]);
$successMessage = "Mapping bande $band enregistre pour l'operateur.";
} elseif ($action === 'delete_band_mapping') {
$mappingId = (int)($_POST['mapping_id'] ?? 0);
if ($mappingId <= 0) {
throw new InvalidArgumentException('Mapping invalide.');
}
$db->prepare('DELETE FROM operator_band_mappings WHERE id = ?')->execute([$mappingId]);
$successMessage = 'Mapping supprime.';
}
} catch (InvalidArgumentException|RuntimeException $e) {
$errorMessage = $e->getMessage();
} catch (PDOException $e) {
error_log('[countries_settings] PDOException: ' . $e->getMessage());
$errorMessage = str_contains($e->getMessage(), 'Duplicate')
? 'Ce couple MCC/MNC ou ce mapping bande existe deja.'
: 'Une erreur est survenue. Consultez les journaux du serveur si le probleme persiste.';
}
}
$countries = $db->query('SELECT * FROM countries ORDER BY name')->fetchAll(PDO::FETCH_ASSOC);
$operators = $db->query(
'SELECT o.*, c.name AS country_name FROM operators o
JOIN countries c ON c.id = o.country_id
ORDER BY c.name, o.brand'
)->fetchAll(PDO::FETCH_ASSOC);
$operatorsByCountry = [];
foreach ($operators as $op) {
$operatorsByCountry[$op['country_id']][] = $op;
}
// Charger les mappings bande/EARFCN pour chaque operateur
$bandMappingsByOperator = [];
$mappings = $db->query('SELECT * FROM operator_band_mappings ORDER BY operator_id, band')->fetchAll(PDO::FETCH_ASSOC);
foreach ($mappings as $m) {
$bandMappingsByOperator[$m['operator_id']][] = $m;
}
$editingCountry = null;
$editCountryId = filter_input(INPUT_GET, 'edit_country', FILTER_VALIDATE_INT);
if ($editCountryId) {
foreach ($countries as $country) {
if ((int)$country['id'] === $editCountryId) {
$editingCountry = $country;
break;
}
}
}
$editingOperator = null;
$editOperatorId = filter_input(INPUT_GET, 'edit_operator', FILTER_VALIDATE_INT);
if ($editOperatorId) {
foreach ($operators as $operator) {
if ((int)$operator['id'] === $editOperatorId) {
$editingOperator = $operator;
break;
}
}
}
function field_value(?array $source, string $key, string $fallback = ''): string
{
return htmlspecialchars((string)($source[$key] ?? $fallback), ENT_QUOTES, 'UTF-8');
}
$countryFormTitle = $editingCountry ? 'Modifier le pays : ' . field_value($editingCountry, 'name') : 'Ajouter un pays';
$operatorFormTitle = $editingOperator ? 'Modifier l\'operateur : ' . field_value($editingOperator, 'brand') : 'Ajouter un operateur';
?>
NetMonster Admin - Pays & operateurs
Configuration des pays & operateurs
Configurez les pays, le centre de carte, les couches cadastre, les operateurs et les mappings bande/EARFCN.
= htmlspecialchars($successMessage, ENT_QUOTES, 'UTF-8') ?>
= htmlspecialchars($errorMessage, ENT_QUOTES, 'UTF-8') ?>
Pays configures (= count($countries) ?>)
Pays MCC ISO2 Centre carte Cadastre Operateurs Actions
= field_value($country, 'name') ?>
= field_value($country, 'mcc') ?>
= field_value($country, 'iso2') ?>
= $country['default_lat'] !== null ? field_value($country, 'default_lat') . ', ' . field_value($country, 'default_lon') : '—' ?>
configure
non configure
= count($operatorsByCountry[$country['id']] ?? []) ?>
Modifier
Aucun pays configure.
Operateurs configures (= count($operators) ?>)
Pays Brand MCC-MNC Raison sociale Actions
= field_value($operator, 'country_name') ?>
= field_value($operator, 'brand') ?>
= field_value($operator, 'mcc') ?>-= field_value($operator, 'mnc') ?>
= field_value($operator, 'full_name') ?>
Modifier
Aucun operateur configure.
Mappings bande 4G → EARFCN par operateur
Configurez les plages EARFCN utilisees par chaque operateur sur chaque bande 4G. Ces valeurs sont utilisees lors de l'import CellMapper pour deduire l'EARFCN a partir de la bande selectionnee.
= field_value($operator, 'brand') ?> (MCC = field_value($operator, 'mcc') ?> / MNC = field_value($operator, 'mnc') ?>)
Bande EARFCN debut EARFCN fin Actions
= field_value($mapping, 'band') ?>
= (int)$mapping['earfcn_start'] ?>
= (int)$mapping['earfcn_end'] ?>
Aucun mapping configure pour cet operateur.
Aucun operateur configure. Ajoutez d'abord un operateur ci-dessus.