5c90e7d71c1fde16e52a6aea7dd56739e06a24aa
[lhc/web/www.git] / www / plugins / saisies / inc / saisies_afficher.php
1 <?php
2
3 /**
4 * Gestion de l'affichage des saisies.
5 *
6 * @return SPIP\Saisies\Afficher
7 **/
8
9 // Sécurité
10 if (!defined('_ECRIRE_INC_VERSION')) {
11 return;
12 }
13
14 /**
15 * Indique si une saisie peut être affichée.
16 *
17 * On s'appuie sur l'éventuelle clé "editable" du $champ.
18 * Si editable vaut :
19 * - absent : le champ est éditable
20 * - 1, le champ est éditable
21 * - 0, le champ n'est pas éditable
22 * - -1, le champ est éditable s'il y a du contenu dans le champ (l'environnement)
23 * ou dans un de ses enfants (fieldsets)
24 *
25 * @param array $champ
26 * Tableau de description de la saisie
27 * @param array $env
28 * Environnement transmis à la saisie, certainement l'environnement du formulaire
29 * @param bool $utiliser_editable
30 * - false pour juste tester le cas -1
31 *
32 * @return bool
33 * Retourne un booléen indiquant l'état éditable ou pas :
34 * - true si la saisie est éditable (peut être affichée)
35 * - false sinon
36 */
37 function saisie_editable($champ, $env, $utiliser_editable = true) {
38 if ($utiliser_editable) {
39 // si le champ n'est pas éditable, on sort.
40 if (!isset($champ['editable'])) {
41 return true;
42 }
43 $editable = $champ['editable'];
44
45 if ($editable > 0) {
46 return true;
47 }
48 if ($editable == 0) {
49 return false;
50 }
51 }
52
53 // cas -1
54 // name de la saisie
55 if (isset($champ['options']['nom'])) {
56 // si on a le name dans l'environnement, on le teste
57 $nom = $champ['options']['nom'];
58 if (isset($env[$nom])) {
59 return $env[$nom] ? true : false;
60 }
61 }
62 // sinon, si on a des sous saisies
63 if (isset($champ['saisies']) and is_array($champ['saisies'])) {
64 foreach ($champ['saisies'] as $saisie) {
65 if (saisie_editable($saisie, $env, false)) {
66 return true;
67 }
68 }
69 }
70
71 // aucun des paramètres demandés n'avait de contenu
72 return false;
73 }
74
75 /**
76 * Génère une saisie à partir d'un tableau la décrivant et de l'environnement.
77 *
78 * @param array $champ
79 * Description de la saisie.
80 * Le tableau doit être de la forme suivante :
81 * array(
82 * 'saisie' => 'input',
83 * 'options' => array(
84 * 'nom' => 'le_name',
85 * 'label' => 'Un titre plus joli',
86 * 'obligatoire' => 'oui',
87 * 'explication' => 'Remplissez ce champ en utilisant votre clavier.'
88 * )
89 * )
90 * @param array $env
91 * Environnement du formulaire
92 * Permet de savoir les valeurs actuelles des contenus des saisies,
93 * les erreurs eventuelles présentes...
94 *
95 * @return string
96 * Code HTML des saisies de formulaire
97 */
98 function saisies_generer_html($champ, $env = array()) {
99 // Si le parametre n'est pas bon, on genere du vide
100 if (!is_array($champ)) {
101 return '';
102 }
103
104 // Si la saisie n'est pas editable, on sort aussi.
105 if (!saisie_editable($champ, $env)) {
106 return '';
107 }
108
109 $contexte = array();
110
111 // On sélectionne le type de saisie
112 $contexte['type_saisie'] = $champ['saisie'];
113 // Identifiant unique de saisie, si present
114 if (isset($champ['identifiant'])) {
115 $contexte['id_saisie'] = $champ['identifiant'];
116 }
117
118 // Peut-être des transformations à faire sur les options textuelles
119 $options = isset($champ['options']) ? $champ['options'] : array();
120 foreach ($options as $option => $valeur) {
121 if ($option == 'datas') {
122 // exploser une chaine datas en tableau (applique _T_ou_typo sur chaque valeur)
123 $options[$option] = saisies_chaine2tableau($valeur);
124 } else {
125 $options[$option] = _T_ou_typo($valeur, 'multi');
126 }
127 }
128
129 // compatibilité li_class > conteneur_class
130 if (!empty($options['li_class'])) {
131 $options['conteneur_class'] = $options['li_class'];
132 }
133
134 // On ajoute les options propres à la saisie
135 $contexte = array_merge($contexte, $options);
136
137 // On ajoute aussi les infos de vérification, si cela peut se faire directement en HTML5
138 if (isset($champ['verifier'])) {
139 $contexte = array_merge($contexte, array('verifier'=>$champ['verifier']));
140 }
141
142 // Si env est définie dans les options ou qu'il y a des enfants, on ajoute tout l'environnement
143 if (isset($contexte['env']) or (isset($champ['saisies']) and is_array($champ['saisies']))) {
144 unset($contexte['env']);
145
146 // on sauve l'ancien environnement
147 // car les sous-saisies ne doivent pas être affectees
148 // par les modification sur l'environnement servant à generer la saisie mère
149 $contexte['_env'] = $env;
150
151 // À partir du moment où on passe tout l'environnement,
152 // il faut enlever certains éléments qui ne doivent absolument provenir que des options
153 unset($env['inserer_debut']);
154 unset($env['inserer_fin']);
155 $saisies_disponibles = saisies_lister_disponibles();
156 if (isset($saisies_disponibles[$contexte['type_saisie']])
157 and isset($saisies_disponibles[$contexte['type_saisie']]['options'])
158 and is_array($saisies_disponibles[$contexte['type_saisie']]['options'])) {
159 $options_a_supprimer = saisies_lister_champs($saisies_disponibles[$contexte['type_saisie']]['options']);
160 foreach ($options_a_supprimer as $option_a_supprimer) {
161 unset($env[$option_a_supprimer]);
162 }
163 }
164
165 $contexte = array_merge($env, $contexte);
166 } else {
167 // Sinon on ne sélectionne que quelques éléments importants
168 // On récupère la liste des erreurs
169 $contexte['erreurs'] = $env['erreurs'];
170 // On récupère la langue de l'objet si existante
171 if (isset($env['langue'])) {
172 $contexte['langue'] = $env['langue'];
173 }
174 // On ajoute toujours le bon self
175 $contexte['self'] = self();
176 }
177
178 // Dans tous les cas on récupère de l'environnement la valeur actuelle du champ
179 // Si le nom du champ est un tableau indexé, il faut parser !
180 if (isset($contexte['nom']) and preg_match('/([\w]+)((\[[\w]+\])+)/', $contexte['nom'], $separe)
181 and isset($env[$separe[1]])) {
182 $contexte['valeur'] = $env[$separe[1]];
183 preg_match_all('/\[([\w]+)\]/', $separe[2], $index);
184 // On va chercher au fond du tableau
185 foreach ($index[1] as $cle) {
186 $contexte['valeur'] = isset($contexte['valeur'][$cle]) ? $contexte['valeur'][$cle] : null;
187 }
188 } elseif (isset($contexte['nom']) and isset($env[$contexte['nom']])) {
189 // Sinon la valeur est juste celle du nom si elle existe
190 $contexte['valeur'] = $env[$contexte['nom']];
191 } else {
192 // Sinon rien
193 $contexte['valeur'] = null;
194 }
195
196 // Si ya des enfants on les remonte dans le contexte
197 if (isset($champ['saisies']) and is_array($champ['saisies'])) {
198 $contexte['saisies'] = $champ['saisies'];
199 }
200
201 // On génère la saisie
202 return recuperer_fond(
203 'saisies/_base',
204 $contexte
205 );
206 }
207
208 /**
209 * Génère une vue d'une saisie à partir d'un tableau la décrivant.
210 *
211 * @see saisies_generer_html()
212 *
213 * @param array $saisie
214 * Tableau de description d'une saisie
215 * @param array $env
216 * L'environnement, contenant normalement la réponse à la saisie
217 * @param array $env_obligatoire
218 * ???
219 *
220 * @return string
221 * Code HTML de la vue de la saisie
222 */
223 function saisies_generer_vue($saisie, $env = array(), $env_obligatoire = array()) {
224 // Si le paramètre n'est pas bon, on génère du vide
225 if (!is_array($saisie)) {
226 return '';
227 }
228
229 $contexte = array();
230
231 // On sélectionne le type de saisie
232 $contexte['type_saisie'] = $saisie['saisie'];
233
234 // Peut-être des transformations à faire sur les options textuelles
235 $options = $saisie['options'];
236 foreach ($options as $option => $valeur) {
237 if ($option == 'datas') {
238 // exploser une chaine datas en tableau (applique _T_ou_typo sur chaque valeur)
239 $options[$option] = saisies_chaine2tableau($valeur);
240 } else {
241 $options[$option] = _T_ou_typo($valeur, 'multi');
242 }
243 }
244
245 // On ajoute les options propres à la saisie
246 $contexte = array_merge($contexte, $options);
247
248 // Si env est définie dans les options ou qu'il y a des enfants, on ajoute tout l'environnement
249 if (isset($contexte['env']) or (isset($saisie['saisies']) and is_array($saisie['saisies']))) {
250 unset($contexte['env']);
251
252 // on sauve l'ancien environnement
253 // car les sous-saisies ne doivent pas être affectees
254 // par les modification sur l'environnement servant à generer la saisie mère
255 $contexte['_env'] = $env;
256
257 // À partir du moment où on passe tout l'environnement, il faut enlever
258 // certains éléments qui ne doivent absolument provenir que des options
259 $saisies_disponibles = saisies_lister_disponibles();
260
261 if (isset($saisies_disponibles[$contexte['type_saisie']]['options'])
262 and is_array($saisies_disponibles[$contexte['type_saisie']]['options'])) {
263 $options_a_supprimer = saisies_lister_champs($saisies_disponibles[$contexte['type_saisie']]['options']);
264 foreach ($options_a_supprimer as $option_a_supprimer) {
265 unset($env[$option_a_supprimer]);
266 }
267 }
268
269 $contexte = array_merge($env, $contexte);
270 }
271
272 // Dans tous les cas on récupère de l'environnement la valeur actuelle du champ
273
274 // On regarde en priorité s'il y a un tableau listant toutes les valeurs
275 if (!empty($env['valeurs']) and is_array($env['valeurs']) and isset($env['valeurs'][$contexte['nom']])) {
276 $contexte['valeur'] = $env['valeurs'][$contexte['nom']];
277 } elseif (preg_match('/([\w]+)((\[[\w]+\])+)/', $contexte['nom'], $separe)) {
278 // Si le nom du champ est un tableau indexé, il faut parser !
279 $contexte['valeur'] = $env[$separe[1]];
280 preg_match_all('/\[([\w]+)\]/', $separe[2], $index);
281 // On va chercher au fond du tableau
282 foreach ($index[1] as $cle) {
283 $contexte['valeur'] = $contexte['valeur'][$cle];
284 }
285 } else {
286 // Sinon la valeur est juste celle du nom
287 // certains n'ont pas de nom (fieldset)
288 $contexte['valeur'] = isset($env[$contexte['nom']]) ? $env[$contexte['nom']] : '';
289 }
290
291 // Si ya des enfants on les remonte dans le contexte
292 if (isset($saisie['saisies']) and is_array($saisie['saisies'])) {
293 $contexte['saisies'] = $saisie['saisies'];
294 }
295
296 if (is_array($env_obligatoire)) {
297 $contexte = array_merge($contexte, $env_obligatoire);
298 }
299
300 // On génère la saisie
301 return recuperer_fond(
302 'saisies-vues/_base',
303 $contexte
304 );
305 }
306
307 /**
308 * Génère, à partir d'un tableau de saisie le code javascript ajouté à la fin de #GENERER_SAISIES
309 * pour produire un affichage conditionnel des saisies ayant une option afficher_si ou afficher_si_remplissage.
310 *
311 * @param array $saisies
312 * Tableau de descriptions des saisies
313 * @param string $id_form
314 * Identifiant unique pour le formulaire
315 *
316 * @return text
317 * Code javascript
318 */
319 function saisies_generer_js_afficher_si($saisies, $id_form) {
320 $i = 0;
321 $saisies = saisies_lister_par_nom($saisies, true);
322 $code = '';
323 $code .= '(function($){';
324 $code .= "$(document).ready(function(){\n\tchargement=true;\n";
325 $code .= "\tverifier_saisies_".$id_form." = function(form){\n";
326 foreach ($saisies as $saisie) {
327 // on utilise comme selecteur l'identifiant de saisie en priorite s'il est connu
328 // parce que conteneur_class = 'tableau[nom][option]' ne fonctionne evidement pas
329 // lorsque le name est un tableau
330 if (isset($saisie['options']['afficher_si']) or isset($saisie['options']['afficher_si_remplissage'])) {
331 ++$i;
332 // retrouver la classe css probable
333 switch ($saisie['saisie']) {
334 case 'fieldset':
335 $class_li = 'fieldset_'.$saisie['options']['nom'];
336 break;
337 case 'explication':
338 $class_li = 'explication_'.$saisie['options']['nom'];
339 break;
340 default:
341 // Les [] dans le nom de la saisie sont transformés en _ dans le
342 // nom de la classe, il faut faire pareil
343 $class_li = 'editer_' . rtrim(
344 preg_replace('/[][]\[?/', '_', $saisie['options']['nom']),
345 '_'
346 );
347 }
348 $afficher_si = isset($saisie['options']['afficher_si']) ? $saisie['options']['afficher_si'] : '';
349 $afficher_si_remplissage = isset($saisie['options']['afficher_si_remplissage']) ?
350 $saisie['options']['afficher_si_remplissage'] : '';
351 $condition = implode("\n", array_filter(array($afficher_si, $afficher_si_remplissage)));
352 // retrouver l'identifiant
353 $identifiant = '';
354 if (isset($saisie['identifiant']) and $saisie['identifiant']) {
355 $identifiant = $saisie['identifiant'];
356 }
357 // On gère le cas @plugin:non_plugin@
358 preg_match_all('#@plugin:(.+)@#U', $condition, $matches);
359 foreach ($matches[1] as $plug) {
360 if (defined('_DIR_PLUGIN_'.strtoupper($plug))) {
361 $condition = preg_replace('#@plugin:'.$plug.'@#U', 'true', $condition);
362 } else {
363 $condition = preg_replace('#@plugin:'.$plug.'@#U', 'false', $condition);
364 }
365 }
366 // On gère le cas @config:plugin:meta@ suivi d'un test
367 preg_match_all('#@config:(.+):(.+)@#U', $condition, $matches);
368 foreach ($matches[1] as $plugin) {
369 $config = lire_config($plugin);
370 $condition = preg_replace('#@config:'.$plugin.':'.$matches[2][0].'@#U', '"'.$config[$matches[2][0]].'"', $condition);
371 }
372 // On transforme en une condition valide
373 preg_match_all('#@(.+)@#U', $condition, $matches);
374 foreach ($matches[1] as $nom) {
375 switch ($saisies[$nom]['saisie']) {
376 case 'radio':
377 case 'oui_non':
378 case 'true_false':
379 $condition = preg_replace('#@'.preg_quote($nom).'@#U', '$(form).find("[name=\''.$nom.'\']:checked").val()', $condition);
380 break;
381 case 'case':
382 $condition = preg_replace('#@'.preg_quote($nom).'@#U', '($(form).find(".checkbox[name=\''.$nom.'\']").is(":checked") ? $(form).find(".checkbox[name=\''.$nom.'\']").val() : "")', $condition);
383 break;
384 case 'checkbox':
385 /**
386 * Faire fonctionner @checkbox_xx@ == 'valeur'
387 */
388 preg_match_all('#@(.+)@\s*==\s*[\'"](.*?)[\'"]$#U', $condition, $matches2);
389 foreach ($matches2[2] as $value) {
390 $condition = preg_replace('#@'.preg_quote($nom).'@#U', '($(form).find(".checkbox[name=\''.$nom.'[]\'][value='.$value.']").is(":checked") ? $(form).find(".checkbox[name=\''.$nom.'[]\'][value='.$value.']").val() : "")', $condition);
391 }
392 /**
393 * Faire fonctionner @checkbox_xx@ IN 'valeur' ou @checkbox_xx@ !IN 'valeur'
394 */
395 preg_match_all('#@(.+)@\s*(!IN|IN)\s*[\'"](.*?)[\'"]$#U', $condition, $matches3);
396 foreach ($matches3[3] as $key => $value) {
397 $not = '';
398 if ($matches3[2][$key] == '!IN') {
399 $not = '!';
400 }
401 $values = explode(',', $value);
402 $new_condition = $not.'(';
403 foreach ($values as $key2 => $cond) {
404 if ($key2 > 0) {
405 $new_condition .= ' || ';
406 }
407 $new_condition .= '($(form).find(".checkbox[name=\''.$nom.'[]\'][value='.$cond.']").is(":checked") ? $(form).find(".checkbox[name=\''.$nom.'[]\'][value='.$cond.']").val() : "") == "'.$cond.'"';
408 }
409 $new_condition .= ')';
410 $condition = str_replace($matches3[0][$key], $new_condition, $condition);
411 }
412 break;
413 default:
414 $condition = preg_replace('#@'.preg_quote($nom).'@#U', '$(form).find("[name=\''.$nom.'\']").val()', $condition);
415 }
416 }
417 if ($identifiant) {
418 $sel = "[data-id='$identifiant']";
419 } else {
420 $sel = ".$class_li";
421 }
422 $code .= "\tif (".$condition.") {\n"
423 . "\t\t$(form).find(\"$sel\").show(400);\n";
424 if (html5_permis()) {
425 $pour_html_5 = "$sel.obligatoire > input, "// si le afficher_si porte directement sur le input
426 ."$sel .obligatoire > input, "// si le afficher_si porte sur le fieldset
427 ."$sel.obligatoire > textarea, "// si le afficher_si porte directement sur le textearea
428 ."$sel .obligatoire > textarea, "// si le afficher_si porte sur le fiedset
429 ."$sel.obligatoire > select, "//si le afficher_si porte directement sur le select
430 ."$sel .obligatoire > select";//si le afficher_si porte sur le fieldset
431 $code .= "\t\t$(form).find("
432 .'"'."$pour_html_5\")".
433 ".attr(\"required\",true);\n";
434 }
435 $code .= "\t}\n";
436 $code .= "\telse {\n";
437 if (html5_permis()) {
438 $code .= "\t\t$(form).find(\n\t\t\t"
439 .'"'."$pour_html_5\")\n"
440 ."\t\t.attr(".'"required"'.",false);\n";
441 }
442 $code .= "\t\tif (chargement==true) {\n"
443 ."\t\t\t$(form).find(\"$sel\").hide(400).css".'("display","none")'.";\n"
444 ."\t\t}\n"
445 ."\t\telse {\n"
446 ."\t\t\t$(form).find(\"$sel\").hide(400);\n"
447 ."\t\t};\n"
448 ."\t}\n";
449 }
450 }
451 $code .= "$(form).trigger('saisies_afficher_si_js_ok');\n";
452 $code .= "};\n";
453 $code .= "\t".'$("#afficher_si_'.$id_form.'").parents("form").each(function(){'."\n\t\t".'verifier_saisies_'.$id_form.'(this);});'."\n";
454 $code .= "\t".'$("#afficher_si_'.$id_form.'").parents("form").change(function(){'."\n\t\t".'verifier_saisies_'.$id_form.'(this);});'."\n";
455 $code .= "\tchargement=false;})\n";
456 $code .= '})(jQuery);'."\n";
457
458 if (!defined('_SAISIES_AFFICHER_SI_JS_LISIBLE')) {
459 define('_SAISIES_AFFICHER_SI_JS_LISIBLE', false);
460 }
461 if (!_SAISIES_AFFICHER_SI_JS_LISIBLE) {
462 // il suffit de régler cette constante à TRUE pour afficher le js de manière plus lisible (et moins sibyllin)
463 $code = str_replace("\n", '', $code); //concatener
464 $code = str_replace("\t", '', $code); //concatener
465 }
466 return $i > 0 ? $code : '';
467 }
468
469 /**
470 * Lorsque l'on affiche les saisies (#VOIR_SAISIES), les saisies ayant une option afficher_si
471 * et dont les conditions ne sont pas remplies doivent être retirées du tableau de saisies.
472 *
473 * Cette fonction sert aussi lors de la vérification des saisies avec saisies_verifier().
474 * À ce moment là, les saisies non affichées sont retirées de _request
475 * (on passe leur valeur à NULL).
476 *
477 * @param array $saisies
478 * Tableau de descriptions de saisies
479 * @param array|null $env
480 * Tableau d'environnement transmis dans inclure/voi_saisies.html,
481 * NULL si on doit rechercher dans _request (pour saisies_verifier()).
482 *
483 * @return array
484 * Tableau de descriptions de saisies
485 */
486 function saisies_verifier_afficher_si($saisies, $env = null) {
487 // eviter une erreur par maladresse d'appel :)
488 if (!is_array($saisies)) {
489 return array();
490 }
491 foreach ($saisies as $cle => $saisie) {
492 if (isset($saisie['options']['afficher_si']) or isset($saisie['options']['afficher_si_remplissage'])) {
493 $condition = '';
494 if (isset($saisie['options']['afficher_si_remplissage'])) {
495 $condition .= $saisie['options']['afficher_si_remplissage'];
496 }
497 if (isset($saisie['options']['afficher_si'])) {
498 $condition .= $saisie['options']['afficher_si'];
499 }
500 // On gère le cas @plugin:non_plugin@
501 preg_match_all('#@plugin:(.+)@#U', $condition, $matches);
502 foreach ($matches[1] as $plug) {
503 if (defined('_DIR_PLUGIN_'.strtoupper($plug))) {
504 $condition = preg_replace('#@plugin:'.$plug.'@#U', 'true', $condition);
505 } else {
506 $condition = preg_replace('#@plugin:'.$plug.'@#U', 'false', $condition);
507 }
508 }
509 // On gère le cas @config:plugin:meta@ suivi d'un test
510 preg_match_all('#@config:(.+):(.+)@#U', $condition, $matches);
511 foreach ($matches[1] as $plugin) {
512 $config = lire_config($plugin);
513 $condition = preg_replace('#@config:'.$plugin.':'.$matches[2][0].'@#U', '"'.$config[$matches[2][0]].'"', $condition);
514 }
515 // On transforme en une condition PHP valide
516 $condition_originale = $condition;
517 if (is_null($env)) {
518 $condition = preg_replace('#@(.+)@#U', '_request(\'$1\')', $condition);
519 } else {
520 $condition = preg_replace('#@(.+)@#U', '$env["valeurs"][\'$1\']', $condition);
521 }
522
523 /**
524 * Tester si la condition utilise des champs qui sont des tableaux
525 * Si le _request renvoie un tableau, changer == et != par in_array et !in_array
526 * TODO: c'est vraiment pas terrible comme fonctionnement
527 */
528 preg_match_all("/(_request\('.*?'\))\s*(!=|==|IN|!IN)\s*['\"](.*?)['\"]/", $condition, $matches);
529 foreach ($matches[1] as $key => $val) {
530 eval('$requete = '.$val.';');
531 if (is_array($requete)) {
532 $not = '>';
533 if (in_array($matches[2][$key], array('!=', '!IN'))) {
534 $not = '==';
535 }
536 $array = var_export(explode(',', $matches[3][$key]), true);
537 $condition = str_replace($matches[0][$key], "(count(array_intersect($val, $array)) $not 0)", $condition);
538 }
539 }
540 // On vérifie que l'on a pas @toto@="valeur" qui fait planter l'eval(),
541 // on annule cette condition dans ce cas pour éviter une erreur du type :
542 // PHP Fatal error: Can't use function return value in write context
543 $type_condition = preg_replace('#@(.+)@#U', '', $condition_originale);
544 if (trim($type_condition) != '=') {
545 eval('$ok = '.$condition.';');
546 }
547 if (!$ok) {
548 unset($saisies[$cle]);
549 if (is_null($env)) {
550 set_request($saisie['options']['nom'], null);
551 }
552 }
553 }
554 if (isset($saisies[$cle]['saisies'])) {
555 // S'il s'agit d'un fieldset ou equivalent, verifier les sous-saisies
556 $saisies[$cle]['saisies'] = saisies_verifier_afficher_si($saisies[$cle]['saisies'], $env);
557 }
558 }
559
560 return $saisies;
561 }