Compare commits

..

2 Commits

5 changed files with 371 additions and 14 deletions

View File

@@ -23,6 +23,7 @@
<a href="<?=$config['rel_root_folder']?>admin/logs" class="button"><i class="fas fa-history"></i> Voir les logs</a> <small>Permet d'accéder aux 200 dernières lignes des logs bruts des actions sur la base de données.</small><br><br>
<a href="<?=$config['rel_root_folder']?>admin/wiki-files" class="button"><i class="fas fa-paperclip"></i> Fichiers attachés</a><small>Gérer les fichiers attachés pour le wiki : liste, ajout, suppression...</small><br><br>
<a href="<?=$config['rel_root_folder']?>admin/stats" class="button"><i class="fas fa-chart-line"></i> Statistiques</a><small>Analyser les logs et afficher les statistiques.</small><br><br>
<a href="<?=$config['rel_root_folder']?>admin/wri-import" class="button"><i class="fas fa-cloud-download-alt"></i> Import WRI</a><small>Importe les points de Refuges.info.</small><br><br>
<?php } ?>
</section>

View File

@@ -0,0 +1,30 @@
<!DOCTYPE html>
<!-- Page: admin logs -->
<html lang="fr">
<?php include('blocks/d.head.html'); ?>
<body>
<?php include('blocks/d.nav.html'); ?>
<section>
<h1><?=$head['title']?></h1>
<p>
Limport depuis <strong>Refuges.info</strong> vient dêtre exécuté.
</p>
<h2>Résumé</h2>
<ul>
<li><strong>Nouveaux POIs créés :</strong> <?= $result['created'] ?></li>
<li><strong>POIs mis à jour :</strong> <?= $result['updated'] ?></li>
<li><strong>Total analysés :</strong> <?= $result['total'] ?></li>
</ul>
</section>
<?php include('blocks/d.footer.html'); ?>
</body>
</html>

View File

@@ -178,7 +178,7 @@
</div>
<?php if ($isCommentable) { ?>
<?php if ($isCommentable && $poi->source_id=='kab') { ?>
<?php if (isset($poi_comments) && $poi_comments->number > 0) { ?>
<div id="comments_photos_gallery" class="gallery">
<?php foreach ($poi_comments->objs as $comment) { ?>
@@ -287,6 +287,8 @@
<?php } ?>
<?php } ?>
<?php if($poi->source_id!='kab') {?><br><div style="text-align: center; font-style: italic;">Données fournies par <a href="https://refuges.info/point/<?=$poi->remote_source_id?>" target="_blank"><i class="fas fa-external-link-alt"></i> <?=$poi->source?></a> sous licence CC BY-SA</div><?php } ?>
</section>
<?php include('blocks/d.footer.html'); ?>

View File

@@ -250,10 +250,12 @@ if(isset($controller->splitted_url[1]) && $user->rankIsHigher("moderator")) {
$timestamp = date('Ymd_His');
$backup_source[0] = $config['public_folder'].'medias/avatars';
$backup_source[1] = $config['public_folder'].'medias/wiki';
$backup_source[2] = $config['public_folder'].'medias/comment_photos';
$backup_filename[0] = $timestamp.'_avatar_files.zip';
$backup_filename[1] = $timestamp.'_wiki_files.zip';
$backup_filename[2] = $timestamp.'_comment_photos.zip';
for($i=0;$i<2;$i++) {
for($i=0;$i<3;$i++) {
$backup_file[$i] = $config['public_folder'].'tmp/'.$backup_filename[$i];
$backup[$i] = new ZipArchive();
@@ -328,6 +330,22 @@ if(isset($controller->splitted_url[1]) && $user->rankIsHigher("moderator")) {
$notfound = 1;
}
break;
case 'wri-import':
if ($user->rankIsHigher("moderator")) {
require_once($config['abs_root_folder']."src/Import/wri.php");
$head['title'] = "Import Refuges.info";
$importer = new \Kabano\Import\WriImporter();
$result = $importer->importAll(false);
include ($config['views_folder']."d.admin.wri-import.html");
}
else {
$notfound = 1;
}
break;
default:
$notfound = 1;
break;

306
src/Import/wri.php Normal file
View File

@@ -0,0 +1,306 @@
<?php
namespace Kabano\Import;
use Kabano\Poi;
use Exception;
require_once($config['models_folder']."d.poi.php");
require_once($config['includes_folder']."poi_types.struct.php");
class WriFetcher
{
private string $url;
public function __construct(
string $url = 'https://www.refuges.info/api/bbox?detail=complet&nb_points=all'
) {
$this->url = $url;
}
public function fetchAll(): array
{
$json = $this->download($this->url);
$data = json_decode($json, true);
if ($data === null) {
throw new Exception(
'JSON invalide reçu depuis refuges.info : ' . json_last_error_msg()
);
}
return $data;
}
private function download(string $url): string
{
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_TIMEOUT => 20,
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_USERAGENT => 'KabanoBot/1.0 (+https://kabano.org)',
]);
$response = curl_exec($ch);
if ($response === false) {
$error = curl_error($ch);
throw new Exception("Erreur réseau lors du téléchargement : $error");
}
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status < 200 || $status >= 300) {
throw new Exception("HTTP $status reçu depuis $url");
}
return $response;
}
}
class WriMapper
{
private array $poi_types;
public function __construct()
{
global $poi_types;
$this->poi_types = $poi_types;
}
public function map(array $raw): Poi
{
if (!isset($raw['properties']) || !isset($raw['geometry'])) {
throw new Exception("Feature GeoJSON invalide");
}
$p = $raw['properties'];
$g = $raw['geometry'];
// Type WRI (structure confirmée)
$wriTypeId = $p['type']['id'] ?? null;
// Filtrage strict
$poiType = $this->mapTypeFromWriId((int)$wriTypeId);
if ($poiType === null) {
throw new Exception("Type WRI non supporté : $wriTypeId");
}
// Si WRI indique "manque un mur", on force le type en basic_hut
if (($p['info_comp']['manque_un_mur']['valeur'] ?? '') === 'Oui') {
$poiType = 'basic_hut';
}
$poi = new Poi();
$poi->source_id = 'wri';
$poi->remote_source_id = $p['id'];
$poi->name = $p['nom'] ?? '—';
$poi->permalink = $this->slugify($poi->name);
$poi->poi_type = $poiType;
// Coordonnées
$poi->lat = $g['coordinates'][1] ?? null;
$poi->lon = $g['coordinates'][0] ?? null;
$poi->ele = $p['coord']['alt'] ?? 0;
$poi->locale = 'fr_FR';
$poi->is_commentable = true;
// Paramètres JSON conformes à ton modèle
$poi->parameters = json_encode(
$this->buildParameters($poiType, $p),
JSON_UNESCAPED_UNICODE
);
return $poi;
}
private function mapTypeFromWriId(int $id): ?string
{
return match ($id) {
10 => 'alpine_hut', // refuge gardé
7 => 'wilderness_hut', // cabane non gardée
9 => 'halt', // gîte d'étape
default => null
};
}
private function cleanValue($value): string
{
if (!is_string($value)) return '';
$value = strip_tags(str_replace(['[b]', '[/b]'], '', $value));
return trim($value);
}
private function isYes($value): int
{
// Nettoyage des balises WRI
$clean = strip_tags(str_replace(['[b]', '[/b]'], '', (string)$value));
$clean = trim($clean);
return match ($clean) {
'Oui' => 2,
'Non' => 0,
'Inconnu' => 1,
default => -1
};
}
private function buildParameters(string $poi_type, array $p): array
{
$fields = $this->poi_types[$poi_type][5];
$params = [];
foreach ($fields as $key => $label) {
switch (true) {
// TEXTES
case str_starts_with($key, 't_'):
$params[$key] = match ($key) {
't_owner' => $p['proprio']['valeur'] ?? '',
't_access' => $p['acces']['valeur'] ?? '',
't_description' => $p['remarque']['valeur'] ?? '',
default => '',
};
break;
// BOOLÉENS
case str_starts_with($key, 'b_'):
$params[$key] = $this->guessBoolean($key, $p);
break;
// NUMÉRIQUES
case str_starts_with($key, 'n_'):
$params[$key] = $this->guessNumeric($key, $p);
break;
// LIENS
case str_starts_with($key, 'l_'):
$params[$key] = null; // Non géré par WRI
break;
default:
$params[$key] = "";
}
}
return $params;
}
private function guessBoolean(string $key, array $p): int
{
$etatId = $p['etat']['id'] ?? '';
return match ($key) {
'b_usable' =>
in_array($etatId, ['detruit', 'fermeture'], true) ? 2 : 0,
'b_water' => $this->isYes($p['info_comp']['eau']['valeur'] ?? ''),
'b_wood' => $this->isYes($p['info_comp']['bois']['valeur'] ?? ''),
'b_cover' => $this->isYes($p['info_comp']['couvertures']['valeur'] ?? ''),
'b_toilet' => $this->isYes($p['info_comp']['latrines']['valeur'] ?? ''),
'b_fireplace' => max(
$this->isYes($p['info_comp']['cheminee']['valeur'] ?? ''),
$this->isYes($p['info_comp']['poele']['valeur'] ?? '')
),
'b_key' =>
$etatId === 'cle_a_recuperer' ? 2 : 0,
default => -1,
};
}
private function guessNumeric(string $key, array $p): ?int
{
$raw = match ($key) {
'n_bed' => $p['places']['valeur'] ?? null,
'n_mattress' => $p['info_comp']['places_matelas']['valeur'] ?? null,
'n_bed_winter' => null, // WRI ne fournit pas
default => null,
};
if (!is_numeric($raw)) {
return null;
}
return (int)$raw;
}
private function slugify(string $text): string
{
$text = iconv('UTF-8', 'ASCII//TRANSLIT', $text);
$text = preg_replace('/[^a-zA-Z0-9]+/', '-', $text);
return strtolower(trim($text, '-'));
}
}
class WriImporter
{
private WriFetcher $fetcher;
private WriMapper $mapper;
public function __construct()
{
$this->fetcher = new WriFetcher();
$this->mapper = new WriMapper();
}
public function importAll(bool $dryRun = false): array
{
$geojson = $this->fetcher->fetchAll();
if (!isset($geojson['features']) || !is_array($geojson['features'])) {
throw new Exception("Format GeoJSON inattendu");
}
$raws = $geojson['features'];
$created = 0;
$updated = 0;
foreach ($raws as $raw) {
try {
$poi = $this->mapper->map($raw);
} catch (\Exception $e) {
continue; // Type non supporté
}
// Vérifier si un POI existe déjà
$existing = new Poi();
if ($existing->checkPermalink($poi->permalink, 1)) {
if (!$dryRun) {
$existing->lat = $poi->lat;
$existing->lon = $poi->lon;
$existing->ele = $poi->ele;
$existing->parameters = $poi->parameters;
$existing->poi_type = $poi->poi_type;
$existing->source_id = 'wri';
$existing->remote_source_id = $poi->remote_source_id;
$existing->update();
}
$updated++;
} else {
if (!$dryRun) {
$poi->insert();
}
$created++;
}
}
return [
'created' => $created,
'updated' => $updated,
'total' => count($raws),
];
}
}