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.
319 lines
10 KiB
319 lines
10 KiB
<?php |
|
|
|
/** |
|
* Gestion de l'affichage conditionnelle des saisies. |
|
* Partie commun js/php |
|
* |
|
* @package SPIP\Saisies\Afficher_si_commun |
|
**/ |
|
|
|
|
|
/** |
|
* Reçoit une condition |
|
* la parse pour trouver champs/opérateurs/valeurs etc. |
|
* @param string $condition |
|
* @param null|string $no_arobase, permet de ne pas parser le arobase |
|
* @return array tableau d'analyse (resultat d'un preg_match_all) montrant sous condition par sous condition l'analyse en champ/opérateur/valeur etc. |
|
**/ |
|
function saisies_parser_condition_afficher_si($condition, $no_arobase=null) { |
|
static $cache = array( |
|
'no_arobase' => array(), |
|
'arobase' => array() |
|
); |
|
if ($no_arobase !== null) { |
|
$cache_ici = &$cache['no_arobase']; |
|
$no_arobase = '?'; |
|
} else { |
|
$no_arobase = ''; |
|
$cache_ici = &$cache['arobase']; |
|
} |
|
if (isset($cache_ici[$condition])) { |
|
return $cache_ici[$condition]; |
|
} |
|
|
|
$condition = preg_replace('#!\d* IN#', '!IN', $condition); // Un peu de souplesse, autoriser ! IN |
|
|
|
$regexp = |
|
'(?<negation>!?)' // négation éventuelle |
|
. "((?:(?<arobase>@)(?<champ>.+?)(\k<arobase>)))$no_arobase" // @champ_@, optionnel (formidable_ts) |
|
. '(?<total>:TOTAL)?' // TOTAL éventuel (pour les champs de type case à cocher) |
|
. '(' // partie operateur + valeur (optionnelle) : debut |
|
. '(?:\s*?)' // espaces éventuels après |
|
. '(?<operateur>==|!=|IN|!IN|>=|>|<=|<|MATCH|!MATCH)' // 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 |
|
. '|(?<booleen>false|true)';//accepter false/true brut |
|
$regexp = "#$regexp#"; |
|
|
|
preg_match_all($regexp, $condition, $tests, PREG_SET_ORDER); |
|
$tests = array_map('saisies_afficher_si_filtrer_parse_condition', $tests); |
|
|
|
$cache_ici[$condition] = $tests; |
|
|
|
return $tests; |
|
} |
|
|
|
/** |
|
* Filtrer le retour d'un parsage d'un test d'afficher_si, |
|
* pour ne pas garder des infos qui ne servent pas par là suite. |
|
* IE : si la REGEXP était optimale, on n'aurai pas besoin de cette fonction |
|
* Note : on garde les fonctions entrées vides, car parfois besoin de distinguer vide de null |
|
* @param array $parse |
|
* @return array $parse |
|
**/ |
|
function saisies_afficher_si_filtrer_parse_condition($parse) { |
|
// Pour des raisons de regexp distingue valeur et valeur numerique, mais pas besoin ici |
|
// Supprimer certaines choses dont on n'a pas besoin |
|
$parse['expression'] = $parse[0]; |
|
unset($parse['arobase']); |
|
unset($parse['guillemet']); |
|
foreach ($parse as $cle => $valeur) { |
|
if (is_int($cle)) { |
|
unset($parse[$cle]); |
|
} |
|
} |
|
if (isset($parse['valeur_numerique']) and $parse['valeur_numerique'] !== '') { |
|
$parse['valeur'] = $parse['valeur_numerique']; |
|
unset($parse['valeur_numerique']); |
|
} |
|
return $parse; |
|
} |
|
|
|
/** |
|
* Retourne le résultat de l'évaluation d'un plugin actif |
|
* @param string $champ (sans les @@) |
|
* @param string $negation ! si on doit nier |
|
* @return bool '' ('' si jamais on ne teste pas un plugin) |
|
**/ |
|
function saisies_afficher_si_evaluer_plugin($champ, $negation = '') { |
|
if (preg_match_all('#plugin:(.*)#', $champ, $matches, PREG_SET_ORDER)) { |
|
foreach ($matches as $plugin) { |
|
$plugin_a_tester = $plugin[1]; |
|
if ($negation) { |
|
$actif = !test_plugin_actif($plugin_a_tester); |
|
} else { |
|
$actif = test_plugin_actif($plugin_a_tester); |
|
} |
|
} |
|
} else { |
|
$actif = ''; |
|
} |
|
return $actif; |
|
} |
|
|
|
|
|
/** |
|
* Teste une condition d'afficher_si |
|
* @param string|array $valeur_champ la valeur du champ à tester. Cela peut être : |
|
* - un string |
|
* - un tableau |
|
* - un tableau sérializé |
|
* @param string $total TOTAL si on demande de faire le décompte dans un tableau |
|
* @param string $operateur : l'opérateur |
|
* @param string $valeur la valeur à tester |
|
* @param string $negation y-a-t-il un négation avant le test ? '!' si oui |
|
* @return bool false / true selon la condition |
|
**/ |
|
function saisies_tester_condition_afficher_si($valeur_champ, $total, $operateur='', $valeur='', $negation = '') { |
|
// Si pas operateur ni de valeur on test juste qu'un champ est cochée / validé |
|
if (!$operateur and !$valeur) { |
|
if ($negation) { |
|
return !(isset($valeur_champ) and $valeur_champ); |
|
} |
|
else { |
|
return isset($valeur_champ) and $valeur_champ; |
|
} |
|
} |
|
|
|
if (is_null($valeur_champ)) { |
|
$valeur_champ = ''; |
|
} |
|
//Si champ est de type string, tenter d'unserializer |
|
if (!is_array($valeur_champ)) { |
|
$tenter_unserialize = @unserialize($valeur_champ); |
|
if ($tenter_unserialize) { |
|
$valeur_champ = $tenter_unserialize; |
|
} |
|
} |
|
// Transformation en tableau des valeurs et valeur_champ, si IN/!IN |
|
if ($operateur === 'IN' or $operateur === '!IN') { |
|
if (!is_array($valeur_champ)) { |
|
if ($valeur_champ) { |
|
$valeur_champ = array($valeur_champ); |
|
} else { |
|
$valeur_champ = array(); |
|
} |
|
} |
|
} |
|
|
|
//Et maintenant appeler les sous fonctions qui vont bien |
|
if (is_string($valeur_champ)) { |
|
$retour = saisies_tester_condition_afficher_si_string($valeur_champ, $operateur, $valeur); |
|
} elseif (is_array($valeur_champ)) { |
|
$retour = saisies_tester_condition_afficher_si_array($valeur_champ, $total, $operateur, $valeur); |
|
} else { |
|
$retour = false; |
|
} |
|
if ($negation) { |
|
return !$retour; |
|
} else { |
|
return $retour; |
|
} |
|
} |
|
|
|
/** |
|
* Teste un condition d'afficher_si lorsque la valeur envoyée par le formulaire est une chaîne |
|
* @param string $valeur_champ la valeur du champ à tester. |
|
* @param string $operateur : l'opérateur: |
|
* @param string|int $valeur la valeur à tester. |
|
* @return bool false / true selon la condition |
|
**/ |
|
function saisies_tester_condition_afficher_si_string($valeur_champ, $operateur, $valeur) { |
|
if ($operateur === '==') { |
|
return $valeur_champ == $valeur; |
|
} elseif ($operateur === '!=') { |
|
return $valeur_champ != $valeur; |
|
} elseif ($operateur === '<') { |
|
return $valeur_champ < $valeur; |
|
} elseif ($operateur === '<=') { |
|
return $valeur_champ <= $valeur; |
|
} elseif ($operateur === '>') { |
|
return $valeur_champ > $valeur; |
|
} elseif ($operateur === '>=') { |
|
return $valeur_champ >= $valeur; |
|
} elseif ($operateur === 'MATCH') { |
|
return preg_match($valeur, $valeur_champ); |
|
} elseif ($operateur === '!MATCH') { |
|
return !preg_match($valeur, $valeur_champ); |
|
} else {//Si mauvaise operateur -> on annule |
|
return false; |
|
} |
|
} |
|
|
|
/** |
|
* Teste un condition d'afficher_si lorsque la valeur postée est un tableau |
|
* @param array $valeur_champ la valeur du 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($valeur_champ, $total, $operateur, $valeur) { |
|
if ($total) {//Cas 1 : on demande à compter le nombre total de champ |
|
return saisies_tester_condition_afficher_si_string(count($valeur_champ), $operateur, $valeur); |
|
} else {//Cas deux : on test une valeur |
|
$valeur = explode(',', $valeur); |
|
$intersection = array_intersect($valeur_champ, $valeur); |
|
if ($operateur === '==' or $operateur === 'IN') { |
|
return count($intersection) > 0; |
|
} else { |
|
return count($intersection) == 0; |
|
} |
|
return false; |
|
} |
|
} |
|
|
|
/** |
|
* Retourne la valeur d'une config, si nécessaire |
|
* @param string $champ le "champ" a tester : config:xxx ou un vrai champ |
|
* @return string |
|
**/ |
|
function saisies_afficher_si_get_valeur_config($champ) { |
|
$valeur = ''; |
|
if (preg_match("#config:(.*)#", $champ, $config)) { |
|
$config_a_tester = str_replace(":", "/", $config[1]); |
|
$valeur = lire_config($config_a_tester); |
|
} |
|
return $valeur; |
|
} |
|
|
|
/** |
|
* 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=array()) { |
|
$condition_original = $condition; |
|
$hors_test = array('||','&&','!','(',')','true','false'); |
|
foreach ($tests as $test) { |
|
$condition = str_replace($test['expression'], '', $condition); |
|
} |
|
foreach ($hors_test as $hors) { |
|
$condition = str_replace($hors, '', $condition); |
|
} |
|
$condition = trim($condition); |
|
if ($condition) {// il reste quelque chose > c'est le mal |
|
spip_log("Afficher_si incorrect. $condition_original non sécurisée; il reste une partie non parsée : $condition", "saisies"._LOG_CRITIQUE); |
|
return false; |
|
} else { |
|
return true; |
|
} |
|
} |
|
|
|
/** |
|
* Vérifie qu'une condition respecte la syntaxe formelle |
|
* @param string $condition |
|
* @param array $tests liste des tests simples |
|
* @return bool |
|
**/ |
|
function saisies_afficher_si_verifier_syntaxe($condition, $tests=array()) { |
|
if ($tests and saisies_afficher_si_secure($condition, $tests)) {//Si cela passe la sécurité, faisons des tests complémentaires |
|
// parenthèses équilibrées |
|
if (substr_count($condition,'(') != substr_count($condition,')')) { |
|
return false; |
|
} |
|
// pas de && ou de || qui traine sans rien à gauche ni à droite |
|
$condition = " $condition "; |
|
$condition_pour_sous_test = str_replace('||','$', $condition); |
|
$condition_pour_sous_test = str_replace('&&','$', $condition_pour_sous_test); |
|
$liste_sous_tests = explode('$', $condition_pour_sous_test); |
|
$liste_sous_tests = array_map('trim', $liste_sous_tests); |
|
if ($liste_sous_tests != array_filter($liste_sous_tests)) { |
|
return false; |
|
} |
|
|
|
//Vérifier la syntaxe regexp en cas de MATCH |
|
foreach ($tests as $test) { |
|
if( |
|
isset($test['operateur']) |
|
and ($test['operateur'] === 'MATCH' or $test['operateur'] === '!MATCH') |
|
) |
|
if (!$regexp = afficher_si_parser_valeur_MATCH($test['valeur']) or !$regexp['regexp']) { |
|
$expression = $test['expression']; |
|
spip_log("Afficher_si incorrect. $expression incluant MATCH, mais valeur à tester non valide (manque // ?)", "saisies"._LOG_CRITIQUE); |
|
return false; |
|
} |
|
} |
|
// Tout va bien ! |
|
return true; |
|
} |
|
// Pas sécure, on refuse |
|
return false; |
|
|
|
} |
|
|
|
/** |
|
* Décompose une chaine |
|
*'/regex/modificateur' |
|
* @param string $valeur |
|
* @return array( |
|
* 'regexp' => string, |
|
* 'modificateur' => string |
|
* ) |
|
**/ |
|
function afficher_si_parser_valeur_MATCH($valeur) { |
|
preg_match('#^\/(.*)\/(.*)$#', $valeur, $m); |
|
if($m) { |
|
return array( |
|
'regexp' => $m[1], |
|
'regexp_modif' => $m[2] |
|
); |
|
} else { |
|
return array( |
|
'regexp' => '', |
|
'regexp_modif' => '' |
|
); |
|
} |
|
}
|
|
|