You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

467 lines
17 KiB

<?php
/**
* Gestion de l'affichage conditionnelle des saisies
*
* @package SPIP\Saisies\Afficher_si
**/
// Sécurité
if (!defined('_ECRIRE_INC_VERSION')) {
return;
}
/**
* Génère, à partir d'un tableau de saisie le code javascript ajouté à la fin de #GENERER_SAISIES
* pour produire un affichage conditionnel des saisies ayant une option afficher_si
*
* @param array $saisies
* Tableau de descriptions des saisies
* @param string $id_form
* Identifiant unique pour le formulaire
*
* @return text
* Code javascript
*/
function saisies_generer_js_afficher_si($saisies, $id_form) {
$i = 0;
$saisies = saisies_lister_par_nom($saisies, true);
$code = '';
$code .= "$(function(){\n\tchargement=true;\n";
$code .= "\tverifier_saisies_".$id_form." = function(form){\n";
if (!defined('_SAISIES_AFFICHER_SI_JS_SHOW')) {
define ('_SAISIES_AFFICHER_SI_JS_SHOW', 'show(400)');
}
if (!defined('_SAISIES_AFFICHER_SI_JS_HIDE')) {
define ('_SAISIES_AFFICHER_SI_JS_HIDE', 'hide(400)');
}
foreach ($saisies as $saisie) {
// on utilise comme selecteur l'identifiant de saisie en priorite s'il est connu
// parce que conteneur_class = 'tableau[nom][option]' ne fonctionne evidement pas
// lorsque le name est un tableau
if (isset($saisie['options']['afficher_si']) && trim($saisie['options']['afficher_si'])) {
++$i;
// Les [] dans le nom de la saisie sont transformés en _ dans le
// nom de la classe, il faut faire pareil
$nom_underscore = rtrim(
preg_replace('/[][]\[?/', '_', $saisie['options']['nom']),
'_'
);
// retrouver la classe css probable
switch ($saisie['saisie']) {
case 'fieldset':
$class_li = 'fieldset_'.$nom_underscore;
break;
case 'explication':
$class_li = 'explication_'.$nom_underscore;
break;
default:
// Les [] dans le nom de la saisie sont transformés en _ dans le
// nom de la classe, il faut faire pareil
$class_li = 'editer_'.$nom_underscore;
}
$condition = trim($saisie['options']['afficher_si']);
// retrouver l'identifiant
$identifiant = '';
if (isset($saisie['identifiant']) and $saisie['identifiant']) {
$identifiant = $saisie['identifiant'];
}
// On gère le cas @plugin:non_plugin@
preg_match_all('#@plugin:(.+)@#U', $condition, $matches);
foreach ($matches[1] as $plug) {
if (defined('_DIR_PLUGIN_'.strtoupper($plug))) {
$condition = preg_replace('#@plugin:'.$plug.'@#U', 'true', $condition);
} else {
$condition = preg_replace('#@plugin:'.$plug.'@#U', 'false', $condition);
}
}
$condition = saisies_transformer_condition_afficher_si_config($condition);
// On transforme en une condition valide
preg_match_all('#@(.+)@#U', $condition, $matches);
foreach ($matches[1] as $nom) {
switch ($saisies[$nom]['saisie']) {
case 'radio':
case 'oui_non':
case 'true_false':
$condition = preg_replace('#@'.preg_quote($nom).'@#U', '$(form).find("[name=\''.$nom.'\']:checked").val()', $condition);
break;
case 'case':
$condition = preg_replace('#@'.preg_quote($nom).'@#U', '($(form).find(".checkbox[name=\''.$nom.'\']").is(":checked") ? $(form).find(".checkbox[name=\''.$nom.'\']").val() : "")', $condition);
break;
case 'checkbox':
/**
* Faire fonctionner @checkbox_xx@ == 'valeur' et @checkbox_xx@ != 'valeur'
*/
$condition = preg_replace('#@(.+)@\s*(==|(!)=)\s*(\'[^\']*\'|"[^"]*")#U', "@$1@ $3IN $4", $condition );
/**
* Faire fonctionner @checkbox_xx@ IN 'valeur' ou @checkbox_xx@ !IN 'valeur'
*/
preg_match_all('#@(.+)@\s*(!IN|IN)\s*[\'"](.*)[\'"]#U', $condition, $matches3);
foreach ($matches3[3] as $key => $value) {
$not = '';
if ($matches3[2][$key] == '!IN') {
$not = '!';
}
$values = explode(',', $value);
$new_condition = $not.'(';
foreach ($values as $key2 => $cond) {
if ($key2 > 0) {
$new_condition .= ' || ';
}
$new_condition .= '($(form).find(".checkbox[name=\''.$nom.'[]\'][value='.$cond.']").is(":checked") ? $(form).find(".checkbox[name=\''.$nom.'[]\'][value='.$cond.']").val() : "") == "'.$cond.'"';
}
$new_condition .= ')';
$condition = str_replace($matches3[0][$key], $new_condition, $condition);
}
break;
default:
$condition = preg_replace('#@'.preg_quote($nom).'@#U', '$(form).find("[name=\''.$nom.'\']").val()', $condition);
}
}
if ($identifiant) {
$sel = "[data-id='$identifiant']";
} else {
$sel = ".$class_li";
}
$code .= "\tif (".$condition.") {\n"
. "\t\t$(form).find(\"$sel\")."._SAISIES_AFFICHER_SI_JS_SHOW.".addClass('afficher_si_visible').removeClass('afficher_si_masque');\n";
if (html5_permis()) {
$code .= "\t\t$(form).find(\"$sel [data-afficher-si-required]\").attr(\"required\",true).attr(\"data-afficher-si-required\",null);\n";
}
$code .= "\t}\n";
$code .= "\telse {\n";
if (html5_permis()) {
$code .= "\t\t$(form).find(\"$sel [required]\").attr(\"required\",false).attr(\"data-afficher-si-required\",true);\n";
}
$code .= "\t\tif (chargement==true) {\n"
."\t\t\t$(form).find(\"$sel\")."._SAISIES_AFFICHER_SI_JS_HIDE.".addClass('afficher_si_masque').removeClass('afficher_si_visible').css".'("display","none")'.";\n"
."\t\t}\n"
."\t\telse {\n"
."\t\t\t$(form).find(\"$sel\")."._SAISIES_AFFICHER_SI_JS_HIDE.".addClass('afficher_si_masque').removeClass('afficher_si_visible');\n"
."\t\t};\n"
."\t}\n";
}
}
$code .= "$(form).trigger('saisies_afficher_si_js_ok');\n";
$code .= "};\n";
$code .= "\t".'$("#afficher_si_'.$id_form.'").parents("form").each(function(){'."\n\t\t".'verifier_saisies_'.$id_form.'(this);});'."\n";
$code .= "\t".'$("#afficher_si_'.$id_form.'").parents("form").change(function(){'."\n\t\t".'verifier_saisies_'.$id_form.'(this);});'."\n";
$code .= "\tchargement=false;})\n";
if (!defined('_SAISIES_AFFICHER_SI_JS_LISIBLE')) {
define('_SAISIES_AFFICHER_SI_JS_LISIBLE', false);
}
if (!_SAISIES_AFFICHER_SI_JS_LISIBLE) {
// il suffit de régler cette constante à TRUE pour afficher le js de manière plus lisible (et moins sibyllin)
$code = str_replace("\n", '', $code); //concatener
$code = str_replace("\t", '', $code); //concatener
}
return $i > 0 ? $code : '';
}
/**
* Lorsque l'on affiche les saisies (#VOIR_SAISIES), les saisies ayant une option afficher_si
* et dont les conditions ne sont pas remplies doivent être retirées du tableau de saisies.
*
* Cette fonction sert aussi lors de la vérification des saisies avec saisies_verifier().
* À ce moment là, les saisies non affichées sont retirées de _request
* (on passe leur valeur à NULL).
*
* @param array $saisies
* Tableau de descriptions de saisies
* @param array|null $env
* Tableau d'environnement transmis dans inclure/voir_saisies.html,
* NULL si on doit rechercher dans _request (pour saisies_verifier()).
*
* @return array
* Tableau de descriptions de saisies
*/
function saisies_verifier_afficher_si($saisies, $env = null) {
// eviter une erreur par maladresse d'appel :)
if (!is_array($saisies)) {
return array();
}
// Economiser un peu de calcul, notamment pour formidable
static $precedent_saisies = array();
static $precedent_env = array();
if ($precedent_saisies == $saisies and $precedent_env == $env) {
return $saisies;
}
$precedent_saisies = $saisies;
$precedent_env = $env;
foreach ($saisies as $cle => $saisie) {
if (isset($saisie['options']['afficher_si'])) {
$condition = $saisie['options']['afficher_si'];
// Est-ce uniquement au remplissage?
if (isset($saisie['options']['afficher_si_remplissage_uniquement'])
and $saisie['options']['afficher_si_remplissage_uniquement']=='on'){
$remplissage_uniquement = true;
} else {
$remplissage_uniquement = false;
}
4 years ago
// On gère le cas @plugin:nom_plugin@
preg_match_all('#@plugin:(.+)@#U', $condition, $matches);
foreach ($matches[1] as $plug) {
if (defined('_DIR_PLUGIN_'.strtoupper($plug))) {
$condition = preg_replace('#@plugin:'.$plug.'@#U', 'true', $condition);
} else {
$condition = preg_replace('#@plugin:'.$plug.'@#U', 'false', $condition);
}
}
// On transforme en une condition PHP valide
$ok = saisies_evaluer_afficher_si($condition, $env);
if (!$ok) {
if ($remplissage_uniquement == false or is_null($env)) {
unset($saisies[$cle]);
}
if (is_null($env)) {
if ($saisie['saisie'] == 'explication') {
unset($saisies[$cle]);
} else {
saisies_set_request_null_recursivement($saisie);
}
}
}
}
if (isset($saisies[$cle]['saisies'])) {
// S'il s'agit d'un fieldset ou equivalent, verifier les sous-saisies
$saisies[$cle]['saisies'] = saisies_verifier_afficher_si($saisies[$cle]['saisies'], $env);
}
}
return $saisies;
}
/**
* Pose un set_request null sur une saisie et toute ses sous-saisies.
* Utiliser notamment pour annuler toutes les sous saisies d'un fieldeset
* si le fieldset est masquée à cause d'un afficher_si.
* @param array $saisie
**/
function saisies_set_request_null_recursivement($saisie) {
set_request($saisie['options']['nom'], null);
if (isset($saisie['saisies'])) {
foreach ($saisie['saisies'] as $sous_saisie) {
saisies_set_request_null_recursivement($sous_saisie);
}
}
}
/**
* Récupère la valeur d'un champ à tester avec afficher_si
* Si le champ est de type @config:xx@, alors prend la valeur de la config
* sinon en _request() ou en $env["valeurs"]
* @param string $champ: le champ
* @param null|array $env
* @return la valeur du champ ou de la config
**/
function saisies_afficher_si_get_valeur_champ($champ, $env) {
if (preg_match_all("#config:(.*)#", $champ, $matches, PREG_SET_ORDER)) {
foreach ($matches as $plugin) {
$arobase = $plugin[0];
$config_a_tester = str_replace(":", "/", $plugin[1]);
$champ = lire_config($config_a_tester);
}
} elseif (is_null($env)) {
$champ = _request($champ);
} else {
$champ = $env["valeurs"][$champ];
}
return $champ;
}
/**
* Prend un test conditionnel
* cherche dedans les test @config:xxx@
* remplace par la valeur de la config
* @param string condition;
* @return string condition;
**/
function saisies_transformer_condition_afficher_si_config($condition) {
include_spip("inc/config");
preg_match_all('#@config:(.*)@#U', $condition, $matches, PREG_SET_ORDER);
foreach ($matches as $plugin) {
$arobase = $plugin[0];
$config_a_tester = str_replace(":", "/", $plugin[1]);
$config = lire_config($config_a_tester);
$condition = str_replace($arobase, '"'.$config.'"', $condition);
}
return $condition;
}
/**
* Prend un test conditionnel,
* le sépare en une série de sous-tests de type champ - operateur - valeur
* remplace chacun de ces sous-tests par son résultat
* renvoie la chaine transformé
* @param string $condition
* @param array|null $env
* Tableau d'environnement transmis dans inclure/voir_saisies.html,
* NULL si on doit rechercher dans _request (pour saisies_verifier()).
* @return string $condition
**/
function saisies_transformer_condition_afficher_si($condition, $env = null) {
$regexp =
"(?<negation>!?)" // négation éventuelle
. "(?:@(?<champ>.+?)@)" // @champ_@
. "(" // partie operateur + valeur (optionnelle) : debut
. "(?:\s*?)" // espaces éventuels après
. "(?<operateur>==|!=|IN|!IN|>=|>|<=|<)" // opérateur
. "(?:\s*?)" // espaces éventuels après
. "((?<guillemet>\"|')(?<valeur>.*?)(\k<guillemet>)|(?<valeur_numerique>\d+))" // valeur (string) ou valeur_numérique (int)
. ")?"; // partie operateur + valeur (optionnelle) : fin
$regexp = "#$regexp#";
if (preg_match_all($regexp, $condition, $tests, PREG_SET_ORDER)) {
if (!saisies_afficher_si_secure($condition, $tests)) {
spip_log("Afficher_si incorrect. $condition non sécurisée", "saisies"._LOG_CRITIQUE);
return '';
}
foreach ($tests as $test) {
$expression = $test[0];
$champ = saisies_afficher_si_get_valeur_champ($test['champ'], $env);
$operateur = isset($test['operateur']) ? $test['operateur'] : null;
if (isset($test['valeur_numerique'])) {
$valeur = intval($test['valeur_numerique']);
} elseif (isset($test['valeur'])) {
$valeur = $test['valeur'];
} else {
$valeur = null;
}
$test_modifie = saisies_tester_condition_afficher_si($champ, $operateur, $valeur) ? 'true' : 'false';
if (isset($test['negation'])) {
$test_modifie = $test['negation'].$test_modifie;
}
$condition = str_replace($expression, $test_modifie, $condition);
}
} else {
$condition = str_replace(' ', '', $condition);
$condition_possible = array("!false", "false", "true", "!true");
if (!in_array($condition, $condition_possible)){
spip_log("Afficher_si incorrect : $condition", "saisies"._LOG_CRITIQUE);
$condition = true;
}
}
return $condition;
}
/** Vérifie qu'une condition est sécurisée
* IE : ne permet pas d'executer n'importe quel code arbitraire.
* @param string $condition
* @param array $tests tableau des tests parsés
* @return bool true si secure / false sinon
**/
function saisies_afficher_si_secure($condition, $tests) {
$hors_test = array('||','&&','!','(',')');
foreach ($tests as $test) {
$condition = str_replace($test[0], '', $condition);
}
foreach ($hors_test as $hors) {
$condition = str_replace($hors, '', $condition);
}
$condition = trim($condition);
if ($condition) {// il reste quelque chose > c'est le mal
return false;
} else {
return true;
}
}
/**
* Teste une condition d'afficher_si
* @param string|array champ le champ à tester. Cela peut être :
* - un string
* - un tableau
* - un tableau sérializé
* @param string $operateur : l'opérateur:
* @param string $valeur la valeur à tester pour un IN. Par exemple "23" ou encore "23", "25"
4 years ago
* @return bool false / true selon la condition
**/
function saisies_tester_condition_afficher_si($champ, $operateur=null, $valeur=null) {
// Si operateur null => on test juste qu'un champ est cochée / validé
if ($operateur === null and $valeur === null) {
return isset($champ) and $champ;
}
// Dans tous les cas, enlever les guillemets qui sont au sein de valeur
//Si champ est de type string, tenter d'unserializer
$tenter_unserialize = @unserialize($champ);
if ($tenter_unserialize) {
$champ = $tenter_unserialize;
}
//Et maintenant appeler les sous fonctions qui vont bien
if (is_string($champ)) {
return saisies_tester_condition_afficher_si_string($champ, $operateur, $valeur);
4 years ago
} elseif (is_array($champ)) {
return saisies_tester_condition_afficher_si_array($champ, $operateur, $valeur);
} else {
return false;
}
}
4 years ago
/**
* Teste un condition d'afficher_si lorsque la valeur envoyée par le formulaire est une chaîne
4 years ago
* @param string champ le champ à tester.
* @param string $operateur : l'opérateur:
* @param string|int $valeur la valeur à tester pour un IN. Par exemple "23" ou encore "23, 25", 23
4 years ago
* @return bool false / true selon la condition
**/
function saisies_tester_condition_afficher_si_string($champ, $operateur, $valeur) {
if ($operateur == "==") {
return $champ == $valeur;
} elseif ($operateur == "!=") {
return $champ != $valeur;
} elseif ($operateur == '<') {
return $champ < $valeur;
} elseif ($operateur == '<=') {
return $champ <= $valeur;
} elseif ($operateur == '>') {
return $champ > $valeur;
} elseif ($operateur == '>=') {
return $champ >= $valeur;
} else {//Si mauvaise operateur -> on annule
return false;
}
}
4 years ago
/**
* Teste un condition d'afficher_si lorsque la valeur postée est un tableau
4 years ago
* @param array champ le champ à tester.
* @param string $operateur : l'opérateur:
* @param string $valeur la valeur à tester pour un IN. Par exemple "23" ou encore "23", "25"
* @return bool false / true selon la condition
**/
function saisies_tester_condition_afficher_si_array($champ, $operateur, $valeur) {
$valeur = explode(',', $valeur);
$intersection = array_intersect($champ, $valeur);
if ($operateur == "==" or $operateur == "IN") {
return count($intersection) > 0;
} else {
return count($intersection) == 0;
}
return false;
}
/**
* Evalue un afficher_si
* @param string $condition (déjà checkée en terme de sécurité)
* @param array|null $env
* Tableau d'environnement transmis dans inclure/voir_saisies.html,
* NULL si on doit rechercher dans _request (pour saisies_verifier()).
* @return bool le résultat du test
**/
function saisies_evaluer_afficher_si($condition, $env = null) {
$condition = saisies_transformer_condition_afficher_si($condition, $env);
if ($condition) {
eval('$ok = '.$condition.';');
} else {
$ok = true;
}
return $ok;
}