You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
320 lines
10 KiB
PHP
320 lines
10 KiB
PHP
<?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' => ''
|
|
);
|
|
}
|
|
}
|