ad7a3e8dbe0815bcbbaea8a3ee8c82aa8e9a18e9
[lhc/web/www.git] / www / plugins / saisies / inc / saisies.php
1 <?php
2
3 /**
4 * Gestion de l'affichage des saisies
5 *
6 * @package SPIP\Saisies\Saisies
7 **/
8
9 // Sécurité
10 if (!defined('_ECRIRE_INC_VERSION')) {
11 return;
12 }
13
14 /*
15 * Une librairie pour manipuler ou obtenir des infos sur un tableau de saisies
16 *
17 * saisies_lister_par_nom()
18 * saisies_lister_champs()
19 * saisies_lister_valeurs_defaut()
20 * saisies_charger_champs()
21 * saisies_chercher()
22 * saisies_supprimer()
23 * saisies_inserer()
24 * saisies_deplacer()
25 * saisies_modifier()
26 * saisies_verifier()
27 * saisies_comparer()
28 * saisies_generer_html()
29 * saisies_generer_vue()
30 * saisies_generer_nom()
31 * saisies_inserer_html()
32 * saisies_lister_disponibles()
33 * saisies_autonomes()
34 */
35
36 // Différentes méthodes pour trouver les saisies
37 include_spip('inc/saisies_lister');
38
39 // Différentes méthodes pour manipuler une liste de saisies
40 include_spip('inc/saisies_manipuler');
41
42 // Les outils pour afficher les saisies et leur vue
43 include_spip('inc/saisies_afficher');
44
45 /**
46 * Cherche la description des saisies d'un formulaire CVT dont on donne le nom
47 *
48 * @param string $form Nom du formulaire dont on cherche les saisies
49 * @param array $args Tableau d'arguments du formulaire
50 * @return array Retourne les saisies du formulaire sinon false
51 */
52 function saisies_chercher_formulaire($form, $args) {
53 if ($fonction_saisies = charger_fonction('saisies', 'formulaires/'.$form, true)
54 and $saisies = call_user_func_array($fonction_saisies, $args)
55 and is_array($saisies)
56 // On passe les saisies dans un pipeline normé comme pour CVT
57 and $saisies = pipeline(
58 'formulaire_saisies',
59 array(
60 'args' => array('form' => $form, 'args' => $args),
61 'data' => $saisies
62 )
63 )
64 // Si c'est toujours un tableau après le pipeline
65 and is_array($saisies)
66 ) {
67 return $saisies;
68 } else {
69 return false;
70 }
71 }
72
73 /**
74 * Cherche une saisie par son id, son nom ou son chemin et renvoie soit la saisie, soit son chemin
75 *
76 * @param array $saisies Un tableau décrivant les saisies
77 * @param unknown_type $id_ou_nom_ou_chemin L'identifiant ou le nom de la saisie à chercher ou le chemin sous forme d'une liste de clés
78 * @param bool $retourner_chemin Indique si on retourne non pas la saisie mais son chemin
79 * @return array Retourne soit la saisie, soit son chemin, soit null
80 */
81 function saisies_chercher($saisies, $id_ou_nom_ou_chemin, $retourner_chemin = false) {
82 if (is_array($saisies) and $id_ou_nom_ou_chemin) {
83 if (is_string($id_ou_nom_ou_chemin)) {
84 $nom = $id_ou_nom_ou_chemin;
85 // identifiant ? premier caractere @
86 $id = ($nom[0] == '@');
87
88 foreach ($saisies as $cle => $saisie) {
89 $chemin = array($cle);
90 // notre saisie est la bonne ?
91 if ($nom == ($id ? $saisie['identifiant'] : $saisie['options']['nom'])) {
92 return $retourner_chemin ? $chemin : $saisie;
93 // sinon a telle des enfants ? et si c'est le cas, cherchons dedans
94 } elseif (isset($saisie['saisies']) and is_array($saisie['saisies']) and $saisie['saisies']
95 and ($retour = saisies_chercher($saisie['saisies'], $nom, $retourner_chemin))) {
96 return $retourner_chemin ? array_merge($chemin, array('saisies'), $retour) : $retour;
97 }
98 }
99 } elseif (is_array($id_ou_nom_ou_chemin)) {
100 $chemin = $id_ou_nom_ou_chemin;
101 $saisie = $saisies;
102 // On vérifie l'existence quand même
103 foreach ($chemin as $cle) {
104 if (isset($saisie[$cle])) {
105 $saisie = $saisie[$cle];
106 } else {
107 return null;
108 }
109 }
110 // Si c'est une vraie saisie
111 if ($saisie['saisie'] and $saisie['options']['nom']) {
112 return $retourner_chemin ? $chemin : $saisie;
113 }
114 }
115 }
116
117 return null;
118 }
119
120 /**
121 * Génère un nom unique pour un champ d'un formulaire donné
122 *
123 * @param array $formulaire
124 * Le formulaire à analyser
125 * @param string $type_saisie
126 * Le type de champ dont on veut un identifiant
127 * @return string
128 * Un nom unique par rapport aux autres champs du formulaire
129 */
130 function saisies_generer_nom($formulaire, $type_saisie) {
131 $champs = saisies_lister_champs($formulaire);
132
133 // Tant que type_numero existe, on incrémente le compteur
134 $compteur = 1;
135 while (array_search($type_saisie.'_'.$compteur, $champs) !== false) {
136 $compteur++;
137 }
138
139 // On a alors un compteur unique pour ce formulaire
140 return $type_saisie.'_'.$compteur;
141 }
142
143 /**
144 * Crée un identifiant Unique
145 * pour toutes les saisies donnees qui n'en ont pas
146 *
147 * @param Array $saisies Tableau de saisies
148 * @param Bool $regenerer Régénère un nouvel identifiant pour toutes les saisies ?
149 * @return Array Tableau de saisies complété des identifiants
150 */
151 function saisies_identifier($saisies, $regenerer = false) {
152 if (!is_array($saisies)) {
153 return array();
154 }
155 foreach ($saisies as $k => $saisie) {
156 $saisies[$k] = saisie_identifier($saisie, $regenerer);
157 }
158 return $saisies;
159 }
160
161 /**
162 * Crée un identifiant Unique
163 * pour la saisie donnee si elle n'en a pas
164 * (et pour ses sous saisies éventuels)
165 *
166 * @param Array $saisie Tableau d'une saisie
167 * @param Bool $regenerer Régénère un nouvel identifiant pour la saisie ?
168 * @return Array Tableau de la saisie complété de l'identifiant
169 **/
170 function saisie_identifier($saisie, $regenerer = false) {
171 if (!isset($saisie['identifiant']) or !$saisie['identifiant']) {
172 $saisie['identifiant'] = uniqid('@');
173 } elseif ($regenerer) {
174 $saisie['identifiant'] = uniqid('@');
175 }
176 if (isset($saisie['saisies']) and is_array($saisie['saisies'])) {
177 $saisie['saisies'] = saisies_identifier($saisie['saisies'], $regenerer);
178 }
179 return $saisie;
180 }
181
182 /**
183 * Vérifier tout un formulaire tel que décrit avec les Saisies
184 *
185 * @param array $formulaire Le contenu d'un formulaire décrit dans un tableau de Saisies
186 * @param bool $saisies_masquees_nulles Si TRUE, les saisies masquées selon afficher_si ne seront pas verifiées, leur valeur étant forcée a NULL. Cette valeur NULL est transmise à traiter (via set_request).
187 * @return array Retourne un tableau d'erreurs
188 */
189 function saisies_verifier($formulaire, $saisies_masquees_nulles = true) {
190 include_spip('inc/verifier');
191 $erreurs = array();
192 $verif_fonction = charger_fonction('verifier', 'inc', true);
193
194 if ($saisies_masquees_nulles) {
195 $formulaire = saisies_verifier_afficher_si($formulaire);
196 }
197
198 $saisies = saisies_lister_par_nom($formulaire);
199 foreach ($saisies as $saisie) {
200 $obligatoire = isset($saisie['options']['obligatoire']) ? $saisie['options']['obligatoire'] : '';
201 $champ = $saisie['options']['nom'];
202 $file = ($saisie['saisie'] == 'input' and isset($saisie['options']['type']) and $saisie['options']['type'] == 'file');
203 $verifier = isset($saisie['verifier']) ? $saisie['verifier'] : false;
204
205 // Si le nom du champ est un tableau indexé, il faut parser !
206 if (preg_match('/([\w]+)((\[[\w]+\])+)/', $champ, $separe)) {
207 $valeur = _request($separe[1]);
208 preg_match_all('/\[([\w]+)\]/', $separe[2], $index);
209 // On va chercher au fond du tableau
210 foreach ($index[1] as $cle) {
211 $valeur = isset($valeur[$cle]) ? $valeur[$cle] : null;
212 }
213 } else {
214 // Sinon la valeur est juste celle du nom
215 $valeur = _request($champ);
216 }
217
218 // Pour la saisie "destinataires" il faut filtrer si jamais on a mis un premier choix vide
219 if ($saisie['saisie'] == 'destinataires') {
220 $valeur = array_filter($valeur);
221 }
222
223 // On regarde d'abord si le champ est obligatoire
224 if ($obligatoire
225 and $obligatoire != 'non'
226 and (
227 ($file and !$_FILES[$champ]['name'])
228 or (!$file and (
229 is_null($valeur)
230 or (is_string($valeur) and trim($valeur) == '')
231 or (is_array($valeur) and count($valeur) == 0)
232 ))
233 )
234 ) {
235 $erreurs[$champ] =
236 (isset($saisie['options']['erreur_obligatoire']) and $saisie['options']['erreur_obligatoire'])
237 ? $saisie['options']['erreur_obligatoire']
238 : _T('info_obligatoire');
239 }
240
241 // On continue seulement si ya pas d'erreur d'obligation et qu'il y a une demande de verif
242 if ((!isset($erreurs[$champ]) or !$erreurs[$champ]) and is_array($verifier) and $verif_fonction) {
243 $normaliser = null;
244 // Si le champ n'est pas valide par rapport au test demandé, on ajoute l'erreur
245 $options = isset($verifier['options']) ? $verifier['options'] : array();
246 if ($erreur_eventuelle = $verif_fonction($valeur, $verifier['type'], $options, $normaliser)) {
247 $erreurs[$champ] = $erreur_eventuelle;
248 // S'il n'y a pas d'erreur et que la variable de normalisation a été remplie, on l'injecte dans le POST
249 } elseif (!is_null($normaliser)) {
250 set_request($champ, $normaliser);
251 }
252 }
253 }
254
255 return $erreurs;
256 }
257
258 /**
259 * Applatie une description tabulaire
260 * @param string $tab Le tableau à aplatir
261 * @return $nouveau_tab
262 */
263 function saisies_aplatir_tableau($tab) {
264 $nouveau_tab = array();
265 foreach ($tab as $entree => $contenu) {
266 if (is_array($contenu)) {
267 foreach ($contenu as $cle => $valeur) {
268 $nouveau_tab[$cle] = $valeur;
269 }
270 } else {
271 $nouveau_tab[$entree] = $contenu;
272 }
273 }
274 return $nouveau_tab;
275 }
276
277 /**
278 * Applatie une description chaînée, en supprimant les sous-groupes.
279 * @param string $chaine La chaîne à aplatir
280 * @return $chaine
281 */
282 function saisies_aplatir_chaine($chaine) {
283 return trim(preg_replace("#(?:^|\n)(\*(?:.*)|/\*)\n#i", "\n", $chaine));
284 }
285
286 /**
287 * Transforme une chaine en tableau avec comme principe :
288 *
289 * - une ligne devient une case
290 * - si la ligne est de la forme truc|bidule alors truc est la clé et bidule la valeur
291 * - si la ligne commence par * alors on commence un sous-tableau
292 * - si la ligne est égale à /*, alors on fini le sous-tableau
293 *
294 * @param string $chaine Une chaine à transformer
295 * @param string $separateur Séparateur utilisé
296 * @return array Retourne un tableau PHP
297 */
298 function saisies_chaine2tableau($chaine, $separateur = "\n") {
299 if ($chaine and is_string($chaine)) {
300 $tableau = array();
301 $soustab = false;
302 // On découpe d'abord en lignes
303 $lignes = explode($separateur, $chaine);
304 foreach ($lignes as $i => $ligne) {
305 $ligne = trim(trim($ligne), '|');
306 // Si ce n'est pas une ligne sans rien
307 if ($ligne !== '') {
308 // si ca commence par * c'est qu'on va faire un sous tableau
309 if (strpos($ligne, '*') === 0) {
310 $soustab=true;
311 $soustab_cle = _T_ou_typo(substr($ligne, 1), 'multi');
312 if (!isset($tableau[$soustab_cle])) {
313 $tableau[$soustab_cle] = array();
314 }
315 } elseif ($ligne == '/*') {//si on finit sous tableau
316 $soustab=false;
317 } else {
318 //sinon c'est une entrée normale
319 // Si on trouve un découpage dans la ligne on fait cle|valeur
320 if (strpos($ligne, '|') !== false) {
321 list($cle,$valeur) = explode('|', $ligne, 2);
322 // permettre les traductions de valeurs au passage
323 if ($soustab == true) {
324 $tableau[$soustab_cle][$cle] = _T_ou_typo($valeur, 'multi');
325 } else {
326 $tableau[$cle] = _T_ou_typo($valeur, 'multi');
327 }
328 } else {
329 // Sinon on génère la clé
330 if ($soustab == true) {
331 $tableau[$soustab_cle][$i] = _T_ou_typo($ligne, 'multi');
332 } else {
333 $tableau[$i] = _T_ou_typo($ligne, 'multi');
334 }
335 }
336 }
337 }
338 }
339 return $tableau;
340 } elseif (is_array($chaine)) {
341 // Si c'est déjà un tableau on lui applique _T_ou_typo (qui fonctionne de manière récursive avant de le renvoyer
342 return _T_ou_typo($chaine, 'multi');
343 } else {
344 return array();
345 }
346 }
347
348 /**
349 * Transforme un tableau en chaine de caractères avec comme principe :
350 *
351 * - une case de vient une ligne de la chaine
352 * - chaque ligne est générée avec la forme cle|valeur
353 * - si une entrée du tableau est elle même un tableau, on met une ligne de la forme *clef
354 * - pour marquer que l'on quitte un sous-tableau, on met une ligne commencant par /*, sauf si on bascule dans un autre sous-tableau.
355 *
356 * @param array $tableau Tableau à transformer
357 * @return string Texte représentant les données du tableau
358 */
359 function saisies_tableau2chaine($tableau) {
360 if ($tableau and is_array($tableau)) {
361 $chaine = '';
362 $avant_est_tableau = false;
363 foreach ($tableau as $cle => $valeur) {
364 if (is_array($valeur)) {
365 $avant_est_tableau = true;
366 $ligne=trim("*$cle");
367 $chaine .= "$ligne\n";
368 $chaine .= saisies_tableau2chaine($valeur)."\n";
369 } else {
370 if ($avant_est_tableau == true) {
371 $avant_est_tableau = false;
372 $chaine.="/*\n";
373 }
374 $ligne = trim("$cle|$valeur");
375 $chaine .= "$ligne\n";
376 }
377 }
378 $chaine = trim($chaine);
379
380 return $chaine;
381 } elseif (is_string($tableau)) {
382 // Si c'est déjà une chaine on la renvoie telle quelle
383 return $tableau;
384 } else {
385 return '';
386 }
387 }
388
389 /**
390 * Transforme une valeur en tableau d'élements si ce n'est pas déjà le cas
391 *
392 * @param mixed $valeur
393 * @return array Tableau de valeurs
394 **/
395 function saisies_valeur2tableau($valeur) {
396 if (is_array($valeur)) {
397 return $valeur;
398 }
399
400 if (!strlen($valeur)) {
401 return array();
402 }
403
404 $t = saisies_chaine2tableau($valeur);
405 if (count($t) > 1) {
406 return $t;
407 }
408
409 // qu'une seule valeur, c'est qu'elle a peut etre un separateur a virgule
410 // et a donc une cle est 0 dans ce cas la d'ailleurs
411 if (isset($t[0])) {
412 $t = saisies_chaine2tableau($t[0], ',');
413 }
414
415 return $t;
416 }
417
418 /**
419 * Pour les saisies multiples (type checkbox) proposant un choix alternatif,
420 * retrouve à partir des data de choix proposés
421 * et des valeurs des choix enregistrés
422 * le texte enregistré pour le choix alternatif.
423 *
424 * @param array $data
425 * @param array $valeur
426 * @return string choix_alternatif
427 **/
428 function saisies_trouver_choix_alternatif($data, $valeur) {
429 if (!is_array($valeur)) {
430 $valeur = saisies_chaine2tableau($valeur) ;
431 }
432 if (!is_array($data)) {
433 $data = saisies_chaine2tableau($data) ;
434 }
435 $choix_theorique = array_keys($data);
436 $choix_alternatif = array_values(array_diff($valeur, $choix_theorique));
437 return $choix_alternatif[0];//on suppose que personne ne s'est amusé à proposer deux choix alternatifs
438 }
439
440 /**
441 * Génère une page d'aide listant toutes les saisies et leurs options
442 *
443 * Retourne le résultat du squelette `inclure/saisies_aide` auquel
444 * on a transmis toutes les saisies connues.
445 *
446 * @return string Code HTML
447 */
448 function saisies_generer_aide() {
449 // On a déjà la liste par saisie
450 $saisies = saisies_lister_disponibles();
451
452 // On construit une liste par options
453 $options = array();
454 foreach ($saisies as $type_saisie => $saisie) {
455 $options_saisie = saisies_lister_par_nom($saisie['options'], false);
456 foreach ($options_saisie as $nom => $option) {
457 // Si l'option n'existe pas encore
458 if (!isset($options[$nom])) {
459 $options[$nom] = _T_ou_typo($option['options']);
460 }
461 // On ajoute toujours par qui c'est utilisé
462 $options[$nom]['utilisee_par'][] = $type_saisie;
463 }
464 ksort($options_saisie);
465 $saisies[$type_saisie]['options'] = $options_saisie;
466 }
467 ksort($options);
468
469 return recuperer_fond(
470 'inclure/saisies_aide',
471 array(
472 'saisies' => $saisies,
473 'options' => $options
474 )
475 );
476 }
477
478 /**
479 * Le tableau de saisies a-t-il une option afficher_si ?
480 *
481 * @param array $saisies Un tableau de saisies
482 * @return boolean
483 */
484
485 function saisies_afficher_si($saisies) {
486 $saisies = saisies_lister_par_nom($saisies, true);
487 // Dès qu'il y a au moins une option afficher_si, on l'active
488 foreach ($saisies as $saisie) {
489 if (isset($saisie['options']['afficher_si'])) {
490 return true;
491 }
492 }
493 return false;
494 }
495
496
497 /**
498 * Le tableau de saisies a-t-il une option afficher_si_remplissage ?
499 *
500 * @param array $saisies Un tableau de saisies
501 * @return boolean
502 */
503 function saisies_afficher_si_remplissage($saisies) {
504 $saisies = saisies_lister_par_nom($saisies, true);
505 // Dès qu'il y a au moins une option afficher_si_remplissage, on l'active
506 foreach ($saisies as $saisie) {
507 if (isset($saisie['options']['afficher_si_remplissage'])) {
508 return true;
509 }
510 }
511 return false;
512 }