Files
2026-10-21 22:00:18 +02:00

454 lines
24 KiB
PHP

<?php
/**
* admin/edit_site.php
*
* Corrige suite a l'audit du 20/09/2026 :
* - Le flux WMS cadastre (URL, nom de couche, attribution HTML) est
* desormais encode en JSON avec JSON_HEX_* au lieu d'addslashes(),
* qui n'est pas un encodeur JavaScript fiable et pouvait permettre
* une injection si un pays contenait une valeur cadastre malicieuse
* (le champ cadastre_attribution_html est explicitement du HTML,
* donc a risque si mal echappe).
* - Les erreurs techniques ($e->getMessage()) ne sont plus affichees
* a l'ecran : elles sont journalisees via error_log() et un message
* generique est montre a l'administrateur.
*
* Note : le controle par role (require_admin_role) est volontairement
* absent de cette version, la separation des privileges etant reportee
* a une migration dediee (voir admin_auth.php).
*
* Version complète :
* - coordonnées GPS avec step="any" ;
* - ajout dynamique de nouvelles cellules ;
* - héritage MCC/MNC depuis l'opérateur du site ;
* - INSERT avec exactement 14 paramètres et NOW() pour validated_at.
*/
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;
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
require_csrf_token();
$action = $_POST['action'] ?? '';
if ($action === 'save_site') {
$siteId = (int)($_POST['site_id'] ?? 0);
$lat = (float)($_POST['lat'] ?? 0);
$lon = (float)($_POST['lon'] ?? 0);
$locationDesc = trim($_POST['location_desc'] ?? '');
$addressDesc = trim($_POST['address_desc'] ?? '');
$cellIds = $_POST['row_cell_pk'] ?? [];
$techs = $_POST['cell_tech'] ?? [];
$cellRadioIds = $_POST['cell_id'] ?? [];
$areaCodes = $_POST['cell_area_code'] ?? [];
$subIds = $_POST['cell_sub_id'] ?? [];
$subIds2 = $_POST['cell_sub_id2'] ?? [];
$bands = $_POST['cell_band'] ?? [];
$frequencies = $_POST['cell_frequency'] ?? [];
$azimuths = $_POST['cell_azimuth'] ?? [];
$bandwidths = $_POST['cell_bandwidth'] ?? [];
$arfcns = $_POST['cell_arfcn'] ?? [];
$newTechs = $_POST['new_cell_tech'] ?? [];
$newCellRadioIds = $_POST['new_cell_id'] ?? [];
$newAreaCodes = $_POST['new_cell_area_code'] ?? [];
$newSubIds = $_POST['new_cell_sub_id'] ?? [];
$newSubIds2 = $_POST['new_cell_sub_id2'] ?? [];
$newBands = $_POST['new_cell_band'] ?? [];
$newFrequencies = $_POST['new_cell_frequency'] ?? [];
$newAzimuths = $_POST['new_cell_azimuth'] ?? [];
$newBandwidths = $_POST['new_cell_bandwidth'] ?? [];
$newArfcns = $_POST['new_cell_arfcn'] ?? [];
if (!$siteId || !$lat || !$lon) {
$errorMessage = 'Coordonnees invalides.';
} else {
$siteMccMncStmt = $db->prepare('
SELECT o.mcc, o.mnc
FROM sites s
JOIN operators o ON o.id = s.operator_id
WHERE s.id = ?
');
$siteMccMncStmt->execute([$siteId]);
$siteMccMnc = $siteMccMncStmt->fetch(PDO::FETCH_ASSOC);
if (!$siteMccMnc) {
$errorMessage = 'Site introuvable, impossible de determiner le MCC/MNC.';
} else {
$siteMcc = $siteMccMnc['mcc'];
$siteMnc = $siteMccMnc['mnc'];
$db->beginTransaction();
try {
$db->prepare('UPDATE sites SET lat = ?, lon = ?, location_desc = ?, address_desc = ? WHERE id = ?')
->execute([$lat, $lon, $locationDesc, $addressDesc, $siteId]);
$updateCell = $db->prepare('
UPDATE cells
SET tech = ?, cell_id = ?, area_code = ?, sub_id = ?, sub_id2 = ?,
band = ?, frequency = ?, azimuth = ?, bandwidth = ?, arfcn = ?,
validated_by = ?, validated_at = NOW()
WHERE id = ? AND site_id = ?
');
foreach ($cellIds as $i => $cellPk) {
$cellPk = (int)$cellPk;
if (!$cellPk) {
continue;
}
$updateCell->execute([
$techs[$i] ?? null,
$cellRadioIds[$i] ?? null,
$areaCodes[$i] ?? null,
($subIds[$i] ?? '') !== '' ? $subIds[$i] : null,
($subIds2[$i] ?? '') !== '' ? $subIds2[$i] : null,
($bands[$i] ?? '') !== '' ? $bands[$i] : null,
($frequencies[$i] ?? '') !== '' ? $frequencies[$i] : null,
($azimuths[$i] ?? '') !== '' ? (int)$azimuths[$i] : null,
($bandwidths[$i] ?? '') !== '' ? $bandwidths[$i] : null,
($arfcns[$i] ?? '') !== '' ? $arfcns[$i] : null,
$user,
$cellPk,
$siteId,
]);
}
// 14 marqueurs (?) et 14 valeurs dans execute().
// validated_at est fourni par NOW(), donc aucune valeur
// supplémentaire ne doit être ajoutée dans execute().
$insertCell = $db->prepare('
INSERT INTO cells
(
site_id, mcc, mnc, tech, cell_id, area_code,
sub_id, sub_id2, band, frequency, azimuth,
bandwidth, arfcn, validated_by, validated_at
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW())
');
foreach ($newTechs as $i => $newTech) {
$newCellIdVal = trim((string)($newCellRadioIds[$i] ?? ''));
$newAreaCodeVal = trim((string)($newAreaCodes[$i] ?? ''));
// Ignore une ligne ajoutée mais non remplie.
if ($newCellIdVal === '' || $newAreaCodeVal === '') {
continue;
}
$insertCell->execute([
$siteId,
$siteMcc,
$siteMnc,
$newTech,
$newCellIdVal,
$newAreaCodeVal,
($newSubIds[$i] ?? '') !== '' ? $newSubIds[$i] : null,
($newSubIds2[$i] ?? '') !== '' ? $newSubIds2[$i] : null,
($newBands[$i] ?? '') !== '' ? $newBands[$i] : null,
($newFrequencies[$i] ?? '') !== '' ? $newFrequencies[$i] : null,
($newAzimuths[$i] ?? '') !== '' ? (int)$newAzimuths[$i] : null,
($newBandwidths[$i] ?? '') !== '' ? $newBandwidths[$i] : null,
($newArfcns[$i] ?? '') !== '' ? $newArfcns[$i] : null,
$user,
]);
}
$db->commit();
$successMessage = 'Site mis a jour.';
} catch (Throwable $e) {
if ($db->inTransaction()) {
$db->rollBack();
}
error_log('[edit_site] save_site: ' . $e->getMessage());
$errorMessage = "Une erreur est survenue lors de l'enregistrement. Consultez les journaux du serveur.";
}
}
}
}
if ($action === 'delete_cell') {
$cellId = (int)($_POST['cell_id_pk'] ?? 0);
$siteId = (int)($_POST['site_id'] ?? 0);
$countStmt = $db->prepare('SELECT COUNT(*) FROM cells WHERE site_id = ?');
$countStmt->execute([$siteId]);
$remaining = (int)$countStmt->fetchColumn();
if ($remaining <= 1) {
$errorMessage = "Impossible de retirer la derniere cellule d'un site : supprimez plutot le site entier.";
} else {
try {
$db->prepare('DELETE FROM cells WHERE id = ? AND site_id = ?')->execute([$cellId, $siteId]);
$successMessage = 'Cellule retiree du site.';
} catch (Throwable $e) {
error_log('[edit_site] delete_cell: ' . $e->getMessage());
$errorMessage = 'Une erreur est survenue lors de la suppression.';
}
}
}
if ($action === 'delete_site') {
$siteId = (int)($_POST['site_id'] ?? 0);
try {
$db->prepare('DELETE FROM sites WHERE id = ?')->execute([$siteId]);
header('Location: edit_site.php?deleted=1');
exit;
} catch (Throwable $e) {
error_log('[edit_site] delete_site: ' . $e->getMessage());
$errorMessage = 'Une erreur est survenue lors de la suppression du site.';
}
}
}
$editingSiteId = isset($_GET['site_id']) ? (int)$_GET['site_id'] : null;
$editingSite = null;
$editingCells = [];
if ($editingSiteId) {
$siteStmt = $db->prepare('
SELECT s.*, o.brand AS operator_brand, o.mcc, o.mnc,
c.name AS country_name, c.default_lat, c.default_lon, c.default_zoom,
c.cadastre_wms_url, c.cadastre_wms_layer,
c.cadastre_attribution_html
FROM sites s
JOIN operators o ON o.id = s.operator_id
JOIN countries c ON c.id = s.country_id
WHERE s.id = ?
');
$siteStmt->execute([$editingSiteId]);
$editingSite = $siteStmt->fetch(PDO::FETCH_ASSOC);
if ($editingSite) {
$cellsStmt = $db->prepare('SELECT * FROM cells WHERE site_id = ? ORDER BY tech, cell_id');
$cellsStmt->execute([$editingSiteId]);
$editingCells = $cellsStmt->fetchAll(PDO::FETCH_ASSOC);
}
}
$mapLat = null;
$mapLon = null;
$coordsInvalid = false;
if ($editingSite) {
$siteLat = (float)$editingSite['lat'];
$siteLon = (float)$editingSite['lon'];
if ($siteLat === 0.0 && $siteLon === 0.0) {
$coordsInvalid = true;
$mapLat = (float)($editingSite['default_lat'] ?? 20);
$mapLon = (float)($editingSite['default_lon'] ?? 0);
} else {
$mapLat = $siteLat;
$mapLon = $siteLon;
}
}
$searchQuery = trim($_GET['q'] ?? '');
$countryFilter = $_GET['country'] ?? '';
$operatorFilter = $_GET['operator'] ?? '';
$sites = [];
if (!$editingSiteId) {
$sql = '
SELECT s.id, s.lat, s.lon, s.location_desc, s.address_desc,
o.brand AS operator_brand, c.name AS country_name,
GROUP_CONCAT(DISTINCT cl.tech ORDER BY cl.tech SEPARATOR ", ") AS techs,
COUNT(cl.id) AS cell_count
FROM sites s
JOIN operators o ON o.id = s.operator_id
JOIN countries c ON c.id = s.country_id
LEFT JOIN cells cl ON cl.site_id = s.id
WHERE 1=1
';
$params = [];
if ($searchQuery !== '') {
$sql .= ' AND (s.location_desc LIKE ? OR s.address_desc LIKE ? OR cl.cell_id LIKE ? OR cl.sub_id2 LIKE ?)';
$like = '%' . $searchQuery . '%';
array_push($params, $like, $like, $like, $like);
}
if ($countryFilter !== '') {
$sql .= ' AND c.iso2 = ?';
$params[] = $countryFilter;
}
if ($operatorFilter !== '') {
$sql .= ' AND o.id = ?';
$params[] = $operatorFilter;
}
$sql .= ' GROUP BY s.id ORDER BY s.created_at DESC LIMIT 200';
$stmt = $db->prepare($sql);
$stmt->execute($params);
$sites = $stmt->fetchAll(PDO::FETCH_ASSOC);
}
$countries = $db->query('SELECT id, iso2, name FROM countries ORDER BY name')->fetchAll(PDO::FETCH_ASSOC);
$operators = $db->query('SELECT id, mcc, mnc, brand FROM operators ORDER BY brand')->fetchAll(PDO::FETCH_ASSOC);
$deletedNotice = isset($_GET['deleted']);
?>
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>NetMonster Admin - Edition de site</title>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" crossorigin>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" crossorigin></script>
<link rel="stylesheet" href="assets/theme.css">
<style>
.search-row { display:flex; gap:.6rem; margin-bottom:1rem; flex-wrap:wrap; }
.search-row input[type=text] { flex:1; min-width:220px; }
table.sites-table td { vertical-align:middle; }
button.edit-btn { background:var(--primary); color:#fff; border:none; padding:.3rem .7rem; border-radius:4px; cursor:pointer; font-size:.78rem; }
.danger-zone { border:1px solid #f3c1bd; background:#fdeceb; border-radius:var(--radius-sm); padding:1rem; margin-top:1.4rem; }
.danger-zone h3 { margin:0 0 .5rem; font-size:.95rem; color:var(--danger-dark); }
#editMap { height:320px; width:100%; margin-bottom:1rem; background:#e8eaf0; border-radius:var(--radius-sm); }
button.add-cell-btn { background:var(--primary); color:#fff; border:none; padding:.4rem .9rem; border-radius:4px; cursor:pointer; font-size:.82rem; margin:.6rem 0 1rem; }
tr.new-cell-row td { background:#f3f8ff; }
</style>
</head>
<body>
<?php render_admin_nav('edit_site.php', $user); ?>
<div class="page-wrap">
<?php if (!$editingSiteId): ?>
<h1>Edition d'un site valide</h1>
<p class="page-subtitle">Recherchez un site pour corriger ses coordonnees, sa description, ou les parametres radio de ses cellules.</p>
<?php if ($deletedNotice): ?><p class="msg-ok">Site supprime avec succes.</p><?php endif; ?>
<?php if ($successMessage): ?><p class="msg-ok"><?= htmlspecialchars($successMessage, ENT_QUOTES, 'UTF-8') ?></p><?php endif; ?>
<div class="card">
<form class="search-row" method="get">
<input type="text" name="q" placeholder="Rechercher par lieu, adresse, Cell ID ou eNB..." value="<?= htmlspecialchars($searchQuery, ENT_QUOTES, 'UTF-8') ?>">
<select name="country"><option value="">Tous les pays</option><?php foreach ($countries as $c): ?><option value="<?= htmlspecialchars($c['iso2'], ENT_QUOTES, 'UTF-8') ?>" <?= $countryFilter === $c['iso2'] ? 'selected' : '' ?>><?= htmlspecialchars($c['name'], ENT_QUOTES, 'UTF-8') ?></option><?php endforeach; ?></select>
<select name="operator"><option value="">Tous les operateurs</option><?php foreach ($operators as $o): ?><option value="<?= $o['id'] ?>" <?= (string)$operatorFilter === (string)$o['id'] ? 'selected' : '' ?>><?= htmlspecialchars($o['brand'], ENT_QUOTES, 'UTF-8') ?></option><?php endforeach; ?></select>
<button type="submit" class="primary">Rechercher</button>
</form>
<table class="data-table sites-table">
<tr><th>Operateur</th><th>Pays</th><th>Technos</th><th>Cellules</th><th>Lieu</th><th>Lat/Lon</th><th></th></tr>
<?php foreach ($sites as $s): ?>
<tr><td><?= htmlspecialchars($s['operator_brand'], ENT_QUOTES, 'UTF-8') ?></td><td><?= htmlspecialchars($s['country_name'], ENT_QUOTES, 'UTF-8') ?></td><td><?= htmlspecialchars((string)($s['techs'] ?? ''), ENT_QUOTES, 'UTF-8') ?></td><td><?= (int)$s['cell_count'] ?></td><td><?= htmlspecialchars($s['address_desc'] ?? $s['location_desc'] ?? '', ENT_QUOTES, 'UTF-8') ?></td><td><?= htmlspecialchars((string)$s['lat'], ENT_QUOTES, 'UTF-8') ?>, <?= htmlspecialchars((string)$s['lon'], ENT_QUOTES, 'UTF-8') ?></td><td><button class="edit-btn" onclick="window.location='?site_id=<?= $s['id'] ?>'">Editer</button></td></tr>
<?php endforeach; ?>
<?php if (empty($sites)): ?><tr><td colspan="7" class="no-cadastre">Aucun site trouve.</td></tr><?php endif; ?>
</table>
</div>
<?php elseif ($editingSite): ?>
<h1>Edition du site &mdash; <?= htmlspecialchars($editingSite['operator_brand'], ENT_QUOTES, 'UTF-8') ?></h1>
<p class="page-subtitle"><a href="edit_site.php">&larr; Retour a la liste</a></p>
<?php if ($successMessage): ?><p class="msg-ok"><?= htmlspecialchars($successMessage, ENT_QUOTES, 'UTF-8') ?></p><?php endif; ?>
<?php if ($errorMessage): ?><p class="msg-err"><?= htmlspecialchars($errorMessage, ENT_QUOTES, 'UTF-8') ?></p><?php endif; ?>
<?php if ($coordsInvalid): ?><p class="msg-err">Attention : les coordonnees enregistrees pour ce site sont a 0,0 (invalides). La carte est centree sur le pays par defaut ; corrigez la position ci-dessous.</p><?php endif; ?>
<div class="card">
<h2>Localisation</h2><div id="editMap"></div>
<form method="post" id="editSiteForm">
<?= csrf_field() ?>
<input type="hidden" name="action" value="save_site">
<input type="hidden" name="site_id" value="<?= (int)$editingSite['id'] ?>">
<div class="form-grid wide-3">
<div><label>Latitude</label><input type="number" step="any" name="lat" id="latInput" value="<?= htmlspecialchars((string)$editingSite['lat'], ENT_QUOTES, 'UTF-8') ?>" required></div>
<div><label>Longitude</label><input type="number" step="any" name="lon" id="lonInput" value="<?= htmlspecialchars((string)$editingSite['lon'], ENT_QUOTES, 'UTF-8') ?>" required></div>
<div><label>Operateur / pays (non modifiable ici)</label><input type="text" value="<?= htmlspecialchars($editingSite['operator_brand'] . ' - ' . $editingSite['country_name'], ENT_QUOTES, 'UTF-8') ?>" disabled></div>
</div>
<div class="form-grid" style="margin-top:.6rem"><div><label>Adresse / lieu</label><input type="text" name="address_desc" value="<?= htmlspecialchars($editingSite['address_desc'] ?? '', ENT_QUOTES, 'UTF-8') ?>"></div><div><label>Description (Location brute, optionnel)</label><input type="text" name="location_desc" value="<?= htmlspecialchars($editingSite['location_desc'] ?? '', ENT_QUOTES, 'UTF-8') ?>"></div></div>
<h2 style="margin-top:1.4rem">Cellules du site (<?= count($editingCells) ?>)</h2>
<table class="data-table" id="cellsTable">
<tr><th>Techno</th><th>Cell ID</th><th>Area code</th><th>Sub ID</th><th>eNB/RNC</th><th>Bande</th><th>Frequence</th><th>Azimut</th><th>Largeur</th><th>ARFCN</th><th></th></tr>
<?php foreach ($editingCells as $c): ?>
<tr>
<input type="hidden" name="row_cell_pk[]" value="<?= (int)$c['id'] ?>">
<td><select name="cell_tech[]"><?php foreach (['2G','3G','4G','5G','CDMA'] as $t): ?><option value="<?= $t ?>" <?= $c['tech'] === $t ? 'selected' : '' ?>><?= $t ?></option><?php endforeach; ?></select></td>
<td><input type="text" name="cell_id[]" value="<?= htmlspecialchars((string)$c['cell_id'], ENT_QUOTES, 'UTF-8') ?>"></td>
<td><input type="text" name="cell_area_code[]" value="<?= htmlspecialchars((string)$c['area_code'], ENT_QUOTES, 'UTF-8') ?>"></td>
<td><input type="text" name="cell_sub_id[]" value="<?= htmlspecialchars((string)($c['sub_id'] ?? ''), ENT_QUOTES, 'UTF-8') ?>"></td>
<td><input type="text" name="cell_sub_id2[]" value="<?= htmlspecialchars((string)($c['sub_id2'] ?? ''), ENT_QUOTES, 'UTF-8') ?>"></td>
<td><input type="text" name="cell_band[]" value="<?= htmlspecialchars((string)($c['band'] ?? ''), ENT_QUOTES, 'UTF-8') ?>"></td>
<td><input type="text" name="cell_frequency[]" value="<?= htmlspecialchars((string)($c['frequency'] ?? ''), ENT_QUOTES, 'UTF-8') ?>"></td>
<td><input type="number" min="0" max="359" name="cell_azimuth[]" value="<?= htmlspecialchars((string)($c['azimuth'] ?? ''), ENT_QUOTES, 'UTF-8') ?>"></td>
<td><input type="text" name="cell_bandwidth[]" value="<?= htmlspecialchars((string)($c['bandwidth'] ?? ''), ENT_QUOTES, 'UTF-8') ?>"></td>
<td><input type="text" name="cell_arfcn[]" value="<?= htmlspecialchars((string)($c['arfcn'] ?? ''), ENT_QUOTES, 'UTF-8') ?>"></td>
<td><button type="button" class="reject-small" onclick="deleteCell(<?= (int)$c['id'] ?>, <?= (int)$editingSiteId ?>)">Retirer</button></td>
</tr>
<?php endforeach; ?>
</table>
<button type="button" class="add-cell-btn" id="addCellBtn">+ Ajouter une bande</button>
<button type="submit" class="validate" style="margin-top:1rem">Enregistrer les modifications</button>
</form>
</div>
<div class="danger-zone"><h3>Zone sensible</h3><p class="page-subtitle" style="margin-bottom:.6rem">La suppression du site retire aussi TOUTES ses cellules de la base validee (irreversible).</p><form method="post" onsubmit="return confirm('Supprimer definitivement ce site et toutes ses cellules ?')"><?= csrf_field() ?><input type="hidden" name="action" value="delete_site"><input type="hidden" name="site_id" value="<?= (int)$editingSiteId ?>"><button type="submit" class="reject">Supprimer ce site</button></form></div>
<form method="post" id="deleteCellForm" style="display:none"><?= csrf_field() ?><input type="hidden" name="action" value="delete_cell"><input type="hidden" name="cell_id_pk" id="deleteCellPk"><input type="hidden" name="site_id" id="deleteCellSiteId"></form>
<script>
function deleteCell(cellPk, siteId) {
if (!confirm('Retirer cette cellule du site ?')) return;
document.getElementById('deleteCellPk').value = cellPk;
document.getElementById('deleteCellSiteId').value = siteId;
document.getElementById('deleteCellForm').submit();
}
let newCellIndex = 0;
document.getElementById('addCellBtn').addEventListener('click', function () {
const table = document.getElementById('cellsTable');
const row = document.createElement('tr');
row.className = 'new-cell-row';
row.innerHTML = `
<td><select name="new_cell_tech[${newCellIndex}]"><option value="2G">2G</option><option value="3G">3G</option><option value="4G" selected>4G</option><option value="5G">5G</option><option value="CDMA">CDMA</option></select></td>
<td><input type="text" name="new_cell_id[${newCellIndex}]" placeholder="Cell ID"></td>
<td><input type="text" name="new_cell_area_code[${newCellIndex}]" placeholder="Area code"></td>
<td><input type="text" name="new_cell_sub_id[${newCellIndex}]" placeholder="Sub ID"></td>
<td><input type="text" name="new_cell_sub_id2[${newCellIndex}]" placeholder="eNB/RNC"></td>
<td><input type="text" name="new_cell_band[${newCellIndex}]" placeholder="Bande (ex: B7)"></td>
<td><input type="text" name="new_cell_frequency[${newCellIndex}]" placeholder="Frequence"></td>
<td><input type="number" min="0" max="359" name="new_cell_azimuth[${newCellIndex}]" placeholder="Azimut"></td>
<td><input type="text" name="new_cell_bandwidth[${newCellIndex}]" placeholder="Largeur"></td>
<td><input type="text" name="new_cell_arfcn[${newCellIndex}]" placeholder="ARFCN"></td>
<td><button type="button" class="reject-small" onclick="this.closest('tr').remove()">Annuler</button></td>
`;
table.appendChild(row);
newCellIndex++;
});
try {
const editMap = L.map('editMap').setView([<?= $mapLat ?>, <?= $mapLon ?>], <?= $coordsInvalid ? 6 : 16 ?>);
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19,
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright" target="_blank">OpenStreetMap</a> contributors'
}).addTo(editMap);
<?php if (!empty($editingSite['cadastre_wms_url']) && !empty($editingSite['cadastre_wms_layer'])): ?>
L.tileLayer.wms(
<?= json_encode($editingSite['cadastre_wms_url'], JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT) ?>,
{
layers: <?= json_encode($editingSite['cadastre_wms_layer'], JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT) ?>,
format: 'image/png',
transparent: true,
opacity: 0.6,
attribution: <?= json_encode($editingSite['cadastre_attribution_html'] ?? '', JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT) ?>
}
).addTo(editMap);
<?php endif; ?>
const marker = L.marker([<?= $mapLat ?>, <?= $mapLon ?>], {draggable: true}).addTo(editMap);
marker.on('dragend', function (e) {
const pos = e.target.getLatLng();
document.getElementById('latInput').value = pos.lat.toFixed(6);
document.getElementById('lonInput').value = pos.lng.toFixed(6);
});
setTimeout(() => editMap.invalidateSize(), 100);
window.addEventListener('resize', () => editMap.invalidateSize());
} catch (err) {
document.getElementById('editMap').innerHTML = '<p style="padding:1rem;color:#b71c1c">Erreur de chargement de la carte.</p>';
console.error('Erreur Leaflet sur edit_site.php :', err);
}
</script>
<?php else: ?>
<p class="msg-err">Site introuvable.</p>
<p><a href="edit_site.php">&larr; Retour a la liste</a></p>
<?php endif; ?>
</div>
</body>
</html>