From ef733821001e7e659986f79461b6d7e734b35abb Mon Sep 17 00:00:00 2001
From: Eric Lupinacci <eric@smellup.net>
Date: Sun, 4 Aug 2024 11:21:29 +0200
Subject: [PATCH] Mise au point des natures d'extra

---
 action/supprimer_territoire_extra.php         |  45 +++++
 ezmashup/territoires_data.php                 |   4 +-
 ...toires.html => creer_territoire_feed.html} |   0
 ...ritoires.php => creer_territoire_feed.php} |  10 +-
 ...es_2.html => creer_territoire_feed_2.html} |   0
 formulaires/editer_territoire_extra.html      |  97 ++++++++++
 formulaires/editer_territoire_extra.php       | 137 ++++++++++++++
 ...oires.html => editer_territoire_feed.html} |   0
 ...itoires.php => editer_territoire_feed.php} |   6 +-
 lang/territoire_extra_fr.php                  |   3 +
 lang/territoires_data_fr.php                  |  21 ++
 paquet.xml                                    |   5 +-
 ...res_extras.html => territoire_extras.html} |  10 +-
 prive/squelettes/contenu/peupler_data.html    |   2 +
 .../contenu/territoire_extra_editer.html      |  15 ++
 ..._creer.html => territoire_feed_creer.html} |   2 +-
 ..._edit.html => territoire_feed_editer.html} |   2 +-
 .../inclure/inc-peupler_data_extra.html       |  10 +-
 .../inclure/inc-peupler_data_jeu.html         |   4 +-
 ...res_extras.html => territoire_extras.html} |  20 +-
 saisies/territoire_extra_formats.html         |  27 +++
 saisies/territoire_extra_types.html           |  27 +++
 saisies/territoire_extra_unites.html          |  27 +++
 territoires_data_administrations.php          |  70 +++----
 territoires_data_autorisations.php            | 179 ++++++++++++++++++
 territoires_data_fonctions.php                |  83 ++++++--
 territoires_data_pipelines.php                |   4 +-
 27 files changed, 727 insertions(+), 83 deletions(-)
 create mode 100644 action/supprimer_territoire_extra.php
 rename formulaires/{creer_feed_territoires.html => creer_territoire_feed.html} (100%)
 rename formulaires/{creer_feed_territoires.php => creer_territoire_feed.php} (97%)
 rename formulaires/{creer_feed_territoires_2.html => creer_territoire_feed_2.html} (100%)
 create mode 100644 formulaires/editer_territoire_extra.html
 create mode 100644 formulaires/editer_territoire_extra.php
 rename formulaires/{editer_feed_territoires.html => editer_territoire_feed.html} (100%)
 rename formulaires/{editer_feed_territoires.php => editer_territoire_feed.php} (97%)
 rename prive/objets/liste/{territoires_extras.html => territoire_extras.html} (83%)
 create mode 100644 prive/squelettes/contenu/territoire_extra_editer.html
 rename prive/squelettes/contenu/{feed_territoires_creer.html => territoire_feed_creer.html} (77%)
 rename prive/squelettes/contenu/{feed_territoires_edit.html => territoire_feed_editer.html} (91%)
 rename prive/squelettes/liste/{territoires_extras.html => territoire_extras.html} (61%)
 create mode 100644 saisies/territoire_extra_formats.html
 create mode 100644 saisies/territoire_extra_types.html
 create mode 100644 saisies/territoire_extra_unites.html
 create mode 100644 territoires_data_autorisations.php

diff --git a/action/supprimer_territoire_extra.php b/action/supprimer_territoire_extra.php
new file mode 100644
index 0000000..386d58f
--- /dev/null
+++ b/action/supprimer_territoire_extra.php
@@ -0,0 +1,45 @@
+<?php
+/**
+ * Ce fichier contient l'action `supprimer_territoire_extra` lancée par un utilisateur autorisé pour
+ * supprimer un extra de territoire créé par formulaire.
+ */
+if (!defined('_ECRIRE_INC_VERSION')) {
+	return;
+}
+
+/**
+ * Cette action permet à l'utilisateur de supprimer, de façon sécurisée,
+ * un extra de territoire créé par formulaire. Cela consiste à supprimer le bloc de configuration dans la meta concernée.
+ *
+ * Cette action est réservée aux utilisateurs pouvant supprimer un extra de territoire.
+ * Elle nécessite l'id de l'extra uniquement.
+ *
+ * @param null|string $arguments Arguments de l'action ou null si l'action est appelée par une URL
+ *
+ * @return void
+ */
+function action_supprimer_territoire_extra_dist(?string $arguments = null) : void {
+	// Sécurisation.
+	// Arguments attendus :
+	// - l'identifiant de l'extra
+	if (null === $arguments) {
+		$securiser_action = charger_fonction('securiser_action', 'inc');
+		$arguments = $securiser_action();
+	}
+	$id_extra = $arguments;
+
+	// Verification des autorisations
+	if (!autoriser('supprimer', 'territoireextra', $id_extra)) {
+		include_spip('inc/minipres');
+		echo minipres();
+		exit();
+	}
+
+	// On supprime l'index de l'extra dans la configuration
+	include_spip('inc/config');
+	$config = lire_config('territoires_data', []);
+	if (isset($config['extras'][$id_extra])) {
+		unset($config['extras'][$id_extra]);
+		ecrire_config('territoires_data', $config);
+	}
+}
diff --git a/ezmashup/territoires_data.php b/ezmashup/territoires_data.php
index 8b06cfd..f28aac0 100644
--- a/ezmashup/territoires_data.php
+++ b/ezmashup/territoires_data.php
@@ -222,12 +222,12 @@ function territoires_data_feed_rediriger_admin(string $action, array $feed) : st
 
 	if ($action === 'editer') {
 		$url = parametre_url(
-			generer_url_ecrire('feed_territoires_edit'),
+			generer_url_ecrire('territoire_feed_editer'),
 			'feed_id',
 			$feed['feed_id']
 		);
 	} elseif ($action === 'creer') {
-		$url = generer_url_ecrire('feed_territoires_creer');
+		$url = generer_url_ecrire('territoire_feed_creer');
 	}
 
 	return $url;
diff --git a/formulaires/creer_feed_territoires.html b/formulaires/creer_territoire_feed.html
similarity index 100%
rename from formulaires/creer_feed_territoires.html
rename to formulaires/creer_territoire_feed.html
diff --git a/formulaires/creer_feed_territoires.php b/formulaires/creer_territoire_feed.php
similarity index 97%
rename from formulaires/creer_feed_territoires.php
rename to formulaires/creer_territoire_feed.php
index 58c5f8f..ee6a688 100644
--- a/formulaires/creer_feed_territoires.php
+++ b/formulaires/creer_territoire_feed.php
@@ -13,7 +13,7 @@ if (!defined('_ECRIRE_INC_VERSION')) {
  *
  * @return array Tableau des données à charger par le formulaire.
  */
-function formulaires_creer_feed_territoires_charger(?string $categorie_id = '') : array {
+function formulaires_creer_territoire_feed_charger(?string $categorie_id = '') : array {
 	// Etape 1
 	// Récupération des saisies éventuelles de l'étape 1
 	$valeurs = [
@@ -106,7 +106,7 @@ function formulaires_creer_feed_territoires_charger(?string $categorie_id = '')
  *
  * @return array Tableau des variables des saisies Fichiers.
  */
-function formulaires_creer_feed_territoires_fichiers(?string $categorie_id = '') {
+function formulaires_creer_territoire_feed_fichiers(?string $categorie_id = '') {
 	return ['fichier_source'];
 }
 
@@ -116,7 +116,7 @@ function formulaires_creer_feed_territoires_fichiers(?string $categorie_id = '')
  * @return array Message d'erreur si aucun pays choisi alors que la configuration du type de teritoire l'oblige.
  *               Sinon, chargement des champs utiles à l'étape 2 :
  */
-function formulaires_creer_feed_territoires_verifier_1() : array {
+function formulaires_creer_territoire_feed_verifier_1() : array {
 	// Initialisation des erreurs de vérification.
 	$erreurs = [];
 
@@ -238,7 +238,7 @@ function formulaires_creer_feed_territoires_verifier_1() : array {
  *
  * @return array Message d'erreur ou vide sinon
  */
-function formulaires_creer_feed_territoires_verifier_2(?string $categorie_id = '') : array {
+function formulaires_creer_territoire_feed_verifier_2(?string $categorie_id = '') : array {
 	// Initialisation des erreurs de vérification.
 	$erreurs = [];
 
@@ -293,7 +293,7 @@ function formulaires_creer_feed_territoires_verifier_2(?string $categorie_id = '
  * @return array Tableau retourné par le formulaire contenant toujours un message de bonne exécution ou
  *               d'erreur.
  */
-function formulaires_creer_feed_territoires_traiter(?string $categorie_id = '') : array {
+function formulaires_creer_territoire_feed_traiter(?string $categorie_id = '') : array {
 	// Initialisation du retour de la fonction
 	$retour = [];
 
diff --git a/formulaires/creer_feed_territoires_2.html b/formulaires/creer_territoire_feed_2.html
similarity index 100%
rename from formulaires/creer_feed_territoires_2.html
rename to formulaires/creer_territoire_feed_2.html
diff --git a/formulaires/editer_territoire_extra.html b/formulaires/editer_territoire_extra.html
new file mode 100644
index 0000000..67e41e1
--- /dev/null
+++ b/formulaires/editer_territoire_extra.html
@@ -0,0 +1,97 @@
+<div class='formulaire_spip formulaire_editer formulaire_#FORM formulaire_#FORM[-(#ENV{extra, new})]'>
+	[<p class="reponse_formulaire reponse_formulaire_erreur">(#ENV*{message_erreur})</p>]
+
+	[(#ENV{editable})
+	<form method="post" action="#ENV{action}"><div>
+		#ACTION_FORMULAIRE
+		<input type="hidden" name="extra" value="#ENV{extra, new}" />
+		<fieldset>
+			<legend><:territoires_data:legende_extra_identite:></legend>
+			<div class="editer-groupe">
+				[(#SET{explication, #ENV{_edition}|?{'', <:territoires_data:explication_extra_id:>}})]
+				[(#SAISIE{input, extra_id,
+					explication=#GET{explication},
+					label=<:territoires_data:label_extra_id:>,
+					disable_avec_post=#ENV{_edition},
+					obligatoire=oui
+				})]
+
+				[(#ENV{_edition}|non)
+					[(#SAISIE{territoire_extra_types, type_extra,
+						label=<:territoires_data:label_extra_type:>,
+						obligatoire=oui
+					})]
+				]
+				[(#ENV{_edition}|oui)
+					[(#SAISIE{input, type_extra,
+						label=<:territoires_data:label_extra_type:>,
+						disable_avec_post=#ENV{_edition},
+					})]
+				]
+
+				[(#SAISIE{input, label,
+					explication=<:territoires_data:explication_extra_titre:>,
+					label=<:territoires_data:label_extra_titre:>,
+					obligatoire=oui
+				})]
+			</div>
+		</fieldset>
+
+		<fieldset>
+			<legend><:territoires_data:legende_extra_format:></legend>
+			<div class="editer-groupe">
+				[(#SAISIE{territoire_extra_formats, format,
+					label=<:territoires_data:label_extra_format:>,
+					obligatoire=oui
+				})]
+
+				<div id="nb_decimales">
+				[(#SAISIE{input, extra_decimale,
+					explication=<:territoires_data:explication_extra_decimale:>,
+					label=<:territoires_data:label_extra_decimale:>,
+				})]
+				</div>
+
+				<div id="unite">
+				[(#SAISIE{territoire_extra_unites, unite,
+					label=<:territoires_data:label_extra_unite:>,
+					fond_saisie=selection
+				})]
+				</div>
+			</div>
+		</fieldset>
+
+		[(#REM) ajouter les saisies supplementaires : extra et autre, a cet endroit ]
+		<!--extra-->
+		<p class="boutons"><input type="submit" class="submit" value="<:bouton_enregistrer|attribut_html:/>" /></p>
+	</div></form>
+	]
+</div>
+<script type="text/javascript">
+//<![CDATA[
+	jQuery(document).ready(function() {
+		// Afficher ou cacher le bloc de choix du nombre de décimales.
+		function afficher_blocs_optionnels(format) {
+			if (format === 'float') {
+				jQuery("div#nb_decimales").show();
+				jQuery("div#unite").show();
+			} else if (format === 'integer') {
+				jQuery("div#nb_decimales").hide();
+				jQuery("div#unite").show();
+			} else {
+				jQuery("div#nb_decimales").hide();
+				jQuery("div#unite").hide();
+			}
+		}
+
+		// A l'initialisation
+		afficher_blocs_optionnels(jQuery("input[name='format']:checked").val());
+
+		// Sur saisie
+		jQuery("input[name='format']").change(function() {
+			afficher_blocs_optionnels(jQuery("input[name='format']:checked").val());
+			jQuery(this).blur();
+		});
+	});
+//]]>
+</script>
diff --git a/formulaires/editer_territoire_extra.php b/formulaires/editer_territoire_extra.php
new file mode 100644
index 0000000..881ebca
--- /dev/null
+++ b/formulaires/editer_territoire_extra.php
@@ -0,0 +1,137 @@
+<?php
+/**
+ * Gestion du formulaire de création et d'édition d'un extra de territoires.
+ */
+if (!defined('_ECRIRE_INC_VERSION')) {
+	return;
+}
+
+/**
+ * Chargement du formulaire de création et d'édition d'un extra de territoires.
+ *
+ * @param null|string $id_extra Identificant de la nature d'extra (édition) ou vide (création)
+ * @param null|string $redirect URL de redirection en sortie de formulaire
+ *
+ * @return array Environnement du formulaire
+ */
+function formulaires_editer_territoire_extra_charger_dist(?string $id_extra = 'new', ?string $redirect = '') : array {
+	// Initialisation du contexte du formulaire pour une création
+	$valeurs = [
+		'extra_id' => $id_extra,
+		'type_extra' => 'stat',
+		'label' => '',
+		'format' => 'integer',
+		'extra_decimale' => '',
+		'unite' => '',
+		'_edition' => '',
+		'editable' => true
+	];
+
+	// Si édition, on charge l'extra
+	if ($id_extra !== 'new') {
+		include_spip('inc/config');
+		$extra = lire_config("territoires_data/extras/{$id_extra}", []);
+		$valeurs = array_merge($valeurs, $extra);
+
+		// On détermine le nombre de décimales
+		if (strpos($extra['format'], 'float') !== false) {
+			$valeurs['format'] = 'float';
+			$valeurs['extra_decimale'] = str_replace('float', '', $extra['format']);
+		}
+
+		// On passe en mode édition
+		$valeurs['_edition'] = 'oui';
+	}
+
+	return $valeurs;
+}
+
+/**
+ * Vérifications du formulaire de création et d'édition d'un extra de territoires.
+ * En particulier, on ne peut pas créer un extra avec le même id.
+ *
+ * @param null|string $id_extra Identificant de la nature d'extra (édition) ou vide (création)
+ * @param null|string $redirect URL de redirection en sortie de formulaire
+ *
+ * @return array Tableau des erreurs
+ */
+function formulaires_editer_territoire_extra_verifier_dist(?string $id_extra = 'new', ?string $redirect = '') : array {
+	// Par défaut, aucune erreur
+	$erreurs = [];
+
+	// On ne cherche pas les champs obligatoires qui sont tous gérés par le formulaire, mais les erreurs suivantes:
+	// - id d'extra déjà existant ou toujours appelé `new`
+	// - un id mal formé
+	if ($id_extra === 'new') {
+		// inutile de vérifier un id si c'est une modification car il n'est pas modifiable
+		$id = _request('extra_id');
+		if (
+			include_spip('inc/config')
+			and (lire_config("territoires_data/extras/{$id}"))
+		) {
+			$erreurs['extra_id'] = _T('territoires_data:erreur_extra_id_existe');
+		} elseif ($id === 'new') {
+			$erreurs['extra_id'] = _T('territoires_data:erreur_extra_id_new');
+		} elseif (!preg_match('#^[\w]+$#i', $id)) {
+			$erreurs['extra_id'] = _T('territoires_data:erreur_extra_id');
+		}
+	}
+
+	// On vérifie que le nombre de décimales est compris entre 1 et 9
+	if (_request('format') === 'float') {
+		$decimale = (int) _request('extra_decimale');
+		if (
+			($decimale < 1)
+			or ($decimale > 9)
+		) {
+			$erreurs['extra_decimale'] = _T('territoires_data:erreur_extra_decimale');
+		}
+	}
+
+	return $erreurs;
+}
+
+/**
+ * Traitement du formulaire de création et d'édition d'un extra de territoires.
+ *
+ * @param null|string $id_extra Identificant de la nature d'extra (édition) ou vide (création)
+ * @param null|string $redirect URL de redirection en sortie de formulaire
+ *
+ * @return array Retours des traitements
+ */
+function formulaires_editer_territoire_extra_traiter_dist(?string $id_extra = 'new', ?string $redirect = '') : array {
+	// Initialisation du retour de la fonction
+	$retour = [];
+
+	// On récupère les saisies et on initialise la configuration de l'extra
+	$extra_id = _request('extra_id');
+	$extra = [
+		'label'     => _request('label'),
+		'is_editable' => true,
+		'type_extra'    => _request('type_extra'),
+		'format' => _request('format'),
+		'unite' => _request('unite'),
+	];
+
+	// On détermine les décimales
+	$decimales = '';
+	if ($extra['format'] === 'float') {
+		$decimales = _request('extra_decimale');
+	}
+	$extra['format'] .= $decimales;
+
+	// Ajout ou mise à jour de l'extra dans la meta de configuration
+	include_spip('inc/config');
+	ecrire_config("territoires_data/extras/{$extra_id}", $extra);
+
+	// Redirection vers la page demandée si tout s'est bien passé
+	if (
+		empty($retour['message_erreur'])
+		and $redirect
+	) {
+		$retour['redirect'] = $redirect;
+	}
+	$retour['editable'] = true;
+
+	return $retour;
+}
diff --git a/formulaires/editer_feed_territoires.html b/formulaires/editer_territoire_feed.html
similarity index 100%
rename from formulaires/editer_feed_territoires.html
rename to formulaires/editer_territoire_feed.html
diff --git a/formulaires/editer_feed_territoires.php b/formulaires/editer_territoire_feed.php
similarity index 97%
rename from formulaires/editer_feed_territoires.php
rename to formulaires/editer_territoire_feed.php
index 4e2b2fa..67a349a 100644
--- a/formulaires/editer_feed_territoires.php
+++ b/formulaires/editer_territoire_feed.php
@@ -14,7 +14,7 @@ if (!defined('_ECRIRE_INC_VERSION')) {
  *
  * @return array Environnement du formulaire
  */
-function formulaires_editer_feed_territoires_charger_dist(string $id_feed, string $redirect) {
+function formulaires_editer_territoire_feed_charger_dist(string $id_feed, string $redirect) {
 	// Récupération des informations sur le feed
 	include_spip('inc/ezmashup_feed');
 	$feed = feed_lire('territoires_data', $id_feed);
@@ -95,7 +95,7 @@ function formulaires_editer_feed_territoires_charger_dist(string $id_feed, strin
  *
  * @return array Tableau des erreurs
  */
-function formulaires_editer_feed_territoires_verifier_dist(string $id_feed, string $redirect) {
+function formulaires_editer_territoire_feed_verifier_dist(string $id_feed, string $redirect) {
 	$erreurs = [];
 
 	// Récupération des informations sur le feed
@@ -167,7 +167,7 @@ function formulaires_editer_feed_territoires_verifier_dist(string $id_feed, stri
  *
  * @return array Retours des traitements
  */
-function formulaires_editer_feed_territoires_traiter_dist(string $id_feed, string $redirect) {
+function formulaires_editer_territoire_feed_traiter_dist(string $id_feed, string $redirect) {
 	// Initialisation du retour de la fonction
 	$retour = [];
 
diff --git a/lang/territoire_extra_fr.php b/lang/territoire_extra_fr.php
index 71371cc..4627b5f 100644
--- a/lang/territoire_extra_fr.php
+++ b/lang/territoire_extra_fr.php
@@ -26,8 +26,11 @@ $GLOBALS[$GLOBALS['idx_lang']] = array(
 
 	// I
 	'info_1_extra' => '1 nature de données',
+	'info_1_unite' => '1 unité',
 	'info_extra_aucun' => 'Utilisez les boutons pour créer de nouvelles natures de données',
+	'info_unite_aucune' => 'Utilisez les boutons pour créer de nouvelles unites',
 	'info_nb_extra' => '@nb@ natures de données',
+	'info_nb_unite' => '@nb@ unités',
 
 	// T
 	'format_extra_date' => 'date',
diff --git a/lang/territoires_data_fr.php b/lang/territoires_data_fr.php
index 2229b35..a8393d3 100644
--- a/lang/territoires_data_fr.php
+++ b/lang/territoires_data_fr.php
@@ -21,6 +21,10 @@ $GLOBALS[$GLOBALS['idx_lang']] = array(
 	'erreur_feed_extra' => 'L\'identifiant saisi n\'est pas valide',
 	'erreur_feed_id' => 'L\'identifiant saisi n\'est pas valide',
 	'erreur_feed_id_existe' => 'L\'identifiant de feed saisi existe déjà : choisissez en un autre',
+	'erreur_extra_id_existe' => 'L\'identifiant de nature de données saisi existe déjà : choisissez en un autre',
+	'erreur_extra_id_new' => 'L\'identifiant de nature de données `new` n\'est pas autorisé : choisissez en un autre',
+	'erreur_extra_id' => 'L\'identifiant saisi n\'est pas valide',
+	'erreur_extra_decimale' => 'Le nombre de décimales saisi n\'est pas autorisé',
 	'erreur_recuperation_source' => 'Le fichier source n\'a pas pu être stocké dans le feed',
 	'explication_feed_id' => 'L\'identifiant est un mot sans espace composé uniquement de lettres, chiffres et du caractère "_" (tiret bas). Il est initialisé avec une valeur unique non déjà utilisée mais vous pouvez néanmoins la modifier',
 	'explication_feed_titre' => 'Le titre peut-être écrit en utilisant la balise multilangues',
@@ -35,9 +39,13 @@ $GLOBALS[$GLOBALS['idx_lang']] = array(
 	'explication_feed_mapping_valeur' => 'Cet index racine représente la partie spécifique permettant d\'accéder à la valeur statistique dans le tableau des données source. Il est exprimé sous la forme index1/index2/index3',
 	'explication_feed_mapping_code_csv' => 'Cet index racine représente le nom de la colonne correspondant au code de territoire dans le le fichier CSV. Les lettres avec accents seront remplacées par leur équivalent sans accent.',
 	'explication_feed_mapping_valeur_csv' => 'Cet index racine représente le nom de la colonne correspondant à la valeur statistique dans le fichier CSV. Les lettres avec accents seront remplacées par leur équivalent sans accent.',
+	'explication_extra_id' => 'L\'identifiant est un mot sans espace composé uniquement de lettres, chiffres et du caractère "_" (tiret bas). Il est initialisé avec la valeur `new` qu\'il convient de modifier.',
+	'explication_extra_decimale' => 'Choisissez une valeur supérieure à 1 et inférieure à 9.',
+	'explication_extra_titre' => 'Le libellé peut-être écrit en utilisant la balise multilangues ou un item de langue',
 
 	// I
 	'icone_creer_feed' => 'Créer un jeu de données',
+	'icone_creer_extra' => 'Créer une nature de données',
 	'icone_modifier_feed' => 'Modifier le jeu de données',
 	'info_0_feed' => 'Aucun jeu de données',
 	'info_1_feed' => '1 jeu de données',
@@ -71,11 +79,19 @@ $GLOBALS[$GLOBALS['idx_lang']] = array(
 	'label_feed_source_provider_name' => 'Fournisseur',
 	'label_feed_source_provider_url' => 'URL du fournisseur',
 	'label_feed_source_date' => 'Date de la dernière mise à jour',
+	'label_extra_decimale' => 'Nombre de décimales affichées',
+	'label_extra_format' => 'Format des valeurs',
+	'label_extra_id' => 'Identifiant de la nature de données',
+	'label_extra_titre' => 'Libellé',
+	'label_extra_type' => 'Type de données',
+	'label_extra_unite' => 'Unité de la valeur',
 	'legende_feed_identite' => 'Identification du jeu de données',
 	'legende_feed_unite_peuplement' => 'Territoires concernés',
 	'legende_feed_source' => 'Origine & signification des données',
 	'legende_feed_mapping' => 'Interprétation des données',
 	'legende_feed_credit_licence' => 'Licence & Crédits',
+	'legende_extra_identite' => 'Identification de la nature de données',
+	'legende_extra_format' => 'Affichage des valeurs',
 
 	// M
 	'menu_peupler' => 'Ajouter des jeux de données',
@@ -84,10 +100,15 @@ $GLOBALS[$GLOBALS['idx_lang']] = array(
 	'option_feed_type_code_iso_territoire' => 'Code primaire normalisé',
 	'option_feed_type_source_api' => 'Données accessibles via une API',
 	'option_feed_type_source_file' => 'Fichier à télécharger',
+	'option_unite_aucune' => '-- Aucune unité',
 
 	// T
 	'titre_etape_creer' => 'Etape @etape@ / @etapes@',
 	'titre_page_creer' => 'Créer un jeu de données',
+	'titre_page_creer_extra' => 'Créer une nature de données',
+	'titre_page_editer_extra' => 'Editer une nature de données',
+	'titre_page_creer_unite' => 'Créer une unité de données',
+	'titre_page_editer_unite' => 'Editer une unité de données',
 	'titre_page_peupler' => 'Gestion des jeux de données sur les territoires',
 	'titre_liste_extras_stat' => 'Données statistiques',
 	'titre_liste_extras_info' => 'Caractéristiques',
diff --git a/paquet.xml b/paquet.xml
index bc6991d..a37f0da 100644
--- a/paquet.xml
+++ b/paquet.xml
@@ -6,14 +6,15 @@
 	logo="territoires_data.svg"
 	schema="1"
 >
-	<nom>Statistiques pour les territoires</nom>
-	<!-- Gérer des données statistiques sur les territoires -->
+	<nom>Jeux de données pour territoires</nom>
+	<!-- Associer des données et statistiques aux territoires -->
 
 	<auteur lien="http://blog.smellup.net/">Eric Lupinacci</auteur>
 
 	<licence lien="http://www.gnu.org/licenses/gpl-3.0.html">GPL</licence>
 
 	<pipeline nom="declarer_tables_auxiliaires" inclure="base/territoires_data.php" />
+    <pipeline nom="autoriser" inclure="territoires_data_autorisations.php" />
     <pipeline nom="affiche_milieu" inclure="territoires_data_pipelines.php" />
     <pipeline nom="post_depeupler_territoire" inclure="territoires_data_pipelines.php" />
 
diff --git a/prive/objets/liste/territoires_extras.html b/prive/objets/liste/territoire_extras.html
similarity index 83%
rename from prive/objets/liste/territoires_extras.html
rename to prive/objets/liste/territoire_extras.html
index 9e7834c..e538d99 100644
--- a/prive/objets/liste/territoires_extras.html
+++ b/prive/objets/liste/territoire_extras.html
@@ -5,13 +5,13 @@
 	iso_pays,1,
 	points,-1
 }})
-]<B_liste_territoires_extras>
+]<B_liste_territoire_extras>
 #ANCRE_PAGINATION
 <div class="liste-objets territoire_extras">
 <table class="spip liste">
 [<caption><strong class="caption">(#ENV*{titre})</strong></caption>]
 	<tbody>
-<BOUCLE_liste_territoires_extras(TERRITOIRES_EXTRAS)
+<BOUCLE_liste_territoire_extras(TERRITOIRES_EXTRAS)
 		{iso_territoire?}{extra?}{type_extra?}{type?}{iso_pays?}{where?}
 		{tri #ENV{par, extra},#GET{defaut_tri}}
 		{pagination #ENV{nb,10}}
@@ -21,11 +21,11 @@
 			<td class="extra principale">[(#EXTRA|territoire_extra_afficher_valeur{#VALEUR})]</td>
 			<td class="unite">[(#EXTRA|territoire_extra_afficher_unite)]</td>
 		</tr>
-</BOUCLE_liste_territoires_extras>
+</BOUCLE_liste_territoire_extras>
 	</tbody>
 </table>
 [<div class="pagination">(#PAGINATION{#ENV{pagination, prive}})</div>]
 </div>
-</B_liste_territoires_extras>[
+</B_liste_territoire_extras>[
 <div class="liste-objets territoires caption-wrap"><strong class="caption">(#ENV*{sinon,''})</strong></div>
-]<//B_liste_territoires_extras>
+]<//B_liste_territoire_extras>
diff --git a/prive/squelettes/contenu/peupler_data.html b/prive/squelettes/contenu/peupler_data.html
index bcf261e..20aef4f 100644
--- a/prive/squelettes/contenu/peupler_data.html
+++ b/prive/squelettes/contenu/peupler_data.html
@@ -17,6 +17,7 @@
 			(#SELF
 				|parametre_url{composant, jeu}
 				|parametre_url{category, #CLE}
+				|parametre_url{feed_id, ''}
 				|lien_ou_expose{
 					[(#GET{libelle}|spip_ucfirst)],
 					#GET{composant}|=={jeu}|et{#GET{categorie}|=={#CLE}}})
@@ -28,6 +29,7 @@
 				(#SELF
 					|parametre_url{composant, #VALEUR}
 					|parametre_url{category, ''}
+					|parametre_url{feed_id, ''}
 					|lien_ou_expose{
 						[(#GET{libelle}|spip_ucfirst)],
 						#GET{composant}|=={#VALEUR}})
diff --git a/prive/squelettes/contenu/territoire_extra_editer.html b/prive/squelettes/contenu/territoire_extra_editer.html
new file mode 100644
index 0000000..909d178
--- /dev/null
+++ b/prive/squelettes/contenu/territoire_extra_editer.html
@@ -0,0 +1,15 @@
+#SET{extra, #ENV{extra, new}}
+#SET{redirect, #ENV{redirect, ''}}
+
+[(#GET{extra}|=={new}|oui)
+	[(#AUTORISER{creer, territoireextra}|sinon_interdire_acces)]
+][(#GET{extra}|=={new}|non)
+	[(#AUTORISER{editer, territoireextra, #GET{extra}}|sinon_interdire_acces)]
+]
+<h1 class="grostitre">
+	[(#GET{extra}|=={new}|?{<:territoires_data:titre_page_creer_extra:>, <:territoires_data:titre_page_editer_extra:>})]
+</h1>
+
+[<div class="noajax">
+	(#FORMULAIRE_EDITER_TERRITOIRE_EXTRA{#GET{extra}, #GET{redirect}})
+</div>]
diff --git a/prive/squelettes/contenu/feed_territoires_creer.html b/prive/squelettes/contenu/territoire_feed_creer.html
similarity index 77%
rename from prive/squelettes/contenu/feed_territoires_creer.html
rename to prive/squelettes/contenu/territoire_feed_creer.html
index 0c27414..6ea4f74 100644
--- a/prive/squelettes/contenu/feed_territoires_creer.html
+++ b/prive/squelettes/contenu/territoire_feed_creer.html
@@ -2,5 +2,5 @@
 <h1 class="grostitre"><:territoires_data:titre_page_creer:></h1>
 
 [<div class="noajax">
-	(#FORMULAIRE_CREER_FEED_TERRITOIRES{#ENV{categorie}})
+	(#FORMULAIRE_CREER_TERRITOIRE_FEED{#ENV{categorie}})
 </div>]
diff --git a/prive/squelettes/contenu/feed_territoires_edit.html b/prive/squelettes/contenu/territoire_feed_editer.html
similarity index 91%
rename from prive/squelettes/contenu/feed_territoires_edit.html
rename to prive/squelettes/contenu/territoire_feed_editer.html
index ffe9f84..b72d18c 100644
--- a/prive/squelettes/contenu/feed_territoires_edit.html
+++ b/prive/squelettes/contenu/territoire_feed_editer.html
@@ -16,7 +16,7 @@
 	#SET{redirect,'javascript:if (window.jQuery) jQuery(".entete-formulaire .retour a").followLink();'}
 	<div class="ajax">
 ]
-[(#FORMULAIRE_EDITER_FEED_TERRITOIRES{#ENV{feed_id}, #GET{redirect}})]
+[(#FORMULAIRE_EDITER_TERRITOIRE_FEED{#ENV{feed_id}, #GET{redirect}})]
 [(#ENV{retourajax,''}|oui)
 	</div>
 	<script type="text/javascript">/*<!\[CDATA\[*/reloadExecPage('#ENV{exec}');/*\]\]>*/</script>
diff --git a/prive/squelettes/inclure/inc-peupler_data_extra.html b/prive/squelettes/inclure/inc-peupler_data_extra.html
index f1a3cfc..3500dfe 100644
--- a/prive/squelettes/inclure/inc-peupler_data_extra.html
+++ b/prive/squelettes/inclure/inc-peupler_data_extra.html
@@ -1,2 +1,10 @@
+[(#AUTORISER{creer, territoireextra})
+	[(#URL_ECRIRE{territoire_extra_editer}
+		|parametre_url{extra, new}
+		|parametre_url{redirect, #SELF}
+		|icone_verticale{<:territoires_data:icone_creer_extra:/>,territoires_feed,new,right})]
+	<div class="clearfix"></div>
+]
+
 [(#REM) <!-- Liste des natures d'extras enregistrées --> ]
-<INCLURE{fond=prive/squelettes/liste/territoires_extras, env, ajax} />
+<INCLURE{fond=prive/squelettes/liste/territoire_extras, env, ajax} />
diff --git a/prive/squelettes/inclure/inc-peupler_data_jeu.html b/prive/squelettes/inclure/inc-peupler_data_jeu.html
index 183fa6b..b816334 100644
--- a/prive/squelettes/inclure/inc-peupler_data_jeu.html
+++ b/prive/squelettes/inclure/inc-peupler_data_jeu.html
@@ -1,5 +1,5 @@
 [(#AUTORISER{creer, feed, '', #NULL, #ARRAY{plugin, territoires_data}})
-	[(#URL_ECRIRE{feed_territoires_creer}
+	[(#URL_ECRIRE{territoire_feed_creer}
 		|parametre_url{categorie, #ENV{categorie}}
 		|icone_verticale{<:territoires_data:icone_creer_feed:/>,territoires_feed,new,right})]
 	<div class="clearfix"></div>
@@ -11,7 +11,7 @@
 </div>]
 
 [(#AUTORISER{creer, feed, '', #NULL, #ARRAY{plugin, territoires_data}})
-	[(#URL_ECRIRE{feed_territoires_creer}
+	[(#URL_ECRIRE{territoire_feed_creer}
 		|parametre_url{categorie, #ENV{categorie}}
 		|icone_verticale{<:territoires_data:icone_creer_feed:/>,territoires_feed,new,right})]
 ]
diff --git a/prive/squelettes/liste/territoires_extras.html b/prive/squelettes/liste/territoire_extras.html
similarity index 61%
rename from prive/squelettes/liste/territoires_extras.html
rename to prive/squelettes/liste/territoire_extras.html
index a870af5..e9db14e 100644
--- a/prive/squelettes/liste/territoires_extras.html
+++ b/prive/squelettes/liste/territoire_extras.html
@@ -21,23 +21,29 @@
 				<th class="id" scope="col"><:territoire_extra:champ_extra_id:></th>
 				<th class="titre principale" scope="col">[(#TRI{label, <:territoire_extra:champ_extra_titre:>, ajax})]</th>
 				<th class="type_extra" scope="col">[(#TRI{type_extra, <:territoire_extra:champ_extra_type:>, ajax})]</th>
-				<th class="type_data"><:territoire_extra:champ_extra_format:></th>
+				<th class="format"><:territoire_extra:champ_extra_format:></th>
 				<th class="unite" scope="col"><:territoire_extra:champ_extra_unite:></th>
 				<th class="action" scope="col">&nbsp;</th>
 			</tr>
 		</thead>
 		<tbody>
-	<BOUCLE_liste_extras(DATA) {source table, #CONFIG{territoires_data/extras}}{where?}{tri #ENV{par, label}, #GET{defaut_tri}}{pagination #ENV{pas, 10}}>
+<BOUCLE_liste_extras(DATA) {source table, #CONFIG{territoires_data/extras}}{where?}{tri #ENV{par, label}, #GET{defaut_tri}}{pagination #ENV{pas, 10}}>
 			<tr class="[(#COMPTEUR_BOUCLE|alterner{row_odd, row_even})]">
 				<td class="id">#CLE</td>
 				<td class="titre principale">[(#VALEUR{label}|typo)]</td>
-				<td class="type_extra">[(#VAL{territoire_extra:type_extra_}|concat{#VALEUR{type_extra}}|_T)]</td>
-				<td class="type_data">[(#VALEUR{type_data}|territoire_extra_afficher_format)]</td>
-				<td class="unite">[(#VALEUR{unite}|oui)[(#VAL{territoire_extra:unite_}|concat{#VALEUR{unite}}|_T)]]</td>
+				<td class="type_extra">[(#VALEUR{type_extra}|territoire_extra_type_traduire)]</td>
+				<td class="format">[(#VALEUR{format}|territoire_extra_format_traduire)]</td>
+				<td class="unite">[(#VALEUR{unite}|territoire_unite_traduire)]</td>
 				<td class="action">
-				</td>
+					<div class="groupe-btns">[
+					(#AUTORISER{editer, territoireextra, #CLE})
+						[<a href="(#URL_ECRIRE{territoire_extra_editer, extra=#CLE}|parametre_url{redirect, #SELF})" class="btn btn_mini btn_secondaire"><:ezmashup:bouton_editer:></a>][
+					(#AUTORISER{supprimer, territoireextra, #CLE})
+						[(#BOUTON_ACTION{<:ezmashup:bouton_supprimer:>, #URL_ACTION_AUTEUR{supprimer_territoire_extra, #CLE, #SELF}, btn_mini btn_secondaire})]
+					]</div>
+				]</td>
 			</tr>
-	</BOUCLE_liste_extras>
+</BOUCLE_liste_extras>
 		</tbody>
 	</table>
 	[<nav class="pagination">(#PAGINATION{#ENV{pagination, prive}})</nav>]
diff --git a/saisies/territoire_extra_formats.html b/saisies/territoire_extra_formats.html
new file mode 100644
index 0000000..920aec3
--- /dev/null
+++ b/saisies/territoire_extra_formats.html
@@ -0,0 +1,27 @@
+[(#REM)
+
+	### /!\ Saisie d'un format d'extra de territoires ###
+
+	On peut saisir par des radios (choix unique) ou un select (choix unique).
+	Paramètres :
+		- fond_saisie : radio (défaut) | selection
+		+ params des saisies afférentes.
+]
+
+[(#REM) Déterminer le paramètre data ]
+#SET{data, #ARRAY}
+<BOUCLE_data_format_extra(DATA) {source table, #LISTE{integer, float, date, string}}>
+	#SET{data, #GET{data}|array_merge{#ARRAY{
+		#VALEUR,
+		#VALEUR|territoire_extra_format_traduire
+	}}}
+</BOUCLE_data_format_extra>
+	[(#REM) On appelle la saisie sélection avec les data et le env qui contient les autres paramètres possibles
+			-- attention à ne pas avoir de caractère autre que , ou } après le env !!!
+	]
+	#SET{fond_saisie,	#ENV{fond_saisie, radio}}
+	<INCLURE{fond=saisies/#GET{fond_saisie},
+		data=#GET{data},
+		cacher_option_intro=oui,
+		env}>
+</B_data_format_extra>
diff --git a/saisies/territoire_extra_types.html b/saisies/territoire_extra_types.html
new file mode 100644
index 0000000..1e5ba23
--- /dev/null
+++ b/saisies/territoire_extra_types.html
@@ -0,0 +1,27 @@
+[(#REM)
+
+	### /!\ Saisie d'un type d'extra de territoires ###
+
+	On peut saisir par des radios (choix unique) ou un select (choix unique).
+	Paramètres :
+		- fond_saisie : radio (défaut) | selection
+		+ params des saisies afférentes.
+]
+
+[(#REM) Déterminer le paramètre data ]
+#SET{data, #ARRAY}
+<BOUCLE_data_type_extra(DATA) {source table, #LISTE{stat, info}}>
+	#SET{data, #GET{data}|array_merge{#ARRAY{
+		#VALEUR,
+		#VALEUR|territoire_extra_type_traduire|spip_ucfirst
+	}}}
+</BOUCLE_data_type_extra>
+	[(#REM) On appelle la saisie sélection avec les data et le env qui contient les autres paramètres possibles
+			-- attention à ne pas avoir de caractère autre que , ou } après le env !!!
+	]
+	#SET{fond_saisie,	#ENV{fond_saisie, radio}}
+	<INCLURE{fond=saisies/#GET{fond_saisie},
+		data=#GET{data},
+		cacher_option_intro=oui,
+		env}>
+</B_data_type_extra>
diff --git a/saisies/territoire_extra_unites.html b/saisies/territoire_extra_unites.html
new file mode 100644
index 0000000..5b7be4f
--- /dev/null
+++ b/saisies/territoire_extra_unites.html
@@ -0,0 +1,27 @@
+[(#REM)
+
+	### /!\ Saisie d'un type d'extra de territoires ###
+
+	On peut saisir par des radios (choix unique) ou un select (choix unique).
+	Paramètres :
+		- fond_saisie : radio (défaut) | selection
+		+ params des saisies afférentes.
+]
+
+[(#REM) Déterminer le paramètre data ]
+#SET{data, #ARRAY}
+<BOUCLE_data_type_extra(DATA) {source table, #CONFIG{territoires_data/unites}}>
+	#SET{data, #GET{data}|array_merge{#ARRAY{
+		#CLE,
+		#VALEUR{label}|typo
+	}}}
+</BOUCLE_data_type_extra>
+	[(#REM) On appelle la saisie sélection avec les data et le env qui contient les autres paramètres possibles
+			-- attention à ne pas avoir de caractère autre que , ou } après le env !!!
+	]
+	#SET{fond_saisie,	#ENV{fond_saisie, radio}}
+	<INCLURE{fond=saisies/#GET{fond_saisie},
+		data=#GET{data},
+		option_intro=<:territoires_data:option_unite_aucune:>,
+		env}>
+</B_data_type_extra>
diff --git a/territoires_data_administrations.php b/territoires_data_administrations.php
index 2b8ad84..0f2ac46 100644
--- a/territoires_data_administrations.php
+++ b/territoires_data_administrations.php
@@ -69,70 +69,70 @@ function territoires_data_configurer() : array {
 	$configuration = [
 		'unites' => [
 			'km2' => [
-				'label' => '<:territoire_extra:unite_km2:>',
+				'label'       => '<:territoire_extra:unite_km2:>',
 				'is_editable' => false
 			],
 			'hab' => [
-				'label' => '<:territoire_extra:unite_hab:>',
+				'label'       => '<:territoire_extra:unite_hab:>',
 				'is_editable' => false
 			],
 		],
 		'extras' => [
 			'area' => [
-				'label' => '<:territoire_extra:extra_area:>',
+				'label'       => '<:territoire_extra:extra_area:>',
 				'is_editable' => false,
-				'type_extra' => 'info',
-				'format' => 'integer',
-				'unite' => 'km2'
+				'type_extra'  => 'info',
+				'format'      => 'integer',
+				'unite'       => 'km2'
 			],
 			'capital' => [
-				'label' => '<:territoire_extra:extra_capital:>',
+				'label'       => '<:territoire_extra:extra_capital:>',
 				'is_editable' => false,
-				'type_extra' => 'info',
-				'format' => 'string',
-				'unite' => ''
+				'type_extra'  => 'info',
+				'format'      => 'string',
+				'unite'       => ''
 			],
 			'date_creation' => [
-				'label' => '<:territoire_extra:extra_date_creation:>',
+				'label'       => '<:territoire_extra:extra_date_creation:>',
 				'is_editable' => false,
-				'type_extra' => 'info',
-				'format' => 'date',
-				'unite' => ''
+				'type_extra'  => 'info',
+				'format'      => 'date',
+				'unite'       => ''
 			],
 			'latitude' => [
-				'label' => '<:territoire_extra:extra_latitude:>',
+				'label'       => '<:territoire_extra:extra_latitude:>',
 				'is_editable' => false,
-				'type_extra' => 'info',
-				'format' => 'float2',
-				'unite' => ''
+				'type_extra'  => 'info',
+				'format'      => 'float2',
+				'unite'       => ''
 			],
 			'longitude' => [
-				'label' => '<:territoire_extra:extra_longitude:>',
+				'label'       => '<:territoire_extra:extra_longitude:>',
 				'is_editable' => false,
-				'type_extra' => 'info',
-				'format' => 'float2',
-				'unite' => ''
+				'type_extra'  => 'info',
+				'format'      => 'float2',
+				'unite'       => ''
 			],
 			'phone_id' => [
-				'label' => '<:territoire_extra:extra_phone_id:>',
+				'label'       => '<:territoire_extra:extra_phone_id:>',
 				'is_editable' => false,
-				'type_extra' => 'info',
-				'format' => 'integer',
-				'unite' => ''
+				'type_extra'  => 'info',
+				'format'      => 'integer',
+				'unite'       => ''
 			],
 			'population' => [
-				'label' => '<:territoire_extra:extra_population:>',
+				'label'       => '<:territoire_extra:extra_population:>',
 				'is_editable' => false,
-				'type_extra' => 'stat',
-				'format' => 'integer',
-				'unite' => 'hab'
+				'type_extra'  => 'stat',
+				'format'      => 'integer',
+				'unite'       => 'hab'
 			],
 			'tld' => [
-				'label' => '<:territoire_extra:extra_tld:>',
+				'label'       => '<:territoire_extra:extra_tld:>',
 				'is_editable' => false,
-				'type_extra' => 'info',
-				'format' => 'string',
-				'unite' => ''
+				'type_extra'  => 'info',
+				'format'      => 'string',
+				'unite'       => ''
 			],
 		],
 	];
@@ -167,7 +167,7 @@ function territoires_data_adapter_configuration(array $config_statique) : void {
 		}
 
 		// On ajoute la nouvelle configuration statique que l'index existe ou pas dans la configuration actuelle
-   		$config[$_index_statique] = array_merge($config[$_index_statique] ?? [], $config_statique[$_index_statique]);
+		$config[$_index_statique] = array_merge($config[$_index_statique] ?? [], $config_statique[$_index_statique]);
 	}
 
 	// Mise à jour en meta
diff --git a/territoires_data_autorisations.php b/territoires_data_autorisations.php
new file mode 100644
index 0000000..a29f691
--- /dev/null
+++ b/territoires_data_autorisations.php
@@ -0,0 +1,179 @@
+<?php
+/**
+ * Ce fichier contient les fonctions d'autorisations du plugin.
+ */
+if (!defined('_ECRIRE_INC_VERSION')) {
+	return;
+}
+
+/**
+ * Fonction appelée par le pipeline.
+ */
+function territoires_data_autoriser() {
+}
+
+/**
+ * Autorisation de créer des extras de territoires.
+ * Il faut :
+ * - posséder l'autorisation de créer des feeds de territoires.
+ *
+ * @param string         $faire   L'action : `creer`
+ * @param string         $type    Le type d'objet ou nom de table : `territoireextra` (ce n'est pas un objet au sens SPIP)
+ * @param null|string    $id      Id de l'objet sur lequel on veut agir : '', inutilisé
+ * @param null|array|int $qui     L'initiateur de l'action:
+ *                                - si null on prend alors visiteur_session
+ *                                - un id_auteur (on regarde dans la base)
+ *                                - un tableau auteur complet, y compris [restreint]
+ * @param null|array     $options Tableau d'options sous forme de tableau associatif : inutilisé
+ *
+ * @return bool `true`si l'auteur est autorisée à exécuter l'action, `false` sinon.
+ */
+function autoriser_territoireextra_creer_dist($faire, $type, $id, $qui, $options) {
+	return
+		autoriser('creer', 'feed', null, $qui, ['plugin' => 'territoires_data']);
+}
+
+/**
+ * Autorisation d'édition pour les extras de territoires éditables.
+ * Il faut :
+ * - posséder l'autorisation de créer un extra
+ * - fournir un identifiant d'extra existant
+ * - et que l'extra soit éditable.
+ *
+ * @param string         $faire   Action demandée : `modifier` (pour éditer)
+ * @param string         $type    Type d'objet sur lequel appliquer l'action : `territoireextra`(ce n'est pas un objet au sens SPIP)
+ * @param null|string    $id      Identifiant de l'objet : celui de l'extra sur lequel appliquer l'action
+ * @param null|array|int $qui     L'initiateur de l'action:
+ *                                - si null on prend alors visiteur_session
+ *                                - un id_auteur (on regarde dans la base)
+ *                                - un tableau auteur complet, y compris [restreint]
+ * @param null|array     $options Tableau d'options sous forme de tableau associatif : inutilisé
+ *
+ * @return bool `true`si l'auteur est autorisée à exécuter l'action, `false` sinon.
+**/
+function autoriser_territoireextra_editer_dist($faire, $type, $id, $qui, $options) {
+	// Initialisation de l'autorisation à non autorisé par défaut.
+	$autorise = false;
+
+	if (
+		autoriser('creer', 'feed', '', $qui, ['plugin' => 'territoires_data'])
+		and $id
+		and is_string($id)
+		and include_spip('inc/config')
+		and ($extra = lire_config("territoires_data/extras/{$id}", []))
+		and $extra['is_editable']
+	) {
+		$autorise = true;
+	}
+
+	return $autorise;
+}
+
+/**
+ * Autorisation de suppression d'un extra de territoires éditable.
+ * Il faut :
+ * - posséder l'autorisation de modifier un extra
+ * - et que l'extra ne soit pas utilisé.
+ *
+ * @uses autoriser_territoireextra_supprimer_dist()
+ *
+ * @param string         $faire   Action demandée : `supprimer`
+ * @param string         $type    Type d'objet sur lequel appliquer l'action : `territoireextra`(ce n'est pas un objet au sens SPIP)
+ * @param null|string    $id      Identifiant de l'objet : celui de l'extra sur lequel appliquer l'action
+ * @param null|array|int $qui     L'initiateur de l'action:
+ *                                - si null on prend alors visiteur_session
+ *                                - un id_auteur (on regarde dans la base)
+ *                                - un tableau auteur complet, y compris [restreint]
+ * @param null|array     $options Tableau d'options sous forme de tableau associatif : inutilisé
+ *
+ * @return bool `true`si l'auteur est autorisée à exécuter l'action, `false` sinon.
+**/
+function autoriser_territoireextra_supprimer_dist($faire, $type, $id, $qui, $options) {
+	// Initialisation de l'autorisation à non autorisé par défaut.
+	$autorise = false;
+
+	if (autoriser('editer', 'territoireextra', $id, $qui, $options)) {
+		// On vérifier qu'il n'existe pas de feed (peuplé ou pas) utilisant cet extra.
+		// Pour ce faire, on vérifie le champ `extra` des static_fields du mapping pour les feeds du plugin uniquement
+		// -- on utilise pas l'API feed_repertorier car elle fournit les champs sans décodage, donc on y gagne rien
+		$where = [
+			'plugin=' . sql_quote('territoires_data'),
+			'target_id=' . sql_quote('territoires_extras'),
+		];
+		$extra_utilise = false;
+		if ($mappings = sql_allfetsel('mapping', 'spip_feeds', $where)) {
+			foreach ($mappings as $_mapping) {
+				$mapping = json_decode($_mapping['mapping'], true);
+				if (
+					isset($mapping['static_fields'][$id])
+					and ($mapping['static_fields'][$id] === $id)
+				) {
+					$extra_utilise = true;
+					break;
+				}
+			}
+		}
+		if (!$extra_utilise) {
+			$autorise = true;
+		}
+	}
+
+	return $autorise;
+}
+
+/**
+ * Autorisation de créer des unites pour les extras de territoires.
+ * Il faut :
+ * - posséder l'autorisation de créer des feeds de territoires.
+ *
+ * @param string         $faire   L'action : `creer`
+ * @param string         $type    Le type d'objet ou nom de table : `territoireunite` (ce n'est pas un objet au sens SPIP)
+ * @param null|string    $id      Id de l'objet sur lequel on veut agir : '', inutilisé
+ * @param null|array|int $qui     L'initiateur de l'action:
+ *                                - si null on prend alors visiteur_session
+ *                                - un id_auteur (on regarde dans la base)
+ *                                - un tableau auteur complet, y compris [restreint]
+ * @param null|array     $options Tableau d'options sous forme de tableau associatif : inutilisé
+ *
+ * @return bool `true`si l'auteur est autorisée à exécuter l'action, `false` sinon.
+ */
+function autoriser_territoireunite_creer_dist($faire, $type, $id, $qui, $options) {
+	return
+		autoriser('creer', 'feed', null, $qui, ['plugin' => 'territoires_data']);
+}
+
+/**
+ * Autorisation de modifier les unites de territoires éditables.
+ * Il faut :
+ * - posséder l'autorisation de créer un feed
+ * - fournir un identifiant d'unité existant
+ * - et que l'unité soit éditable.
+ *
+ * @param string         $faire   Action demandée : `modifier` (pour éditer)
+ * @param string         $type    Type d'objet sur lequel appliquer l'action : `territoireunite`(ce n'est pas un objet au sens SPIP)
+ * @param null|string    $id      Identifiant de l'objet : celui de l'unité sur lequel appliquer l'action
+ * @param null|array|int $qui     L'initiateur de l'action:
+ *                                - si null on prend alors visiteur_session
+ *                                - un id_auteur (on regarde dans la base)
+ *                                - un tableau auteur complet, y compris [restreint]
+ * @param null|array     $options Tableau d'options sous forme de tableau associatif : inutilisé
+ *
+ * @return bool `true`si l'auteur est autorisée à exécuter l'action, `false` sinon.
+**/
+function autoriser_territoireunite_editer_dist($faire, $type, $id, $qui, $options) {
+	// Initialisation de l'autorisation à non autorisé par défaut.
+	$autorise = false;
+
+	if (
+		autoriser('creer', 'feed', '', $qui, ['plugin' => 'territoires_data'])
+		and $id
+		and is_string($id)
+		and include_spip('inc/config')
+		and ($unite = lire_config("territoires_data/unites/{$id}", []))
+		and $unite['is_editable']
+	) {
+		$autorise = true;
+	}
+
+	return $autorise;
+}
diff --git a/territoires_data_fonctions.php b/territoires_data_fonctions.php
index cb3ccda..c62ae4e 100644
--- a/territoires_data_fonctions.php
+++ b/territoires_data_fonctions.php
@@ -18,12 +18,16 @@ if (!defined('_ECRIRE_INC_VERSION')) {
  */
 function territoire_extra_afficher_label(string $extra) {
 	// On renvoie vide si erreur
+	static $config = [];
 	$label = '';
 
-	include_spip('inc/config');
-	$config = lire_config("territoires_data/extras/{$extra}", []);
-	if (!empty($config['label'])) {
-		$label = typo($config['label']);
+	if (!$config) {
+		include_spip('inc/config');
+		$config = lire_config('territoires_data/extras', []);
+	}
+
+	if (!empty($config[$extra]['label'])) {
+		$label = typo($config[$extra]['label']);
 	}
 
 	return $label;
@@ -31,7 +35,6 @@ function territoire_extra_afficher_label(string $extra) {
 
 /**
  * Formate la valeur d'un extra en fonction de sa nature.
- * Les codes ne sont pas concernés.
  *
  * @api
  *
@@ -69,13 +72,12 @@ function territoire_extra_afficher_valeur(string $extra, $valeur) {
 
 /**
  * Détermine l'unité d'un extra en fonction de sa nature.
- * Les codes ne sont pas concernés.
  *
  * @api
  *
- * @param string $extra      La nature de l'extra.
+ * @param string $extra La nature de l'extra.
  *
- * @return string Unité de l'extra ou vide sinon aucune.
+ * @return string Unité de l'extra ou vide si aucune.
  */
 function territoire_extra_afficher_unite(string $extra) : string {
 	// On renvoie par défaut la valeur d'entrée
@@ -84,22 +86,20 @@ function territoire_extra_afficher_unite(string $extra) : string {
 
 	if (!$config) {
 		include_spip('inc/config');
-		$config = lire_config('territoires_data', []);
+		$config = lire_config('territoires_data/extras', []);
 	}
 
 	// Identifier l'unite de l'extra et en extraire le label
-	if (!empty($config['extras'][$extra]['unite'])) {
-		$unite_id = $config['extras'][$extra]['unite'];
-		if (!empty($config['unites'][$unite_id]['label'])) {
-			$unite = typo($config['unites'][$unite_id]['label']);
-		}
+	if (!empty($config[$extra]['unite'])) {
+		$unite_id = $config[$extra]['unite'];
+		$unite = territoire_unite_traduire($unite_id);
 	}
 
 	return $unite;
 }
 
 /**
- * Détermine le format de la valeur de l'extra (chaine, nombre, date).
+ * Traduit le format de la valeur de l'extra (chaine, nombre entier ou décimal, date).
  *
  * @api
  *
@@ -107,8 +107,8 @@ function territoire_extra_afficher_unite(string $extra) : string {
  *
  * @return string Libellé du format d'extra.
  */
-function territoire_extra_afficher_format(string $format) : string {
-	// On stocke les libellé de format pour le même hit
+function territoire_extra_format_traduire(string $format) : string {
+	// On stocke les libellés de format pour le même hit
 	static $formats = [];
 
 	if (!isset($formats[$format])) {
@@ -124,3 +124,52 @@ function territoire_extra_afficher_format(string $format) : string {
 
 	return $formats[$format];
 }
+
+/**
+ * Traduit le type d'extra (code, info ou stat).
+ *
+ * @api
+ *
+ * @param string $type Type de l'extra.
+ *
+ * @return string Libellé du type d'extra.
+ */
+function territoire_extra_type_traduire(string $type) : string {
+	// On stocke les libellés de type pour le même hit
+	static $types = [];
+
+	if (!isset($types[$type])) {
+		$types[$type] = $type ? _T('territoire_extra:type_extra_' . $type) : '';
+	}
+
+	return $types[$type];
+}
+
+/**
+ * Traduit l'unité à partir de son identifiant.
+ *
+ * @api
+ *
+ * @param string $unite_id Identifiant de l'unité.
+ *
+ * @return string Libellé de l'unité.
+ */
+function territoire_unite_traduire(string $unite_id) : string {
+	// On stocke les libellés d'unité pour le même hit
+	static $config = [];
+	static $unites = [];
+
+	if (!$config) {
+		include_spip('inc/config');
+		$config = lire_config('territoires_data/unites', []);
+	}
+
+	if (!isset($unites[$unite_id])) {
+		$unites[$unite_id] = '';
+		if (!empty($config[$unite_id]['label'])) {
+			$unites[$unite_id] = typo($config[$unite_id]['label']);
+		}
+	}
+
+	return $unites[$unite_id];
+}
diff --git a/territoires_data_pipelines.php b/territoires_data_pipelines.php
index 5381ee7..efa6de9 100644
--- a/territoires_data_pipelines.php
+++ b/territoires_data_pipelines.php
@@ -46,7 +46,7 @@ function territoires_data_affiche_milieu(array $flux) : array {
 				$extra_peuple = unite_peuplement_extra_est_charge($territoire['type'], $pays, 'info');
 				if ($extra_peuple) {
 					$texte .= recuperer_fond(
-						'prive/objets/liste/territoires_extras',
+						'prive/objets/liste/territoire_extras',
 						[
 							'iso_territoire' => $territoire['iso_territoire'],
 							'type_extra'     => 'info',
@@ -58,7 +58,7 @@ function territoires_data_affiche_milieu(array $flux) : array {
 				$extra_peuple = unite_peuplement_extra_est_charge($territoire['type'], $pays, 'stat');
 				if ($extra_peuple) {
 					$texte .= recuperer_fond(
-						'prive/objets/liste/territoires_extras',
+						'prive/objets/liste/territoire_extras',
 						[
 							'iso_territoire' => $territoire['iso_territoire'],
 							'type_extra'     => 'stat',
-- 
GitLab