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

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

deux constantes pour définir les effets de masquage/affichage de champ avec afficher_si

File size: 16.2 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, "// 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)
133                                                        ."$sel.obligatoire > textarea, "// si le afficher_si porte directement sur le textearea
134                                                        ."$sel .obligatoire > textarea, "// si le afficher_si porte sur le fiedset
135                                                        ."$sel.obligatoire > select, "//si le afficher_si porte directement sur le select
136                                                        ."$sel .obligatoire > select, "//si le afficher_si porte sur le fieldset
137                                                        ."$sel.obligatoire input:radio";//si le afficher_si porte sur un radio
138                        $code .=        "\t\t$(form).find("
139                                                        .'"'."$pour_html_5\")".
140                                                        ".attr(\"required\",true);\n";
141                        }
142                        $code .=        "\t}\n";
143                        $code .= "\telse {\n";
144                        if (html5_permis()) {
145                                $code .= "\t\t$(form).find(\n\t\t\t"
146                                                        .'"'."$pour_html_5\")\n"
147                                                        ."\t\t.attr(".'"required"'.",false);\n";
148                        }
149                        $code .= "\t\tif (chargement==true) {\n"
150                                        ."\t\t\t$(form).find(\"$sel\")."._SAISIES_AFFICHER_SI_JS_HIDE.".css".'("display","none")'.";\n"
151                                        ."\t\t}\n"
152                                        ."\t\telse {\n"
153                                        ."\t\t\t$(form).find(\"$sel\")."._SAISIES_AFFICHER_SI_JS_HIDE.";\n"
154                                        ."\t\t};\n"
155                                        ."\t}\n";
156                }
157        }
158        $code .= "$(form).trigger('saisies_afficher_si_js_ok');\n";
159        $code .= "};\n";
160        $code .= "\t".'$("#afficher_si_'.$id_form.'").parents("form").each(function(){'."\n\t\t".'verifier_saisies_'.$id_form.'(this);});'."\n";
161        $code .= "\t".'$("#afficher_si_'.$id_form.'").parents("form").change(function(){'."\n\t\t".'verifier_saisies_'.$id_form.'(this);});'."\n";
162        $code .= "\tchargement=false;})\n";
163
164        if (!defined('_SAISIES_AFFICHER_SI_JS_LISIBLE')) {
165                define('_SAISIES_AFFICHER_SI_JS_LISIBLE', false);
166        }
167        if (!_SAISIES_AFFICHER_SI_JS_LISIBLE) {
168                // il suffit de régler cette constante à TRUE pour afficher le js de manière plus lisible (et moins sibyllin)
169                $code = str_replace("\n", '', $code); //concatener
170                $code = str_replace("\t", '', $code); //concatener
171        }
172        return $i > 0 ? $code : '';
173}
174
175/**
176 * Lorsque l'on affiche les saisies (#VOIR_SAISIES), les saisies ayant une option afficher_si
177 * et dont les conditions ne sont pas remplies doivent être retirées du tableau de saisies.
178 *
179 * Cette fonction sert aussi lors de la vérification des saisies avec saisies_verifier().
180 * À ce moment là, les saisies non affichées sont retirées de _request
181 * (on passe leur valeur à NULL).
182 *
183 * @param array      $saisies
184 *                            Tableau de descriptions de saisies
185 * @param array|null $env
186 *                            Tableau d'environnement transmis dans inclure/voir_saisies.html,
187 *                            NULL si on doit rechercher dans _request (pour saisies_verifier()).
188 *
189 * @return array
190 *               Tableau de descriptions de saisies
191 */
192function saisies_verifier_afficher_si($saisies, $env = null) {
193        // eviter une erreur par maladresse d'appel :)
194        if (!is_array($saisies)) {
195                return array();
196        }
197        // Economiser un peu de calcul, notamment pour formidable
198        static $precedent_saisies = array();
199        static $precedent_env = array();
200        if ($precedent_saisies == $saisies and $precedent_env == $env) {
201                return $saisies;
202        }
203        $precedent_saisies = $saisies;
204        $precedent_env = $env;
205        foreach ($saisies as $cle => $saisie) {
206                if (isset($saisie['options']['afficher_si'])) {
207                        $condition = $saisie['options']['afficher_si'];
208                        // Si tentative de code malicieux, on rejete
209                        if (!saisies_verifier_securite_afficher_si($condition)) {
210                                spip_log("Afficher_si malicieuse : $condition", "saisies"._LOG_CRITIQUE);
211                                $condition = '$ok';
212                        }
213                        // Est-ce uniquement au remplissage?
214                        if (isset($saisie['options']['afficher_si_remplissage_uniquement'])
215                                and $saisie['options']['afficher_si_remplissage_uniquement']=='on'){
216                                $remplissage_uniquement = true;
217                        } else {
218                                $remplissage_uniquement = false;
219                        }
220
221                        // On gère le cas @plugin:non_plugin@
222                        preg_match_all('#@plugin:(.+)@#U', $condition, $matches);
223                        foreach ($matches[1] as $plug) {
224                                if (defined('_DIR_PLUGIN_'.strtoupper($plug))) {
225                                        $condition = preg_replace('#@plugin:'.$plug.'@#U', 'true', $condition);
226                                } else {
227                                        $condition = preg_replace('#@plugin:'.$plug.'@#U', 'false', $condition);
228                                }
229                        }
230                        $condition = saisies_transformer_condition_afficher_si_config($condition);
231                        // On transforme en une condition PHP valide
232                        $ok = saisies_evaluer_afficher_si($condition, $env);
233                        if (!$ok) {
234                                if ($remplissage_uniquement == false or is_null($env)) {
235                                        unset($saisies[$cle]);
236                                }
237                                if (is_null($env)) {
238                                        if ($saisie['saisie'] == 'explication') {
239                                                unset($saisies[$cle]);
240                                        } else {
241                                                saisies_set_request_null_recursivement($saisie);
242                                        }
243                                }
244                        }
245                }
246                if (isset($saisies[$cle]['saisies'])) {
247                        // S'il s'agit d'un fieldset ou equivalent, verifier les sous-saisies
248                        $saisies[$cle]['saisies'] = saisies_verifier_afficher_si($saisies[$cle]['saisies'], $env);
249                }
250        }
251        return $saisies;
252}
253
254
255
256/**
257 * Pose un set_request null sur une saisie et toute ses sous-saisies.
258 * Utiliser notamment pour annuler toutes les sous saisies d'un fieldeset
259 * si le fieldset est masquée à cause d'un afficher_si.
260 * @param array $saisie
261**/
262function saisies_set_request_null_recursivement($saisie) {
263        set_request($saisie['options']['nom'], null);
264        if (isset($saisie['saisies'])) {
265                foreach ($saisie['saisies'] as $sous_saisie) {
266                        saisies_set_request_null_recursivement($sous_saisie);
267                }
268        }
269}
270
271/**
272 * Vérifie qu'on ne tente pas de faire executer du code PHP en utilisant afficher_si.
273 * N'importe quoi autorisé entre @@ et "" et ''
274 * Liste de mot clé autorisé en dehors
275 * @param string $condition
276 * @return bool true si usage légitime, false si tentative d'execution de code PHP
277 */
278function saisies_verifier_securite_afficher_si($condition) {
279        $autoriser_hors_guillemets = array("!", "IN", "\(", "\)", "=", "\s", "&&", "\|\|");
280        $autoriser_hors_guillemets = "#(".implode($autoriser_hors_guillemets, "|").")#m";
281
282        $entre_guillemets = "#(?<guillemet>(^\\\)?(\"|'|@))(.*)(\k<guillemet>)#mU"; // trouver tout ce qu'il y entre guillemet, sauf si les guillemets sont échapés
283        $condition = preg_replace($entre_guillemets, "", $condition);//Supprimer tout ce qu'il y a entre guillement
284        $condition = preg_replace($autoriser_hors_guillemets, "", $condition);//Supprimer tout ce qui est autorisé hors guillemets
285        if ($condition) {//S'il restre quelque chose, c'est pas normal
286                return false;
287        }
288        //Sinon c'est que c'est bon
289        return true;
290}
291
292/**
293 * Prend un test conditionnel
294 * cherche dedans les test @config:xxx@
295 * remplace par la valeur de la config
296 * @param string condition;
297 * @return string condition;
298**/
299function saisies_transformer_condition_afficher_si_config($condition) {
300        include_spip("inc/config");
301        preg_match_all('#@config:(.*)@#U', $condition, $matches, PREG_SET_ORDER);
302        foreach ($matches as $plugin) {
303                $arobase = $plugin[0];
304                $config_a_tester = str_replace(":", "/", $plugin[1]);
305                $config = lire_config($config_a_tester);
306                $condition = str_replace($arobase, '"'.$config.'"', $condition);
307        }
308        return $condition;
309}
310
311/**
312 * Prend un test conditionnel,
313 * le sépare en une série de sous-tests de type champ - operateur - valeur
314 * remplace chacun de ces sous-tests par son résultat
315 * renvoie la chaine transformé
316 * @param string $condition
317 * @param array|null $env
318 *   Tableau d'environnement transmis dans inclure/voir_saisies.html,
319 *   NULL si on doit rechercher dans _request (pour saisies_verifier()).
320 * @return string $condition
321**/
322function saisies_transformer_condition_afficher_si($condition, $env = null) {
323        $regexp =
324          "(?<negation>!?)" // négation éventuelle
325                . "(?:@(?<champ>.+?)@)" // @champ_@
326                . "(" // partie operateur + valeur (optionnelle) : debut
327                . "(?:\s*?)" // espaces éventuels après
328                . "(?<operateur>==|!=|IN|!IN)" // opérateur
329                . "(?:\s*?)" // espaces éventuels après
330                . "(?<guillemet>\"|')(?<valeur>.*?)(\k<guillemet>)" // valeur
331                . ")?"; // partie operateur + valeur (optionnelle) : fin
332        $regexp = "#$regexp#";
333        if (preg_match_all($regexp, $condition, $tests, PREG_SET_ORDER)) {
334                foreach ($tests as $test) {
335                        $expression = $test[0];
336                        $champ = $test['champ'];
337                        if (is_null($env)) {
338                                $champ = _request($champ);
339                        } else {
340                                $champ = $env["valeurs"][$champ];
341                        }
342                        $operateur = isset($test['operateur']) ? $test['operateur'] : null;
343                        $valeur = isset($test['valeur']) ? $test['valeur'] : null;
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        }
351        return $condition;
352}
353
354/**
355 * Teste une condition d'afficher_si
356 * @param string|array champ le champ à tester. Cela peut être :
357 *      - un string
358 *      - un tableau
359 *      - un tableau sérializé
360 * @param string $operateur : l'opérateur:
361 *      - IN
362 *      - !IN
363 *      - ==
364 *      - !=
365 *      @param string $valeur la valeur à tester pour un IN. Par exemple "23" ou encore "23", "25"
366 * @return bool false / true selon la condition
367 **/
368function saisies_tester_condition_afficher_si($champ, $operateur=null, $valeur=null) {
369        // Si operateur null => on test juste qu'un champ est cochée / validé
370        if ($operateur === null and $valeur === null) {
371                return isset($champ) and $champ;
372        }
373
374        // Dans tous les cas, enlever les guillemets qui sont au sein de valeur
375        //Si champ est de type string, tenter d'unserializer
376        $tenter_unserialize = @unserialize($champ);
377        if ($tenter_unserialize)  {
378                $champ = $tenter_unserialize;
379        }
380
381        //Et maintenant appeler les sous fonctions qui vont bien
382        if (is_string($champ)) {
383                return saisies_tester_condition_afficher_si_string($champ, $operateur, $valeur);
384        }  elseif (is_array($champ)) {
385                return saisies_tester_condition_afficher_si_array($champ, $operateur, $valeur);
386        } else {
387                return false;
388        }
389}
390
391/**
392 * Teste un condition d'afficher_si lorsqu'il s'agit d'une chaîne
393 * @param string champ le champ à tester.
394 * @param string $operateur : l'opérateur:
395 *      - IN
396 *      - !IN
397 *      - ==
398 *      - !=
399 *      @param string $valeur la valeur à tester pour un IN. Par exemple "23" ou encore "23", "25"
400 * @return bool false / true selon la condition
401**/
402function saisies_tester_condition_afficher_si_string($champ, $operateur, $valeur) {
403        if ($operateur == "==") {
404                return $champ == $valeur;
405        } elseif ($operateur == "!=") {
406                return $champ != $valeur;
407        } else {//Si mauvaise operateur -> on annule
408                return false;
409        }
410}
411
412/**
413 * Teste un condition d'afficher_si lorsqu'il s'agit d'un tableau
414 * @param array champ le champ à tester.
415 * @param string $operateur : l'opérateur:
416 *      - IN
417 *      - !IN
418 *      - ==
419 *      - !=
420 * @param string $valeur la valeur à tester pour un IN. Par exemple "23" ou encore "23", "25"
421 * @return bool false / true selon la condition
422**/
423function saisies_tester_condition_afficher_si_array($champ, $operateur, $valeur) {
424        $valeur = explode(',', $valeur);
425        $intersection = array_intersect($champ, $valeur);
426        if ($operateur == "==" or $operateur == "IN") {
427                return count($intersection) > 0;
428        } else {
429                return count($intersection) == 0;
430        }
431        return false;
432}
433
434/**
435 * Evalue un afficher_si
436 * @param string $condition (déjà checkée en terme de sécurité)
437 * @param array|null $env
438 *   Tableau d'environnement transmis dans inclure/voir_saisies.html,
439 *   NULL si on doit rechercher dans _request (pour saisies_verifier()).
440 * @return bool le résultat du test
441**/
442function saisies_evaluer_afficher_si($condition, $env = null) {
443        $condition = saisies_transformer_condition_afficher_si($condition, $env);
444        eval('$ok = '.$condition.';');
445        return $ok;
446}
Note: See TracBrowser for help on using the repository browser.