Compare commits
4 Commits
58e134637c
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fb471b3dfc | ||
|
|
b0ed643ee8 | ||
|
|
6c59be35a9 | ||
|
|
137aedcfb3 |
@@ -2,8 +2,8 @@
|
||||
-- PostgreSQL database dump
|
||||
--
|
||||
|
||||
-- Dumped from database version 18.1
|
||||
-- Dumped by pg_dump version 18.1
|
||||
-- Dumped from database version 18.3
|
||||
-- Dumped by pg_dump version 18.3
|
||||
|
||||
SET statement_timeout = 0;
|
||||
SET lock_timeout = 0;
|
||||
@@ -86,6 +86,29 @@ CREATE TYPE public.user_rank_enum AS ENUM (
|
||||
|
||||
ALTER TYPE public.user_rank_enum OWNER TO kabano;
|
||||
|
||||
--
|
||||
-- Name: content_comment_photos; Type: TABLE; Schema: public; Owner: kabano
|
||||
--
|
||||
|
||||
CREATE SEQUENCE public.content_comment_photos_id_seq
|
||||
AS integer
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
ALTER SEQUENCE public.content_comment_photos_id_seq OWNER TO kabano;
|
||||
|
||||
CREATE TABLE public.content_comment_photos (
|
||||
id integer NOT NULL,
|
||||
comment_id integer NOT NULL,
|
||||
url text NOT NULL,
|
||||
creation_date timestamp without time zone DEFAULT now()
|
||||
);
|
||||
|
||||
ALTER TABLE public.content_comment_photos OWNER TO kabano;
|
||||
|
||||
--
|
||||
-- Name: content_comments_sequence; Type: SEQUENCE; Schema: public; Owner: kabano
|
||||
--
|
||||
@@ -407,6 +430,19 @@ CREATE TABLE public.users (
|
||||
|
||||
ALTER TABLE public.users OWNER TO kabano;
|
||||
|
||||
--
|
||||
-- Name: content_comment_photos id; Type: DEFAULT; Schema: public; Owner: kabano
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.content_comment_photos ALTER COLUMN id SET DEFAULT nextval('public.content_comment_photos_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- Name: content_comment_photos content_comment_photos_pkey; Type: CONSTRAINT; Schema: public; Owner: kabano
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.content_comment_photos
|
||||
ADD CONSTRAINT content_comment_photos_pkey PRIMARY KEY (id);
|
||||
|
||||
--
|
||||
-- Data for Name: locales; Type: TABLE DATA; Schema: public; Owner: kabano
|
||||
@@ -625,6 +661,14 @@ CREATE INDEX users_is_archive_index ON public.users USING btree (is_archive);
|
||||
CREATE INDEX users_register_date_index ON public.users USING btree (register_date);
|
||||
|
||||
|
||||
--
|
||||
-- Name: content_comment_photos content_comment_photos_comment_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: kabano
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.content_comment_photos
|
||||
ADD CONSTRAINT content_comment_photos_comment_id_fkey FOREIGN KEY (comment_id) REFERENCES public.content_comments(id) ON DELETE CASCADE;
|
||||
|
||||
|
||||
--
|
||||
-- Name: content_comments content_comments_author_fkey; Type: FK CONSTRAINT; Schema: public; Owner: kabano
|
||||
--
|
||||
|
||||
BIN
public/medias/watermark.png
Normal file
BIN
public/medias/watermark.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.6 KiB |
@@ -99,7 +99,7 @@ section.archive > * {
|
||||
#new_comment_form #photo {
|
||||
display: none;
|
||||
}
|
||||
#new_comment_form .upload-btn {
|
||||
#new_comment_form #upload-btn {
|
||||
display: block;
|
||||
width: 100%;
|
||||
border-bottom: 2px solid blue;
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
30
public/views/d.admin.wri-import.html
Normal file
30
public/views/d.admin.wri-import.html
Normal 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>
|
||||
L’import 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>
|
||||
@@ -178,11 +178,11 @@
|
||||
</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) { ?>
|
||||
<?php if (!empty($comment->photo)) { ?>
|
||||
<?php if (!empty($comment->photo) && $comment->is_archive != 't' && $comment->is_public != 'f') { ?>
|
||||
<a href="<?=$config['rel_root_folder'].'medias/comment_photos/'.$comment->photo?>">
|
||||
<img src="<?=$config['rel_root_folder'].'medias/comment_photos/'.$comment->photo?>" title="<?= (mb_strlen($comment->comment) > 50 ? mb_substr($comment->comment, 0, 50) . '...' : $comment->comment) ?>">
|
||||
</a>
|
||||
@@ -211,7 +211,7 @@
|
||||
<div id="new_comment_form">
|
||||
<textarea id="comment" name="comment" rows="5" placeholder="Votre commentaire"></textarea>
|
||||
|
||||
<label for="photo" class="upload-btn">
|
||||
<label for="photo" id="upload-btn">
|
||||
<i class="fas fa-file-image"></i> Ajouter une photo
|
||||
</label>
|
||||
<input type="file" id="photo" name="photo" accept="image/*">
|
||||
@@ -219,6 +219,14 @@
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.getElementById('photo').addEventListener('change', function() {
|
||||
document.getElementById('upload-btn').innerHTML = this.files.length
|
||||
? "<i class='fas fa-check-square'></i> Photo sélectionnée"
|
||||
: "<i class='fas fa-file-image'></i> Ajouter une photo";
|
||||
});
|
||||
</script>
|
||||
|
||||
<?php if (isset($poi_comments) && $poi_comments->number > 0) { ?>
|
||||
<!-- Comment list -->
|
||||
<div id="comments">
|
||||
@@ -279,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'); ?>
|
||||
|
||||
@@ -229,31 +229,33 @@ if(isset($controller->splitted_url[1]) && $user->rankIsHigher("moderator")) {
|
||||
$output = Array();
|
||||
$backup_file = Array();
|
||||
|
||||
// Suppression d'une archive existante.
|
||||
if(isset($controller->splitted_url[2]) && $controller->splitted_url[2]=='delete' && isset($controller->splitted_url[3])) {
|
||||
$tmp_folder = realpath($config['public_folder'].'tmp');
|
||||
if ($tmp_folder !== false) {
|
||||
$safe_name = basename($controller->splitted_url[3]);
|
||||
$tmp_folder_root = rtrim($tmp_folder, DIRECTORY_SEPARATOR);
|
||||
$delete_path = $tmp_folder_root . DIRECTORY_SEPARATOR . $safe_name;
|
||||
$real_delete_path = realpath($delete_path);
|
||||
if ($real_delete_path && str_starts_with($real_delete_path, $tmp_folder_root . DIRECTORY_SEPARATOR)) {
|
||||
if (file_exists($real_delete_path)) {
|
||||
unlink($real_delete_path);
|
||||
// Suppression d'une archive existante.
|
||||
if(isset($controller->splitted_url[2]) && $controller->splitted_url[2]=='delete' && isset($controller->splitted_url[3])) {
|
||||
$tmp_folder = realpath($config['public_folder'].'tmp');
|
||||
if ($tmp_folder !== false) {
|
||||
$safe_name = basename($controller->splitted_url[3]);
|
||||
$tmp_folder_root = rtrim($tmp_folder, DIRECTORY_SEPARATOR);
|
||||
$delete_path = $tmp_folder_root . DIRECTORY_SEPARATOR . $safe_name;
|
||||
$real_delete_path = realpath($delete_path);
|
||||
if ($real_delete_path && str_starts_with($real_delete_path, $tmp_folder_root . DIRECTORY_SEPARATOR)) {
|
||||
if (file_exists($real_delete_path)) {
|
||||
unlink($real_delete_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Création des archives de fichiers.
|
||||
// Nom du fichier de sauvegarde
|
||||
$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
306
src/Import/wri.php
Normal 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),
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user