source: spip-zone/_plugins_/saisies/trunk/inc/saisies_afficher_si.php @ 115022

Last change on this file since 115022 was 115022, checked in by maieul@…, 14 months ago

revert r115020, il faudrait échapper les retours lignes eux mêmes, ca va encore complexifier les choses

File size: 16.4 KB
Line 
1<?php
2
3/**
4 * Gestion de l'affichage conditionnelle des saisies
5 *
6 * @package SPIP\Saisies\Afficher_si
7**/
8
9// Sécurité
10if (!defined('_ECRIRE_INC_VERSION')) {
11        return;
12}
13
14
15/**
16 * Génère, à partir d'un tableau de saisie le code javascript ajouté à la fin de #GENERER_SAISIES
17 * pour produire un affichage conditionnel des saisies ayant une option afficher_si
18 *
19 * @param array  $saisies
20 *                        Tableau de descriptions des saisies
21 * @param string $id_form
22 *                        Identifiant unique pour le formulaire
23 *
24 * @return text
25 *              Code javascript
26 */
27function saisies_generer_js_afficher_si($saisies, $id_form) {
28        $i = 0;
29        $saisies = saisies_lister_par_nom($saisies, true);
30        $code = '';
31        $code .= "$(function(){\n\tchargement=true;\n";
32        $code .= "\tverifier_saisies_".$id_form." = function(form){\n";
33
34        if (!defined('_SAISIES_AFFICHER_SI_JS_SHOW')) {
35                define ('_SAISIES_AFFICHER_SI_JS_SHOW', 'show(400)');
36        }
37        if (!defined('_SAISIES_AFFICHER_SI_JS_HIDE')) {
38                define ('_SAISIES_AFFICHER_SI_JS_HIDE', 'hide(400)');
39        }
40        foreach ($saisies as $saisie) {
41                // on utilise comme selecteur l'identifiant de saisie en priorite s'il est connu
42                // parce que conteneur_class = 'tableau[nom][option]' ne fonctionne evidement pas
43                // lorsque le name est un tableau
44                if (isset($saisie['options']['afficher_si'])) {
45                        ++$i;
46                        // Les [] dans le nom de la saisie sont transformés en _ dans le
47                        // nom de la classe, il faut faire pareil
48                        $nom_underscore = rtrim(
49                                        preg_replace('/[][]\[?/', '_', $saisie['options']['nom']),
50                                        '_'
51                        );
52                        // retrouver la classe css probable
53                        switch ($saisie['saisie']) {
54                                case 'fieldset':
55                                        $class_li = 'fieldset_'.$nom_underscore;
56                                        break;
57                                case 'explication':
58                                        $class_li = 'explication_'.$nom_underscore;
59                                        break;
60                                default:
61                                        // Les [] dans le nom de la saisie sont transformés en _ dans le
62                                        // nom de la classe, il faut faire pareil
63                                        $class_li = 'editer_'.$nom_underscore;
64                        }
65                        $condition = isset($saisie['options']['afficher_si']) ? $saisie['options']['afficher_si'] : '';
66                        // retrouver l'identifiant
67                        $identifiant = '';
68                        if (isset($saisie['identifiant']) and $saisie['identifiant']) {
69                                $identifiant = $saisie['identifiant'];
70                        }
71                        // On gère le cas @plugin:non_plugin@
72                        preg_match_all('#@plugin:(.+)@#U', $condition, $matches);
73                        foreach ($matches[1] as $plug) {
74                                if (defined('_DIR_PLUGIN_'.strtoupper($plug))) {
75                                        $condition = preg_replace('#@plugin:'.$plug.'@#U', 'true', $condition);
76                                } else {
77                                        $condition = preg_replace('#@plugin:'.$plug.'@#U', 'false', $condition);
78                                }
79                        }
80                        $condition = saisies_transformer_condition_afficher_si_config($condition);
81                        // On transforme en une condition valide
82                        preg_match_all('#@(.+)@#U', $condition, $matches);
83                        foreach ($matches[1] as $nom) {
84                                switch ($saisies[$nom]['saisie']) {
85                                        case 'radio':
86                                        case 'oui_non':
87                                        case 'true_false':
88                                                $condition = preg_replace('#@'.preg_quote($nom).'@#U', '$(form).find("[name=\''.$nom.'\']:checked").val()', $condition);
89                                                break;
90                                        case 'case':
91                                                $condition = preg_replace('#@'.preg_quote($nom).'@#U', '($(form).find(".checkbox[name=\''.$nom.'\']").is(":checked") ? $(form).find(".checkbox[name=\''.$nom.'\']").val() : "")', $condition);
92                                                break;
93                                        case 'checkbox':
94                                                /**
95                                                 * Faire fonctionner @checkbox_xx@ == 'valeur' et @checkbox_xx@ != 'valeur'
96                                                 */
97                                                $condition = preg_replace('#@(.+)@\s*(==|(!)=)\s*(\'[^\']*\'|"[^"]*")#U', "@$1@ $3IN $4", $condition );
98                                                /**
99                                                 * Faire fonctionner @checkbox_xx@ IN 'valeur' ou @checkbox_xx@ !IN 'valeur'
100                                                 */
101                                                preg_match_all('#@(.+)@\s*(!IN|IN)\s*[\'"](.*)[\'"]#U', $condition, $matches3);
102                                                foreach ($matches3[3] as $key => $value) {
103                                                        $not = '';
104                                                        if ($matches3[2][$key] == '!IN') {
105                                                                $not = '!';
106                                                        }
107                                                        $values = explode(',', $value);
108                                                        $new_condition = $not.'(';
109                                                        foreach ($values as $key2 => $cond) {
110                                                                if ($key2 > 0) {
111                                                                        $new_condition .= ' || ';
112                                                                }
113                                                                $new_condition .= '($(form).find(".checkbox[name=\''.$nom.'[]\'][value='.$cond.']").is(":checked") ? $(form).find(".checkbox[name=\''.$nom.'[]\'][value='.$cond.']").val() : "") == "'.$cond.'"';
114                                                        }
115                                                        $new_condition .= ')';
116                                                        $condition = str_replace($matches3[0][$key], $new_condition, $condition);
117                                                }
118                                                break;
119                                        default:
120                                                $condition = preg_replace('#@'.preg_quote($nom).'@#U', '$(form).find("[name=\''.$nom.'\']").val()', $condition);
121                                }
122                        }
123                        if ($identifiant) {
124                                $sel = "[data-id='$identifiant']";
125                        } else {
126                                $sel = ".$class_li";
127                        }
128                        $code .= "\tif (".$condition.") {\n"
129                                                         .      "\t\t$(form).find(\"$sel\")."._SAISIES_AFFICHER_SI_JS_SHOW.";\n";
130                        if (html5_permis()) {
131                        $pour_html_5 =  "$sel.obligatoire > input,"// si le afficher_si porte directement sur le input
132                                                        ."$sel .obligatoire input:not('chekbox'),"// si le afficher_si porte sur le fieldset, tous les input dedans sont concernés, quelque soit leur profondeur (ce qui inclut notamment les cas complexe, type radio ou date). Une exception toutefois : les checbbox multiple, qui ne peuvent avoir de required
133                                                        ."$sel.obligatoire > textarea,"// si le afficher_si porte directement sur le textearea
134                                                        ."$sel.obligatoire > .edition textarea,"// si le afficher_si porte directement sur le textearea encapsulé dans un markitup
135                                                        ."$sel .obligatoire > textarea,"// si le afficher_si porte sur le fiedset
136                                                        ."$sel.obligatoire > select,"//si le afficher_si porte directement sur le select
137                                                        ."$sel .obligatoire > select,"//si le afficher_si porte sur le fieldset
138                                                        ."$sel.obligatoire input:radio";//si le afficher_si porte sur un radio
139                        $code .=        "\t\t$(form).find("
140                                                        .'"'."$pour_html_5\")".
141                                                        ".attr(\"required\",true);\n";
142                        }
143                        $code .=        "\t}\n";
144                        $code .= "\telse {\n";
145                        if (html5_permis()) {
146                                $code .= "\t\t$(form).find(\n\t\t\t"
147                                                        .'"'."$pour_html_5\")\n"
148                                                        ."\t\t.attr(".'"required"'.",false);\n";
149                        }
150                        $code .= "\t\tif (chargement==true) {\n"
151                                        ."\t\t\t$(form).find(\"$sel\")."._SAISIES_AFFICHER_SI_JS_HIDE.".css".'("display","none")'.";\n"
152                                        ."\t\t}\n"
153                                        ."\t\telse {\n"
154                                        ."\t\t\t$(form).find(\"$sel\")."._SAISIES_AFFICHER_SI_JS_HIDE.";\n"
155                                        ."\t\t};\n"
156                                        ."\t}\n";
157                }
158        }
159        $code .= "$(form).trigger('saisies_afficher_si_js_ok');\n";
160        $code .= "};\n";
161        $code .= "\t".'$("#afficher_si_'.$id_form.'").parents("form").each(function(){'."\n\t\t".'verifier_saisies_'.$id_form.'(this);});'."\n";
162        $code .= "\t".'$("#afficher_si_'.$id_form.'").parents("form").change(function(){'."\n\t\t".'verifier_saisies_'.$id_form.'(this);});'."\n";
163        $code .= "\tchargement=false;})\n";
164
165        if (!defined('_SAISIES_AFFICHER_SI_JS_LISIBLE')) {
166                define('_SAISIES_AFFICHER_SI_JS_LISIBLE', false);
167        }
168        if (!_SAISIES_AFFICHER_SI_JS_LISIBLE) {
169                // il suffit de régler cette constante à TRUE pour afficher le js de manière plus lisible (et moins sibyllin)
170                $code = str_replace("\n", '', $code); //concatener
171                $code = str_replace("\t", '', $code); //concatener
172        }
173        return $i > 0 ? $code : '';
174}
175
176/**
177 * Lorsque l'on affiche les saisies (#VOIR_SAISIES), les saisies ayant une option afficher_si
178 * et dont les conditions ne sont pas remplies doivent être retirées du tableau de saisies.
179 *
180 * Cette fonction sert aussi lors de la vérification des saisies avec saisies_verifier().
181 * À ce moment là, les saisies non affichées sont retirées de _request
182 * (on passe leur valeur à NULL).
183 *
184 * @param array      $saisies
185 *                            Tableau de descriptions de saisies
186 * @param array|null $env
187 *                            Tableau d'environnement transmis dans inclure/voir_saisies.html,
188 *                            NULL si on doit rechercher dans _request (pour saisies_verifier()).
189 *
190 * @return array
191 *               Tableau de descriptions de saisies
192 */
193function saisies_verifier_afficher_si($saisies, $env = null) {
194        // eviter une erreur par maladresse d'appel :)
195        if (!is_array($saisies)) {
196                return array();
197        }
198        // Economiser un peu de calcul, notamment pour formidable
199        static $precedent_saisies = array();
200        static $precedent_env = array();
201        if ($precedent_saisies == $saisies and $precedent_env == $env) {
202                return $saisies;
203        }
204        $precedent_saisies = $saisies;
205        $precedent_env = $env;
206        foreach ($saisies as $cle => $saisie) {
207                if (isset($saisie['options']['afficher_si'])) {
208                        $condition = $saisie['options']['afficher_si'];
209                        // Est-ce uniquement au remplissage?
210                        if (isset($saisie['options']['afficher_si_remplissage_uniquement'])
211                                and $saisie['options']['afficher_si_remplissage_uniquement']=='on'){
212                                $remplissage_uniquement = true;
213                        } else {
214                                $remplissage_uniquement = false;
215                        }
216
217                        // On gère le cas @plugin:nom_plugin@
218                        preg_match_all('#@plugin:(.+)@#U', $condition, $matches);
219                        foreach ($matches[1] as $plug) {
220                                if (defined('_DIR_PLUGIN_'.strtoupper($plug))) {
221                                        $condition = preg_replace('#@plugin:'.$plug.'@#U', 'true', $condition);
222                                } else {
223                                        $condition = preg_replace('#@plugin:'.$plug.'@#U', 'false', $condition);
224                                }
225                        }
226                        // On transforme en une condition PHP valide
227                        $ok = saisies_evaluer_afficher_si($condition, $env);
228                        if (!$ok) {
229                                if ($remplissage_uniquement == false or is_null($env)) {
230                                        unset($saisies[$cle]);
231                                }
232                                if (is_null($env)) {
233                                        if ($saisie['saisie'] == 'explication') {
234                                                unset($saisies[$cle]);
235                                        } else {
236                                                saisies_set_request_null_recursivement($saisie);
237                                        }
238                                }
239                        }
240                }
241                if (isset($saisies[$cle]['saisies'])) {
242                        // S'il s'agit d'un fieldset ou equivalent, verifier les sous-saisies
243                        $saisies[$cle]['saisies'] = saisies_verifier_afficher_si($saisies[$cle]['saisies'], $env);
244                }
245        }
246        return $saisies;
247}
248
249
250
251/**
252 * Pose un set_request null sur une saisie et toute ses sous-saisies.
253 * Utiliser notamment pour annuler toutes les sous saisies d'un fieldeset
254 * si le fieldset est masquée à cause d'un afficher_si.
255 * @param array $saisie
256**/
257function saisies_set_request_null_recursivement($saisie) {
258        set_request($saisie['options']['nom'], null);
259        if (isset($saisie['saisies'])) {
260                foreach ($saisie['saisies'] as $sous_saisie) {
261                        saisies_set_request_null_recursivement($sous_saisie);
262                }
263        }
264}
265
266/**
267 * Récupère la valeur d'un champ à tester avec afficher_si
268 * Si le champ est de type @config:xx@, alors prend la valeur de la config
269 * sinon en _request() ou en $env["valeurs"]
270 * @param string $champ: le champ
271 * @param null|array $env
272 * @return  la valeur du champ ou de la config
273 **/
274function saisies_afficher_si_get_valeur_champ($champ, $env) {
275        if (preg_match_all("#config:(.*)#", $champ, $matches, PREG_SET_ORDER)) {
276                foreach ($matches as $plugin) {
277                        $arobase = $plugin[0];
278                        $config_a_tester = str_replace(":", "/", $plugin[1]);
279                        $champ = lire_config($config_a_tester);
280                }
281        } elseif (is_null($env)) {
282                $champ = _request($champ);
283        } else {
284                $champ = $env["valeurs"][$champ];
285        }
286        return $champ;
287}
288
289/**
290 * Prend un test conditionnel
291 * cherche dedans les test @config:xxx@
292 * remplace par la valeur de la config
293 * @param string condition;
294 * @return string condition;
295**/
296function saisies_transformer_condition_afficher_si_config($condition) {
297        include_spip("inc/config");
298        preg_match_all('#@config:(.*)@#U', $condition, $matches, PREG_SET_ORDER);
299        foreach ($matches as $plugin) {
300                $arobase = $plugin[0];
301                $config_a_tester = str_replace(":", "/", $plugin[1]);
302                $config = lire_config($config_a_tester);
303                $condition = str_replace($arobase, '"'.$config.'"', $condition);
304        }
305        return $condition;
306}
307
308/**
309 * Prend un test conditionnel,
310 * le sépare en une série de sous-tests de type champ - operateur - valeur
311 * remplace chacun de ces sous-tests par son résultat
312 * renvoie la chaine transformé
313 * @param string $condition
314 * @param array|null $env
315 *   Tableau d'environnement transmis dans inclure/voir_saisies.html,
316 *   NULL si on doit rechercher dans _request (pour saisies_verifier()).
317 * @return string $condition
318**/
319function saisies_transformer_condition_afficher_si($condition, $env = null) {
320        $regexp =
321          "(?<negation>!?)" // négation éventuelle
322                . "(?:@(?<champ>.+?)@)" // @champ_@
323                . "(" // partie operateur + valeur (optionnelle) : debut
324                . "(?:\s*?)" // espaces éventuels après
325                . "(?<operateur>==|!=|IN|!IN|>=|>|<=|<)" // opérateur
326                . "(?:\s*?)" // espaces éventuels après
327                . "((?<guillemet>\"|')(?<valeur>.*?)(\k<guillemet>)|(?<valeur_numerique>\d+))" // valeur (string) ou valeur_numérique (int)
328                . ")?"; // partie operateur + valeur (optionnelle) : fin
329        $regexp = "#$regexp#";
330        if (preg_match_all($regexp, $condition, $tests, PREG_SET_ORDER)) {
331                foreach ($tests as $test) {
332                        $expression = $test[0];
333                        $champ = saisies_afficher_si_get_valeur_champ($test['champ'], $env);
334                        $operateur = isset($test['operateur']) ? $test['operateur'] : null;
335
336                        if (isset($test['valeur_numerique'])) {
337                                $valeur = intval($test['valeur_numerique']);
338                        } elseif (isset($test['valeur'])) {
339                                $valeur = $test['valeur'];
340                        } else {
341                                $valeur = null;
342                        }
343
344                        $test_modifie = saisies_tester_condition_afficher_si($champ, $operateur, $valeur) ? 'true' : 'false';
345                        if (isset($test['negation'])) {
346                                $test_modifie = $test['negation'].$test_modifie;
347                        }
348                        $condition = str_replace($expression, $test_modifie, $condition);
349                }
350        } else {
351                $condition = str_replace(' ', '', $condition);
352                $condition_possible = array("!false", "false", "true", "!true");
353                if (!in_array($condition, $condition_possible)){
354                        spip_log("Afficher_si incorrect : $condition", "saisies"._LOG_CRITIQUE);
355                        $condition = true;
356                }
357        }
358        return $condition;
359}
360
361/**
362 * Teste une condition d'afficher_si
363 * @param string|array champ le champ à tester. Cela peut être :
364 *      - un string
365 *      - un tableau
366 *      - un tableau sérializé
367 * @param string $operateur : l'opérateur:
368 * @param string $valeur la valeur à tester pour un IN. Par exemple "23" ou encore "23", "25"
369 * @return bool false / true selon la condition
370**/
371function saisies_tester_condition_afficher_si($champ, $operateur=null, $valeur=null) {
372        // Si operateur null => on test juste qu'un champ est cochée / validé
373        if ($operateur === null and $valeur === null) {
374                return isset($champ) and $champ;
375        }
376        // Dans tous les cas, enlever les guillemets qui sont au sein de valeur
377        //Si champ est de type string, tenter d'unserializer
378        $tenter_unserialize = @unserialize($champ);
379        if ($tenter_unserialize)  {
380                $champ = $tenter_unserialize;
381        }
382
383        //Et maintenant appeler les sous fonctions qui vont bien
384        if (is_string($champ)) {
385                return saisies_tester_condition_afficher_si_string($champ, $operateur, $valeur);
386        } elseif (is_array($champ)) {
387                return saisies_tester_condition_afficher_si_array($champ, $operateur, $valeur);
388        } else {
389                return false;
390        }
391}
392
393/**
394 * Teste un condition d'afficher_si lorsque la valeur envoyée par le formulaire est une chaîne
395 * @param string champ le champ à tester.
396 * @param string $operateur : l'opérateur:
397 * @param string|int $valeur la valeur à tester pour un IN. Par exemple "23" ou encore "23, 25", 23
398 * @return bool false / true selon la condition
399**/
400function saisies_tester_condition_afficher_si_string($champ, $operateur, $valeur) {
401        if ($operateur == "==") {
402                return $champ == $valeur;
403        } elseif ($operateur == "!=") {
404                return $champ != $valeur;
405        } elseif ($operateur == '<') {
406                return $champ < $valeur;
407        } elseif ($operateur == '<=') {
408                return $champ <= $valeur;
409        } elseif ($operateur == '>') {
410                return $champ > $valeur;
411        } elseif ($operateur == '>=') {
412                return $champ >= $valeur;
413        } else {//Si mauvaise operateur -> on annule
414                return false;
415        }
416}
417
418/**
419 * Teste un condition d'afficher_si lorsque la valeur postée est  un tableau
420 * @param array champ le champ à tester.
421 * @param string $operateur : l'opérateur:
422 * @param string $valeur la valeur à tester pour un IN. Par exemple "23" ou encore "23", "25"
423 * @return bool false / true selon la condition
424**/
425function saisies_tester_condition_afficher_si_array($champ, $operateur, $valeur) {
426        $valeur = explode(',', $valeur);
427        $intersection = array_intersect($champ, $valeur);
428        if ($operateur == "==" or $operateur == "IN") {
429                return count($intersection) > 0;
430        } else {
431                return count($intersection) == 0;
432        }
433        return false;
434}
435
436/**
437 * Evalue un afficher_si
438 * @param string $condition (déjà checkée en terme de sécurité)
439 * @param array|null $env
440 *   Tableau d'environnement transmis dans inclure/voir_saisies.html,
441 *   NULL si on doit rechercher dans _request (pour saisies_verifier()).
442 * @return bool le résultat du test
443**/
444function saisies_evaluer_afficher_si($condition, $env = null) {
445        $condition = saisies_transformer_condition_afficher_si($condition, $env);
446        eval('$ok = '.$condition.';');
447        return $ok;
448}
Note: See TracBrowser for help on using the repository browser.