From c12776ca2cd4639b3b502b4c29724ffe7dd01a83 Mon Sep 17 00:00:00 2001
From: "rastapopoulos@spip.org" <>
Date: Wed, 13 Mar 2019 17:24:49 +0000
Subject: [PATCH] =?UTF-8?q?Nouvelle=20fonctionnalit=C3=A9=20:=20on=20utili?=
 =?UTF-8?q?se=20toutes=20les=20modifs=20faites=20en=20amont=20sur=20Saisie?=
 =?UTF-8?q?s.=20On=20peut=20donc=20maintenant=20configurer=20certaines=20o?=
 =?UTF-8?q?ptions=20globales=20dans=20l'interface=20du=20constructeur.=20P?=
 =?UTF-8?q?our=20cela=20on=20d=C3=A9clare=20au=20constructeur=20quelles=20?=
 =?UTF-8?q?options=20on=20accepte=20(c'est=20propre=20au=20contexte=20de?=
 =?UTF-8?q?=20Formidable).?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Dans le lot, il y a donc la personnalisation du bouton final, et… le multi-étapes !

Pour cela, il a fallu faire un refactoring de la fonction verifier() de Formidable où tout était en dur… On l'a donc vidé de son contenu, afin que Formidable utilise enfin l'API CVT de Saisies avec la fonction saisies().

Au passsage, il y avait une vérification propre à un traitement (enregistrement) sur l'unicité : on en profite pour mettre en place un fonctionnement générique et extensible. Tout type de traitement peut désormais déclarer une fonction de vérification qui lui est propre avec traiter/montraitement_verifier(). Et si ça existe, ça sera utilisé.

Il reste un problème avec ce dernier point : pour le cas habituel ça continue de marcher pareil MAIS quand on active les étapes… Pour le moment j'ai fait le choix lorsqu'il y a étapes, de lancer ces vérifications propres aux traitements à la toute fin, quand on sait qu'on est à la dernière étape. Mais du coup ça ne va pas, car les erreurs ajoutées peuvent être sur des champs qui sont à d'autres étapes. Et CVT ne le sait pas, il reste sur la dernière étape. Donc ça montre "Vous avez X erreurs" en haut, mais on ne voit pas forcément le ou les champs précis en erreur s'ils sont ailleurs. Je ne sais pas encore comment résoudre ça…
---
 formulaires/editer_formulaire_champs.html |   2 +-
 formulaires/editer_formulaire_champs.php  |  33 ++++
 formulaires/formidable.html               |  46 +++--
 formulaires/formidable.php                | 209 +++++++++++++---------
 formulaires/inc-formidable-boutons.html   |   9 +-
 lang/formidable_fr.php                    |   5 +
 paquet.xml                                |   6 +-
 prive/squelettes/contenu/formulaire.html  |   6 +-
 traiter/enregistrement.php                |  38 ++++
 9 files changed, 248 insertions(+), 106 deletions(-)

diff --git a/formulaires/editer_formulaire_champs.html b/formulaires/editer_formulaire_champs.html
index 341af9dc..125a863c 100644
--- a/formulaires/editer_formulaire_champs.html
+++ b/formulaires/editer_formulaire_champs.html
@@ -6,7 +6,7 @@
 
 	[(#ENV*{message_erreur}|non)
 	<div class="ajax">
-		#FORMULAIRE_CONSTRUIRE_FORMULAIRE{#ENV{saisie_id}, #ENV{_saisies}}
+		#FORMULAIRE_CONSTRUIRE_FORMULAIRE{#ENV{saisie_id}, #ENV{_saisies}, #ARRAY{options_globales,#ENV{_options_globales}}}
 	</div>
 	]
 
diff --git a/formulaires/editer_formulaire_champs.php b/formulaires/editer_formulaire_champs.php
index 39e8c6a3..ab7a637a 100644
--- a/formulaires/editer_formulaire_champs.php
+++ b/formulaires/editer_formulaire_champs.php
@@ -21,6 +21,39 @@ function formulaires_editer_formulaire_champs_charger($id_formulaire) {
 		$contexte['_saisies'] = $saisies;
 		$contexte['id'] = $id_formulaire;
 		$contexte['saisie_id'] = "formidable_$id_formulaire";
+		
+		// Les options globales que l'on permet de configurer pour le contexte de Formidables
+		$contexte['_options_globales'] = array(
+			array(
+				'saisie' => 'input',
+				'options' => array(
+					'nom' => 'texte_submit',
+					'label' => _T('formidable:editer_globales_texte_submit_label'),
+				),
+			),
+			array(
+				'saisie' => 'case',
+				'options' => array(
+					'nom' => 'etapes_activer',
+					'label_case' => _T('formidable:editer_globales_etapes_activer_label_case'),
+					'explication' => _T('formidable:editer_globales_etapes_activer_explication'),
+				),
+			),
+			array(
+				'saisie' => 'input',
+				'options' => array(
+					'nom' => 'etapes_suivant',
+					'label' => _T('formidable:editer_globales_etapes_suivant_label'),
+				),
+			),
+			array(
+				'saisie' => 'input',
+				'options' => array(
+					'nom' => 'etapes_precedent',
+					'label' => _T('formidable:editer_globales_etapes_precedent_label'),
+				),
+			),
+		);
 	}
 
 	return $contexte;
diff --git a/formulaires/formidable.html b/formulaires/formidable.html
index 990572bc..9f0826e3 100644
--- a/formulaires/formidable.html
+++ b/formulaires/formidable.html
@@ -9,26 +9,38 @@
 
 	[(#ENV{editable}|oui)
 		[(#ENV{_saisies}|et{#ENV{_saisies}|count}|oui)
-	<form method='post' action='[(#ENV{action}|ancre_url{[formulaire_#ENV{form}-(#ENV{id,nouveau})]})]' enctype='multipart/form-data'><div>
-		[(#REM) declarer les hidden qui declencheront le service du formulaire
-			parametre : url d'action ]
-		#ACTION_FORMULAIRE{#ENV{action}}
-		<[(#VAL{ul}|saisie_balise_structure_formulaire)] class="editer-groupe">
-			#GENERER_SAISIES{#ENV{_saisies}}
-			<[(#VAL{li}|saisie_balise_structure_formulaire)] style="display:none;">
-				<label for="mechantrobot-#ENV{id}"><:antispam_champ_vide:></label>
-				<input type="text" id="mechantrobot-#ENV{id}" name="mechantrobot" value="#ENV{mechantrobot}" />
-			</[(#VAL{li}|saisie_balise_structure_formulaire)]>
-		</[(#VAL{ul}|saisie_balise_structure_formulaire)]>
+		<form method='post' action='[(#ENV{action}|ancre_url{[formulaire_#ENV{form}-(#ENV{id,nouveau})]})]' enctype='multipart/form-data'><div>
+			[(#REM) declarer les hidden qui declencheront le service du formulaire
+				parametre : url d'action ]
+			#ACTION_FORMULAIRE{#ENV{action}}
+			
+			[(#ENV{_etape}|oui)
+				#SET{etapes, #ENV{_saisies}|saisies_lister_par_etapes}
+				<INCLURE{fond=formulaires/inc-saisies-cvt-etapes, etapes=#GET{etapes}, env} />
+			]
+			
+			<[(#VAL{ul}|saisie_balise_structure_formulaire)] class="editer-groupe">
+				#SET{saisies, #ENV{_saisies}}
+				[(#ENV{_etape}|oui)
+					#SET{saisies, #GET{etapes}|table_valeur{#ENV{_etape}/saisies}}
+				]
+				#GENERER_SAISIES{#GET{saisies}}
+				
+				<[(#VAL{li}|saisie_balise_structure_formulaire)] style="display:none;">
+					<label for="mechantrobot-#ENV{id}"><:antispam_champ_vide:></label>
+					<input type="text" id="mechantrobot-#ENV{id}" name="mechantrobot" value="#ENV{mechantrobot}" />
+				</[(#VAL{li}|saisie_balise_structure_formulaire)]>
+			</[(#VAL{ul}|saisie_balise_structure_formulaire)]>
 
-		[(#REM) ajouter les saisies supplementaires : extra et autre, a cet endroit ]
-		<!--extra-->
+			[(#REM) ajouter les saisies supplementaires : extra et autre, a cet endroit ]
+			<!--extra-->
 
-		<INCLURE{fond=formulaires/inc-formidable-boutons,id_formulaire=#ENV{id}} />
-	</div></form>
-	]
+			<INCLURE{fond=formulaires/inc-formidable-boutons, id_formulaire=#ENV{id}, env} />
+		</div></form>
+		]
+		
 		[(#ENV{_saisies}|et{#ENV{_saisies}|count}|non)
-			<p class="notice"><:formidable:formulaires_aucun_champ:></p>
+		<p class="notice"><:formidable:formulaires_aucun_champ:></p>
 		]
 	]
 	[(#ENV{editable}|non)
diff --git a/formulaires/formidable.php b/formulaires/formidable.php
index 6e8f1b04..ae120fc9 100644
--- a/formulaires/formidable.php
+++ b/formulaires/formidable.php
@@ -45,6 +45,46 @@ function formidable_id_formulaire($id) {
 	return $id_formulaires[$id] = $id_formulaire;
 }
 
+/**
+* Déclaration des saisies du formulaire à l'API Saisies.
+*
+* @param int|string $id
+*     Identifiant numerique ou textuel du formulaire formidable
+* @param array $valeurs
+*     Valeurs par défauts passées au contexte du formulaire
+*     Exemple : array('hidden_1' => 3) pour que champ identifie "@hidden_1@" soit prerempli
+* @param int|bool $id_formulaires_reponse
+*     Identifiant d'une réponse pour forcer la reedition de cette reponse spécifique
+*
+* @return array
+*     Tableau des saisies
+**/
+function formulaires_formidable_saisies_dist($id, $valeurs = array(), $id_formulaires_reponse = false) {
+	$saisies = array();
+	
+	if (
+		$id_formulaire = formidable_id_formulaire($id)
+		and $formulaire = sql_fetsel('*', 'spip_formulaires', 'id_formulaire = ' . intval($id_formulaire))
+	) {
+		$saisies = unserialize($formulaire['saisies']);
+		
+		// Si on est en train de réafficher les valeurs postées,
+		// ne pas afficher les saisies hidden
+		if (
+			$formulaire['apres'] == 'valeurs'
+			and _request('formidable_afficher_apres') == 'valeurs'
+			and _request('formidable_traiter_ok') == true
+		) {
+			$champs_hidden = saisies_lister_avec_type($saisies, 'hidden');
+			foreach ($champs_hidden as $champ => $desc) {
+				$saisies = saisies_supprimer($saisies, $champ);
+			}
+		}
+	}
+	
+	return $saisies;
+}
+
 /**
 * Chargement du formulaire CVT de Formidable.
 *
@@ -61,7 +101,7 @@ function formidable_id_formulaire($id) {
 * @return array
 *     Contexte envoyé au squelette HTML du formulaire.
 **/
-function formulaires_formidable_charger($id, $valeurs = array(), $id_formulaires_reponse = false) {
+function formulaires_formidable_charger_dist($id, $valeurs = array(), $id_formulaires_reponse = false) {
 	$contexte = array();
 
 	// On peut donner soit un id soit un identifiant
@@ -79,25 +119,9 @@ function formulaires_formidable_charger($id, $valeurs = array(), $id_formulaires
 
 		// Est-ce que la personne a le droit de répondre ?
 		if (autoriser('repondre', 'formulaire', $formulaire['id_formulaire'], null, array('formulaire' => $formulaire))) {
-			$saisies = unserialize($formulaire['saisies']);
 			$traitements = unserialize($formulaire['traitements']);
-			// Si on est en train de réafficher les valeurs postées,
-			// ne pas afficher les saisies hidden
-			if ($formulaire['apres'] == 'valeurs'
-				and _request('formidable_afficher_apres') == 'valeurs'
-				and _request('erreurs') == false
-			) {
-				$champs_hidden = saisies_lister_avec_type($saisies, 'hidden');
-				foreach ($champs_hidden as $champ => $desc) {
-					$saisies = saisies_supprimer($saisies, $champ);
-				}
-			}
 
-			// On déclare les champs avec les valeurs par défaut
-			$contexte = array_merge(saisies_lister_valeurs_defaut($saisies), $contexte);
 			$contexte['mechantrobot'] = '';
-			// On ajoute le formulaire complet
-			$contexte['_saisies'] = $saisies;
 
 			$contexte['id'] = $formulaire['id_formulaire'];
 			$contexte['_hidden'] = '<input type="hidden" name="id_formulaire" value="' . $contexte['id'] . '"/>';
@@ -185,81 +209,97 @@ function formulaires_formidable_charger($id, $valeurs = array(), $id_formulaires
 * @return array
 *     Tableau des erreurs
 **/
-function formulaires_formidable_verifier($id, $valeurs = array(), $id_formulaires_reponse = false) {
+function formulaires_formidable_verifier_dist($id, $valeurs = array(), $id_formulaires_reponse = false) {
 	$erreurs = array();
-	// On peut donner soit un id soit un identifiant
-	if (!$id_formulaire = formidable_id_formulaire($id)) {
-		$erreurs['message_erreur'] = _T('formidable:erreur_base');
-	} else {
-		// Sale bête !
-		if (_request('mechantrobot')!='') {
-			$erreurs['hahahaha'] = 'hahahaha';
-			return $erreurs;
-		}
-
-		$formulaire = sql_fetsel('*', 'spip_formulaires', 'id_formulaire = ' . intval($id_formulaire));
-		$saisies = unserialize($formulaire['saisies']);
-
-		$erreurs_par_fichier = array();
-		$erreurs = saisies_verifier($saisies, true, $erreurs_par_fichier);
+	
+	include_spip('inc/saisies');
+	$saisies = saisies_chercher_formulaire('formidable', array($id, $valeurs, $id_formulaires_reponse));
+	
+	// Si on n'est pas dans un formulaire à étape, on lance les vérifications des traitements
+	if ($saisies and !saisies_lister_par_etapes($saisies)) {
+		$erreurs = formulaires_formidable_verifier_traitements($id, $valeurs, $id_formulaires_reponse);
+	}
+	
+	// Sale bête ! Ça on le fait tout le temps
+	if (_request('mechantrobot')!='') {
+		$erreurs['hahahaha'] = 'hahahaha';
+	}
+	
+	return $erreurs;
+}
 
-		// On supprime de $_FILES les fichiers envoyés qui ne passent pas le test de vérification
+/**
+* Vérification du formulaire CVT de Formidable mais s'il y a des étapes
+*
+* @param int|string $id
+*     Identifiant numerique ou textuel du formulaire formidable
+* @param array $valeurs
+*     Valeurs par défauts passées au contexte du formulaire
+*     Exemple : array('hidden_1' => 3) pour que champ identifie "@hidden_1@" soit prerempli
+* @param int|bool $id_formulaires_reponse
+*     Identifiant d'une réponse pour forcer la reedition de cette reponse spécifique
+*
+* @return array
+*     Tableau des erreurs
+**/
+function formulaires_formidable_verifier_etape_dist($etape, $id, $valeurs = array(), $id_formulaires_reponse = false) {
+	$erreurs = array();
+	
+	include_spip('inc/saisies');
+	$saisies = saisies_chercher_formulaire('formidable', array($id, $valeurs, $id_formulaires_reponse));
+	
+	// Seulement si on est à la DERNIÈRE étape, on lance les vérifications propres aux traitements
+	if ($saisies and $etapes = saisies_lister_par_etapes($saisies) and $etape==count($etapes)) {
+		$erreurs = formulaires_formidable_verifier_traitements($id, $valeurs, $id_formulaires_reponse);
+	}
+	
+	return $erreurs;
+}
 
-		$plugins_actifs = liste_plugin_actifs();
-		if (isset($plugins_actifs['CVTUPLOAD'])) {
-			include_spip('inc/cvtupload');
-			foreach ($erreurs as $champ => $erreur) {
-				if (isset($erreurs_par_fichier[$champ])) {
-					cvtupload_nettoyer_files_selon_erreurs($champ, $erreurs_par_fichier[$champ]);
-				}
-			}
-		}
-		// Si on a pas déjà une erreur sur le champ unicite, on lance une verification
+/**
+ * Lancer des vérifications propres aux traitements
+ * 
+ * @param int|string $id
+ *     Identifiant numerique ou textuel du formulaire formidable
+ * @param array $valeurs
+ *     Valeurs par défauts passées au contexte du formulaire
+ *     Exemple : array('hidden_1' => 3) pour que champ identifie "@hidden_1@" soit prerempli
+ * @param int|bool $id_formulaires_reponse
+ *     Identifiant d'une réponse pour forcer la reedition de cette reponse spécifique
+ *
+ * @return array
+ *     Tableau des erreurs
+ */
+function formulaires_formidable_verifier_traitements($id, $valeurs = array(), $id_formulaires_reponse = false) {
+	$erreurs = array();
+	
+	if (
+		$id_formulaire = formidable_id_formulaire($id)
+		and $formulaire = sql_fetsel('*', 'spip_formulaires', 'id_formulaire = ' . intval($id_formulaire))
+	) {
 		$traitements = unserialize($formulaire['traitements']);
-		$unicite = $traitements['enregistrement']['unicite'];
-		$message_erreur_unicite = $traitements['enregistrement']['message_erreur_unicite'];
-		if ($unicite != '') {
-			if (!$erreurs[$unicite]) {
-				$options_enregistrement = isset($traitements['enregistrement']) ? $traitements['enregistrement'] : null;
-				if (!$id_formulaires_reponse) { // si pas de réponse explictement passée au formulaire, on cherche la réponse qui serait édité
-					$id_formulaires_reponse = formidable_trouver_reponse_a_editer($formulaire['id_formulaire'], $id_formulaires_reponse, $options_enregistrement);
-				}
-				if ($id_formulaires_reponse != false) {
-					$unicite_exclure_reponse_courante = ' AND R.id_formulaires_reponse != '.$id_formulaires_reponse;
-				} else {
-					$unicite_exclure_reponse_courante = '';
-				}
-				$reponses = sql_allfetsel(
-					'R.id_formulaire AS id',
-					'spip_formulaires_reponses AS R
-						LEFT JOIN spip_formulaires AS F
-						ON R.id_formulaire=F.id_formulaire
-						LEFT JOIN spip_formulaires_reponses_champs AS C
-						ON R.id_formulaires_reponse=C.id_formulaires_reponse',
-					'R.id_formulaire = ' . $id_formulaire .
-						$unicite_exclure_reponse_courante .
-						' AND C.nom='.sql_quote($unicite).'
-						AND C.valeur='.sql_quote(_request($unicite)).'
-						AND R.statut = "publie"'
+		
+		// Pour chaque traitement choisi, on cherche s'il propose une fonction de vérification propre à ses besoins
+		foreach ($traitements as $type_traitement => $options) {
+			if ($verifier_traitement = charger_fonction('verifier', "traiter/$type_traitement", true)) {
+				$erreurs_traitements = $verifier_traitement(
+					array(
+						'formulaire' => $formulaire,
+						'options' => $options,
+						'id_formulaire' => $formulaire['id_formulaire'],
+						'valeurs' => $valeurs,
+						'id_formulaires_reponse' => $id_formulaires_reponse,
+					),
+					$erreurs
 				);
-				if (is_array($reponses) && count($reponses) > 0) {
-					$erreurs[$unicite] = $message_erreur_unicite ?
-						_T($message_erreur_unicite) : _T('formidable:erreur_unicite');
-				}
+				$erreurs = array_merge($erreurs, $erreurs_traitements);
 			}
 		}
-
-		if ($erreurs and !isset($erreurs['message_erreur'])) {
-			$erreurs['message_erreur'] = _T('formidable:erreur_generique');
-		}
-		if ($erreurs) { // Pour savoir au chargement si le formulaire a deja été envoyé avec erreur'
-			set_request('erreurs', true);
-		}
 	}
+	
 	return $erreurs;
 }
 
-
 /**
  * Traitement du formulaire CVT de Formidable.
  *
@@ -283,7 +323,7 @@ function formulaires_formidable_verifier($id, $valeurs = array(), $id_formulaire
  * @return array
  *     Tableau des erreurs
  **/
-function formulaires_formidable_traiter($id, $valeurs = array(), $id_formulaires_reponse = false) {
+function formulaires_formidable_traiter_dist($id, $valeurs = array(), $id_formulaires_reponse = false) {
 	$retours = array();
 
 	// POST Mortem de securite : on log le $_POST pour ne pas le perdre si quelque chose se passe mal
@@ -483,7 +523,12 @@ function formulaires_formidable_traiter($id, $valeurs = array(), $id_formulaires
 		$envoyer_mail = charger_fonction('envoyer_mail', 'inc');
 		$envoyer_mail($GLOBALS['meta']['email_webmaster'], $erreur_sujet, $erreur_texte);
 	}
+	
+	// Pas besoin de ça dans le vrai retour final
 	unset($retours['traitements']);
+	// Drapeau pour dire que tous les traitements sont terminés, afin qu'on le sache dans le charger()
+	set_request('formidable_traiter_ok', true);
+	
 	return $retours;
 }
 
diff --git a/formulaires/inc-formidable-boutons.html b/formulaires/inc-formidable-boutons.html
index 5381699d..c637d82f 100644
--- a/formulaires/inc-formidable-boutons.html
+++ b/formulaires/inc-formidable-boutons.html
@@ -1,4 +1,11 @@
 <p class='boutons'>
     <span class='image_loading'></span>
-    <input type='submit' class='submit' value='<:bouton_valider:>' />
+    [(#SET{label_valider, #ENV{_saisies/options/texte_submit, #ENV{saisies_texte_submit, <:bouton_enregistrer:>}}})]
+		[(#ENV{_etape}|et{#ENV{_etape}|!={#ENV{_etapes}}})
+			[(#SET{label_valider, #ENV{_saisies/options/etapes_suivant, <:bouton_suivant:>}})]
+		]
+		[(#ENV{_etape}|>{1}|oui)
+			<input type="submit" class="submit" name="_retour_etape_[(#ENV{_etape}|moins{1})]" value="[(#ENV{_saisies/options/etapes_precedent, <:bouton_precedent:>})]" />
+		]
+		<input type="submit" class="submit" value="#GET{label_valider}" />
 </p>
diff --git a/lang/formidable_fr.php b/lang/formidable_fr.php
index 0a888b0f..797eb2c9 100644
--- a/lang/formidable_fr.php
+++ b/lang/formidable_fr.php
@@ -57,6 +57,11 @@ $GLOBALS[$GLOBALS['idx_lang']] = array(
 	'editer_css' => 'Classes CSS',
 	'editer_descriptif' => 'Descriptif',
 	'editer_descriptif_explication' => 'Une explication du formulaire destinée à l’espace privé.',
+	'editer_globales_etapes_activer_explication' => 'Lorsque cette option est active, chaque groupe de champs de premier niveau est transformé en étape du formulaire.',
+	'editer_globales_etapes_activer_label_case' => 'Activer la gestion multi-étapes',
+	'editer_globales_etapes_precedent_label' => 'Texte du bouton de validation vers l’étape suivante (par défaut "Suivant")',
+	'editer_globales_etapes_suivant_label' => 'Texte du bouton de validation vers l’étape suivante (par défaut "Suivant")',
+	'editer_globales_texte_submit_label' => 'Texte du bouton de validation',
 	'editer_identifiant' => 'Identifiant',
 	'editer_identifiant_explication' => 'Donnez un identifiant textuel unique qui vous permettra d’appeler plus facilement le formulaire. L’identifiant ne peut contenir que des chiffres, lettres latines non accentuées et le caractère "_".',
 	'editer_menu_auteurs' => 'Configurer les auteurs',
diff --git a/paquet.xml b/paquet.xml
index c9cd3def..24cf7cdb 100644
--- a/paquet.xml
+++ b/paquet.xml
@@ -1,7 +1,7 @@
 <paquet
 	prefix="formidable"
 	categorie="communication"
-	version="3.35.0"
+	version="3.36.0"
 	etat="stable"
 	compatibilite="[3.0.0;3.2.*]"
 	logo="images/formidable-64.png"
@@ -38,14 +38,14 @@
 
 	<pipeline nom="corbeille_table_infos" inclure="formidable_pipelines.php" />
 	<necessite nom="spip_bonux" compatibilite="[3.3.8;[" />
-	<necessite nom="saisies" compatibilite="[3.13.0;[" />
+	<necessite nom="saisies" compatibilite="[3.18.1;[" />
 	<necessite nom="verifier" compatibilite="[1.6.2;[" />
 	<necessite nom="yaml" compatibilite="[1.5.2;[" />
 	<necessite nom="facteur" compatibilite="[3.6.2;[" />
 	<necessite nom="nospam" compatibilite="[1.6.1;[" />
 
 	<utilise nom="collectionjson" compatibilite="[1.5.0;[" />
-	<utilise nom="cvtupload" compatibilite="[1.11.0;[" />
+	<utilise nom="cvtupload" compatibilite="[1.16.0;[" />
 	<utilise nom="corbeille" compatibilite="[3.1.0;[" />
 
 	<menu nom="formulaires" titre="formidable:bouton_formulaires" parent="menu_edition" icone="images/formulaire-16.png" />
diff --git a/prive/squelettes/contenu/formulaire.html b/prive/squelettes/contenu/formulaire.html
index b61b10b5..bd766485 100644
--- a/prive/squelettes/contenu/formulaire.html
+++ b/prive/squelettes/contenu/formulaire.html
@@ -29,8 +29,10 @@
 		<p><:formidable:aucun_traitement:></p>
 	<//B_traitements>
 	#BOITE_FERMER
-
-	#FORMULAIRE_FORMIDABLE{#ID_FORMULAIRE}
+	
+	<div class="ajax">
+		#FORMULAIRE_FORMIDABLE{#ID_FORMULAIRE}
+	</div>
 
 	#PIPELINE{afficher_complement_objet,#ARRAY{args,#ARRAY{type,formulaire,id,#ID_FORMULAIRE},data,'<div class="nettoyeur"></div>'}}
 
diff --git a/traiter/enregistrement.php b/traiter/enregistrement.php
index 72cb0538..b4c9b559 100644
--- a/traiter/enregistrement.php
+++ b/traiter/enregistrement.php
@@ -190,6 +190,44 @@ function traiter_enregistrement_update_dist($id_formulaire, $traitement, $saisie
 	}
 }
 
+function traiter_enregistrement_verifier_dist($args, $erreurs) {
+	$id_formulaire = $args['id_formulaire'];
+	$options = $args['options'];
+	$id_formulaires_reponse = $args['id_formulaires_reponse'];
+	
+	if (($unicite = $options['unicite']) != '' and !$erreurs[$unicite]) {
+		if (!$id_formulaires_reponse) { // si pas de réponse explictement passée au formulaire, on cherche la réponse qui serait édité
+			$id_formulaires_reponse = formidable_trouver_reponse_a_editer($id_formulaire, $id_formulaires_reponse, $options);
+		}
+		
+		if ($id_formulaires_reponse != false) {
+			$unicite_exclure_reponse_courante = ' AND R.id_formulaires_reponse != '.$id_formulaires_reponse;
+		} else {
+			$unicite_exclure_reponse_courante = '';
+		}
+		
+		$reponses = sql_allfetsel(
+			'R.id_formulaire AS id',
+			'spip_formulaires_reponses AS R
+				LEFT JOIN spip_formulaires AS F
+				ON R.id_formulaire=F.id_formulaire
+				LEFT JOIN spip_formulaires_reponses_champs AS C
+				ON R.id_formulaires_reponse=C.id_formulaires_reponse',
+			'R.id_formulaire = ' . $id_formulaire .
+				$unicite_exclure_reponse_courante .
+				' AND C.nom='.sql_quote($unicite).'
+				AND C.valeur='.sql_quote(_request($unicite)).'
+				AND R.statut = "publie"'
+		);
+		if (is_array($reponses) && count($reponses) > 0) {
+			$erreurs[$unicite] = $options['message_erreur_unicite'] ?
+				_T($options['message_erreur_unicite']) : _T('formidable:erreur_unicite');
+		}
+	}
+	
+	return $erreurs;
+}
+
 /**
  * Pour une saisie 'fichiers' particulière,
  * déplace chaque fichier envoyé dans le dossier
-- 
GitLab