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