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

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

maintenant qu'on a un vrai parseur pour l'évaluation des conditions en php, il n'est pas la peine de remettre un vérificateur de sécurité, puisqu'on ne fait plus de eval() direct

File size: 15.0 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                        // Est-ce uniquement au remplissage?
209                        if (isset($saisie['options']['afficher_si_remplissage_uniquement'])
210                                and $saisie['options']['afficher_si_remplissage_uniquement']=='on'){
211                                $remplissage_uniquement = true;
212                        } else {
213                                $remplissage_uniquement = false;
214                        }
215
216                        // On gère le cas @plugin:non_plugin@
217                        preg_match_all('#@plugin:(.+)@#U', $condition, $matches);
218                        foreach ($matches[1] as $plug) {
219                                if (defined('_DIR_PLUGIN_'.strtoupper($plug))) {
220                                        $condition = preg_replace('#@plugin:'.$plug.'@#U', 'true', $condition);
221                                } else {
222                                        $condition = preg_replace('#@plugin:'.$plug.'@#U', 'false', $condition);
223                                }
224                        }
225                        $condition = saisies_transformer_condition_afficher_si_config($condition);
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/**
268 * Prend un test conditionnel
269 * cherche dedans les test @config:xxx@
270 * remplace par la valeur de la config
271 * @param string condition;
272 * @return string condition;
273**/
274function saisies_transformer_condition_afficher_si_config($condition) {
275        include_spip("inc/config");
276        preg_match_all('#@config:(.*)@#U', $condition, $matches, PREG_SET_ORDER);
277        foreach ($matches as $plugin) {
278                $arobase = $plugin[0];
279                $config_a_tester = str_replace(":", "/", $plugin[1]);
280                $config = lire_config($config_a_tester);
281                $condition = str_replace($arobase, '"'.$config.'"', $condition);
282        }
283        return $condition;
284}
285
286/**
287 * Prend un test conditionnel,
288 * le sépare en une série de sous-tests de type champ - operateur - valeur
289 * remplace chacun de ces sous-tests par son résultat
290 * renvoie la chaine transformé
291 * @param string $condition
292 * @param array|null $env
293 *   Tableau d'environnement transmis dans inclure/voir_saisies.html,
294 *   NULL si on doit rechercher dans _request (pour saisies_verifier()).
295 * @return string $condition
296**/
297function saisies_transformer_condition_afficher_si($condition, $env = null) {
298        $regexp =
299          "(?<negation>!?)" // négation éventuelle
300                . "(?:@(?<champ>.+?)@)" // @champ_@
301                . "(" // partie operateur + valeur (optionnelle) : debut
302                . "(?:\s*?)" // espaces éventuels après
303                . "(?<operateur>==|!=|IN|!IN)" // opérateur
304                . "(?:\s*?)" // espaces éventuels après
305                . "(?<guillemet>\"|')(?<valeur>.*?)(\k<guillemet>)" // valeur
306                . ")?"; // partie operateur + valeur (optionnelle) : fin
307        $regexp = "#$regexp#";
308        if (preg_match_all($regexp, $condition, $tests, PREG_SET_ORDER)) {
309                foreach ($tests as $test) {
310                        $expression = $test[0];
311                        $champ = $test['champ'];
312                        if (is_null($env)) {
313                                $champ = _request($champ);
314                        } else {
315                                $champ = $env["valeurs"][$champ];
316                        }
317                        $operateur = isset($test['operateur']) ? $test['operateur'] : null;
318                        $valeur = isset($test['valeur']) ? $test['valeur'] : null;
319                        $test_modifie = saisies_tester_condition_afficher_si($champ, $operateur, $valeur) ? 'true' : 'false';
320                        if (isset($test['negation'])) {
321                                $test_modifie = $test['negation'].$test_modifie;
322                        }
323                        $condition = str_replace($expression, $test_modifie, $condition);
324                }
325        }
326        return $condition;
327}
328
329/**
330 * Teste une condition d'afficher_si
331 * @param string|array champ le champ à tester. Cela peut être :
332 *      - un string
333 *      - un tableau
334 *      - un tableau sérializé
335 * @param string $operateur : l'opérateur:
336 *      - IN
337 *      - !IN
338 *      - ==
339 *      - !=
340 *      @param string $valeur la valeur à tester pour un IN. Par exemple "23" ou encore "23", "25"
341 * @return bool false / true selon la condition
342 **/
343function saisies_tester_condition_afficher_si($champ, $operateur=null, $valeur=null) {
344        // Si operateur null => on test juste qu'un champ est cochée / validé
345        if ($operateur === null and $valeur === null) {
346                return isset($champ) and $champ;
347        }
348
349        // Dans tous les cas, enlever les guillemets qui sont au sein de valeur
350        //Si champ est de type string, tenter d'unserializer
351        $tenter_unserialize = @unserialize($champ);
352        if ($tenter_unserialize)  {
353                $champ = $tenter_unserialize;
354        }
355
356        //Et maintenant appeler les sous fonctions qui vont bien
357        if (is_string($champ)) {
358                return saisies_tester_condition_afficher_si_string($champ, $operateur, $valeur);
359        }  elseif (is_array($champ)) {
360                return saisies_tester_condition_afficher_si_array($champ, $operateur, $valeur);
361        } else {
362                return false;
363        }
364}
365
366/**
367 * Teste un condition d'afficher_si lorsqu'il s'agit d'une chaîne
368 * @param string champ le champ à tester.
369 * @param string $operateur : l'opérateur:
370 *      - IN
371 *      - !IN
372 *      - ==
373 *      - !=
374 *      @param string $valeur la valeur à tester pour un IN. Par exemple "23" ou encore "23", "25"
375 * @return bool false / true selon la condition
376**/
377function saisies_tester_condition_afficher_si_string($champ, $operateur, $valeur) {
378        if ($operateur == "==") {
379                return $champ == $valeur;
380        } elseif ($operateur == "!=") {
381                return $champ != $valeur;
382        } else {//Si mauvaise operateur -> on annule
383                return false;
384        }
385}
386
387/**
388 * Teste un condition d'afficher_si lorsqu'il s'agit d'un tableau
389 * @param array champ le champ à tester.
390 * @param string $operateur : l'opérateur:
391 *      - IN
392 *      - !IN
393 *      - ==
394 *      - !=
395 * @param string $valeur la valeur à tester pour un IN. Par exemple "23" ou encore "23", "25"
396 * @return bool false / true selon la condition
397**/
398function saisies_tester_condition_afficher_si_array($champ, $operateur, $valeur) {
399        $valeur = explode(',', $valeur);
400        $intersection = array_intersect($champ, $valeur);
401        if ($operateur == "==" or $operateur == "IN") {
402                return count($intersection) > 0;
403        } else {
404                return count($intersection) == 0;
405        }
406        return false;
407}
408
409/**
410 * Evalue un afficher_si
411 * @param string $condition (déjà checkée en terme de sécurité)
412 * @param array|null $env
413 *   Tableau d'environnement transmis dans inclure/voir_saisies.html,
414 *   NULL si on doit rechercher dans _request (pour saisies_verifier()).
415 * @return bool le résultat du test
416**/
417function saisies_evaluer_afficher_si($condition, $env = null) {
418        $condition = saisies_transformer_condition_afficher_si($condition, $env);
419        eval('$ok = '.$condition.';');
420        return $ok;
421}
Note: See TracBrowser for help on using the repository browser.