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

Last change on this file since 113036 was 113036, checked in by root, 2 months ago

saisies_verifier_afficher_si() : unset, si nécessaire, les saisei explication

File size: 15.3 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        foreach ($saisies as $saisie) {
34                // on utilise comme selecteur l'identifiant de saisie en priorite s'il est connu
35                // parce que conteneur_class = 'tableau[nom][option]' ne fonctionne evidement pas
36                // lorsque le name est un tableau
37                if (isset($saisie['options']['afficher_si'])) {
38                        ++$i;
39                        // Les [] dans le nom de la saisie sont transformés en _ dans le
40                        // nom de la classe, il faut faire pareil
41                        $nom_underscore = rtrim(
42                                        preg_replace('/[][]\[?/', '_', $saisie['options']['nom']),
43                                        '_'
44                        );
45                        // retrouver la classe css probable
46                        switch ($saisie['saisie']) {
47                                case 'fieldset':
48                                        $class_li = 'fieldset_'.$nom_underscore;
49                                        break;
50                                case 'explication':
51                                        $class_li = 'explication_'.$nom_underscore;
52                                        break;
53                                default:
54                                        // Les [] dans le nom de la saisie sont transformés en _ dans le
55                                        // nom de la classe, il faut faire pareil
56                                        $class_li = 'editer_'.$nom_underscore;
57                        }
58                        $condition = isset($saisie['options']['afficher_si']) ? $saisie['options']['afficher_si'] : '';
59                        // retrouver l'identifiant
60                        $identifiant = '';
61                        if (isset($saisie['identifiant']) and $saisie['identifiant']) {
62                                $identifiant = $saisie['identifiant'];
63                        }
64                        // On gère le cas @plugin:non_plugin@
65                        preg_match_all('#@plugin:(.+)@#U', $condition, $matches);
66                        foreach ($matches[1] as $plug) {
67                                if (defined('_DIR_PLUGIN_'.strtoupper($plug))) {
68                                        $condition = preg_replace('#@plugin:'.$plug.'@#U', 'true', $condition);
69                                } else {
70                                        $condition = preg_replace('#@plugin:'.$plug.'@#U', 'false', $condition);
71                                }
72                        }
73                        // On gère le cas @config:plugin:meta@ suivi d'un test
74                        preg_match_all('#@config:(.+):(.+)@#U', $condition, $matches);
75                        foreach ($matches[1] as $plugin) {
76                                $config = lire_config($plugin);
77                                $condition = preg_replace('#@config:'.$plugin.':'.$matches[2][0].'@#U', '"'.$config[$matches[2][0]].'"', $condition);
78                        }
79                        // On transforme en une condition valide
80                        preg_match_all('#@(.+)@#U', $condition, $matches);
81                        foreach ($matches[1] as $nom) {
82                                switch ($saisies[$nom]['saisie']) {
83                                        case 'radio':
84                                        case 'oui_non':
85                                        case 'true_false':
86                                                $condition = preg_replace('#@'.preg_quote($nom).'@#U', '$(form).find("[name=\''.$nom.'\']:checked").val()', $condition);
87                                                break;
88                                        case 'case':
89                                                $condition = preg_replace('#@'.preg_quote($nom).'@#U', '($(form).find(".checkbox[name=\''.$nom.'\']").is(":checked") ? $(form).find(".checkbox[name=\''.$nom.'\']").val() : "")', $condition);
90                                                break;
91                                        case 'checkbox':
92                                                /**
93                                                 * Faire fonctionner @checkbox_xx@ == 'valeur' et @checkbox_xx@ != 'valeur'
94                                                 */
95                                                $condition = preg_replace('#@(.+)@\s*(==|(!)=)\s*(\'[^\']*\'|"[^"]*")#U', "@$1@ $3IN $4", $condition );
96                                                /**
97                                                 * Faire fonctionner @checkbox_xx@ IN 'valeur' ou @checkbox_xx@ !IN 'valeur'
98                                                 */
99                                                preg_match_all('#@(.+)@\s*(!IN|IN)\s*[\'"](.*)[\'"]#U', $condition, $matches3);
100                                                foreach ($matches3[3] as $key => $value) {
101                                                        $not = '';
102                                                        if ($matches3[2][$key] == '!IN') {
103                                                                $not = '!';
104                                                        }
105                                                        $values = explode(',', $value);
106                                                        $new_condition = $not.'(';
107                                                        foreach ($values as $key2 => $cond) {
108                                                                if ($key2 > 0) {
109                                                                        $new_condition .= ' || ';
110                                                                }
111                                                                $new_condition .= '($(form).find(".checkbox[name=\''.$nom.'[]\'][value='.$cond.']").is(":checked") ? $(form).find(".checkbox[name=\''.$nom.'[]\'][value='.$cond.']").val() : "") == "'.$cond.'"';
112                                                        }
113                                                        $new_condition .= ')';
114                                                        $condition = str_replace($matches3[0][$key], $new_condition, $condition);
115                                                }
116                                                break;
117                                        default:
118                                                $condition = preg_replace('#@'.preg_quote($nom).'@#U', '$(form).find("[name=\''.$nom.'\']").val()', $condition);
119                                }
120                        }
121                        if ($identifiant) {
122                                $sel = "[data-id='$identifiant']";
123                        } else {
124                                $sel = ".$class_li";
125                        }
126                        $code .= "\tif (".$condition.") {\n"
127                                                         .      "\t\t$(form).find(\"$sel\").show(400);\n";
128                        if (html5_permis()) {
129                        $pour_html_5 =  "$sel.obligatoire > input, "// si le afficher_si porte directement sur le input
130                                                        ."$sel .obligatoire > input, "// si le afficher_si porte sur le fieldset
131                                                        ."$sel.obligatoire > textarea, "// si le afficher_si porte directement sur le textearea
132                                                        ."$sel .obligatoire > textarea, "// si le afficher_si porte sur le fiedset
133                                                        ."$sel.obligatoire > select, "//si le afficher_si porte directement sur le select
134                                                        ."$sel .obligatoire > select";//si le afficher_si porte sur le fieldset
135                        $code .=        "\t\t$(form).find("
136                                                        .'"'."$pour_html_5\")".
137                                                        ".attr(\"required\",true);\n";
138                        }
139                        $code .=        "\t}\n";
140                        $code .= "\telse {\n";
141                        if (html5_permis()) {
142                                $code .= "\t\t$(form).find(\n\t\t\t"
143                                                        .'"'."$pour_html_5\")\n"
144                                                        ."\t\t.attr(".'"required"'.",false);\n";
145                        }
146                        $code .= "\t\tif (chargement==true) {\n"
147                                        ."\t\t\t$(form).find(\"$sel\").hide(400).css".'("display","none")'.";\n"
148                                        ."\t\t}\n"
149                                        ."\t\telse {\n"
150                                        ."\t\t\t$(form).find(\"$sel\").hide(400);\n"
151                                        ."\t\t};\n"
152                                        ."\t}\n";
153                }
154        }
155        $code .= "$(form).trigger('saisies_afficher_si_js_ok');\n";
156        $code .= "};\n";
157        $code .= "\t".'$("#afficher_si_'.$id_form.'").parents("form").each(function(){'."\n\t\t".'verifier_saisies_'.$id_form.'(this);});'."\n";
158        $code .= "\t".'$("#afficher_si_'.$id_form.'").parents("form").change(function(){'."\n\t\t".'verifier_saisies_'.$id_form.'(this);});'."\n";
159        $code .= "\tchargement=false;})\n";
160
161        if (!defined('_SAISIES_AFFICHER_SI_JS_LISIBLE')) {
162                define('_SAISIES_AFFICHER_SI_JS_LISIBLE', false);
163        }
164        if (!_SAISIES_AFFICHER_SI_JS_LISIBLE) {
165                // il suffit de régler cette constante à TRUE pour afficher le js de manière plus lisible (et moins sibyllin)
166                $code = str_replace("\n", '', $code); //concatener
167                $code = str_replace("\t", '', $code); //concatener
168        }
169        return $i > 0 ? $code : '';
170}
171
172/**
173 * Lorsque l'on affiche les saisies (#VOIR_SAISIES), les saisies ayant une option afficher_si
174 * et dont les conditions ne sont pas remplies doivent être retirées du tableau de saisies.
175 *
176 * Cette fonction sert aussi lors de la vérification des saisies avec saisies_verifier().
177 * À ce moment là, les saisies non affichées sont retirées de _request
178 * (on passe leur valeur à NULL).
179 *
180 * @param array      $saisies
181 *                            Tableau de descriptions de saisies
182 * @param array|null $env
183 *                            Tableau d'environnement transmis dans inclure/voir_saisies.html,
184 *                            NULL si on doit rechercher dans _request (pour saisies_verifier()).
185 *
186 * @return array
187 *               Tableau de descriptions de saisies
188 */
189function saisies_verifier_afficher_si($saisies, $env = null) {
190        // eviter une erreur par maladresse d'appel :)
191        if (!is_array($saisies)) {
192                return array();
193        }
194        foreach ($saisies as $cle => $saisie) {
195                if (isset($saisie['options']['afficher_si'])) {
196                        $condition = $saisie['options']['afficher_si'];
197                        // Si tentative de code malicieux, on rejete
198                        if (!saisies_verifier_securite_afficher_si($condition)) {
199                                spip_log("Afficher_si malicieuse : $condition", "saisies"._LOG_CRITIQUE);
200                                $condition = '$ok';
201                        }
202                        // Est-ce uniquement au remplissage?
203                        if (isset($saisie['options']['afficher_si_remplissage_uniquement'])
204                                and $saisie['options']['afficher_si_remplissage_uniquement']=='on'){
205                                $remplissage_uniquement = true;
206                        } else {
207                                $remplissage_uniquement = false;
208                        }
209
210                        // On gère le cas @plugin:non_plugin@
211                        preg_match_all('#@plugin:(.+)@#U', $condition, $matches);
212                        foreach ($matches[1] as $plug) {
213                                if (defined('_DIR_PLUGIN_'.strtoupper($plug))) {
214                                        $condition = preg_replace('#@plugin:'.$plug.'@#U', 'true', $condition);
215                                } else {
216                                        $condition = preg_replace('#@plugin:'.$plug.'@#U', 'false', $condition);
217                                }
218                        }
219                        // On gère le cas @config:plugin:meta@ suivi d'un test
220                        preg_match_all('#@config:(.+):(.+)@#U', $condition, $matches);
221                        foreach ($matches[1] as $plugin) {
222                                $config = lire_config($plugin);
223                                $condition = preg_replace('#@config:'.$plugin.':'.$matches[2][0].'@#U', '"'.$config[$matches[2][0]].'"', $condition);
224                        }
225                        // On transforme en une condition PHP valide
226                        $ok = saisies_evaluer_afficher_si($condition, $env);
227                        if (!$ok) {
228                                if ($remplissage_uniquement == false or is_null($env)) {
229                                        unset($saisies[$cle]);
230                                }
231                                if (is_null($env)) {
232                                        if ($saisie['saisie'] == 'explication') {
233                                                unset($saisies[$cle]);
234                                        } else {
235                                                saisies_set_request_null_recursivement($saisie);
236                                        }
237                                }
238                        }
239                }
240                if (isset($saisies[$cle]['saisies'])) {
241                        // S'il s'agit d'un fieldset ou equivalent, verifier les sous-saisies
242                        $saisies[$cle]['saisies'] = saisies_verifier_afficher_si($saisies[$cle]['saisies'], $env);
243                }
244        }
245        return $saisies;
246}
247
248
249
250/**
251 * Pose un set_request null sur une saisie et toute ses sous-saisies.
252 * Utiliser notamment pour annuler toutes les sous saisies d'un fieldeset
253 * si le fieldset est masquée à cause d'un afficher_si.
254 * @param array $saisie
255**/
256function saisies_set_request_null_recursivement($saisie) {
257        set_request($saisie['options']['nom'], null);
258        if (isset($saisie['saisies'])) {
259                foreach ($saisie['saisies'] as $sous_saisie) {
260                        saisies_set_request_null_recursivement($sous_saisie);
261                }
262        }
263}
264
265/**
266 * Vérifie qu'on ne tente pas de faire executer du code PHP en utilisant afficher_si.
267 * N'importe quoi autorisé entre @@ et "" et ''
268 * Liste de mot clé autorisé en dehors
269 * @param string $condition
270 * @return bool true si usage légitime, false si tentative d'execution de code PHP
271 */
272function saisies_verifier_securite_afficher_si($condition) {
273        $autoriser_hors_guillemets = array("!", "IN", "\(", "\)", "=", "\s", "&&", "\|\|");
274        $autoriser_hors_guillemets = "#(".implode($autoriser_hors_guillemets, "|").")#m";
275
276        $entre_guillemets = "#(?<guillemet>(^\\\)?(\"|'|@))(.*)(\k<guillemet>)#mU"; // trouver tout ce qu'il y entre guillemet, sauf si les guillemets sont échapés
277        $condition = preg_replace($entre_guillemets, "", $condition);//Supprimer tout ce qu'il y a entre guillement
278        $condition = preg_replace($autoriser_hors_guillemets, "", $condition);//Supprimer tout ce qui est autorisé hors guillemets
279        if ($condition) {//S'il restre quelque chose, c'est pas normal
280                return false;
281        }
282        //Sinon c'est que c'est bon
283        return true;
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        if ($operateur == "==" or $operateur == "IN") {
400                return in_array($valeur, $champ);
401        } else {
402                return !in_array($valeur, $champ);
403        }
404        return false;
405}
406
407/**
408 * Evalue un afficher_si
409 * @param string $condition (déjà checkée en terme de sécurité)
410 * @param array|null $env
411 *   Tableau d'environnement transmis dans inclure/voir_saisies.html,
412 *   NULL si on doit rechercher dans _request (pour saisies_verifier()).
413 * @return bool le résultat du test
414**/
415function saisies_evaluer_afficher_si($condition, $env = null) {
416        $condition = saisies_transformer_condition_afficher_si($condition, $env);
417        eval('$ok = '.$condition.';');
418        return $ok;
419}
Note: See TracBrowser for help on using the repository browser.