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 = '(?!?)' // négation éventuelle . "((?:(?@)(?.+?)(\k)))$no_arobase" // @champ_@, optionnel (formidable_ts) . '(?:TOTAL)?' // TOTAL éventuel (pour les champs de type case à cocher) . '(' // partie operateur + valeur (optionnelle) : debut . '(?:\s*?)' // espaces éventuels après . '(?==|!=|IN|!IN|>=|>|<=|<|MATCH|!MATCH)' // opérateur . '(?:\s*?)' // espaces éventuels après . '((?"|\')(?.*?)(\k)|(?[+-]?\d+))' // valeur (string) ou valeur_numérique (int) . ')?' // partie operateur + valeur (optionnelle) : fin . '|(?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' => '' ); } }