3 /***************************************************************************\
4 * SPIP, Systeme de publication pour l'internet *
6 * Copyright (c) 2001-2017 *
7 * Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James *
9 * Ce programme est un logiciel libre distribue sous licence GNU/GPL. *
10 * Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne. *
11 \***************************************************************************/
14 * Compose un squelette : compile le squelette au besoin et vérifie
15 * la validité du code compilé
17 * @package SPIP\Core\Compilateur\Composer
20 if (!defined('_ECRIRE_INC_VERSION')) {
24 include_spip('inc/texte');
25 include_spip('inc/documents');
26 include_spip('inc/distant');
27 include_spip('inc/rubriques'); # pour calcul_branche (cf critere branche)
28 include_spip('inc/acces'); // Gestion des acces pour ical
29 include_spip('inc/actions');
30 include_spip('public/iterateur');
31 include_spip('public/interfaces');
32 include_spip('public/quete');
34 # Charge et retourne un composeur ou '' s'il est inconnu. Le compile au besoin
35 # Charge egalement un fichier homonyme de celui du squelette
36 # mais de suffixe '_fonctions.php' pouvant contenir:
38 # 2. des fonctions de traduction de balise, de critere et de boucle
39 # 3. des declaration de tables SQL supplementaires
40 # Toutefois pour 2. et 3. preferer la technique de la surcharge
42 // http://code.spip.net/@public_composer_dist
43 function public_composer_dist($squelette, $mime_type, $gram, $source, $connect = '') {
45 $nom = calculer_nom_fonction_squel($squelette, $mime_type, $connect);
47 // si deja en memoire (INCLURE a repetition) c'est bon.
48 if (function_exists($nom)) {
52 if (defined('_VAR_MODE') and _VAR_MODE
== 'debug') {
53 $GLOBALS['debug_objets']['courant'] = $nom;
56 $phpfile = sous_repertoire(_DIR_SKELS
, '', false, true) . $nom . '.php';
58 // si squelette est deja compile et perenne, le charger
59 if (!squelette_obsolete($phpfile, $source)) {
60 include_once $phpfile;
61 #if (!squelette_obsolete($phpfile, $source)
62 # AND lire_fichier ($phpfile, $skel_code,
63 # array('critique' => 'oui', 'phpcheck' => 'oui'))){
64 ## eval('?'.'>'.$skel_code);
65 # spip_log($skel_code, 'comp')
69 if (file_exists($lib = $squelette . '_fonctions' . '.php')) {
73 // tester si le eval ci-dessus a mis le squelette en memoire
75 if (function_exists($nom)) {
79 // charger le source, si possible, et compiler
80 if (lire_fichier($source, $skel)) {
81 $compiler = charger_fonction('compiler', 'public');
82 $skel_code = $compiler($skel, $nom, $gram, $source, $connect);
85 // Ne plus rien faire si le compilateur n'a pas pu operer.
90 foreach ($skel_code as $id => $boucle) {
92 if (@eval
("return true; $f ;") === false) {
93 // Code syntaxiquement faux (critere etc mal programme')
94 $msg = _T('zbug_erreur_compilation');
95 erreur_squelette($msg, $boucle);
96 // continuer pour trouver d'autres fautes eventuelles
97 // mais prevenir que c'est mort
100 // Contexte de compil inutile a present
101 // (mais la derniere valeur de $boucle est utilisee ci-dessous)
102 $skel_code[$id] = $f;
107 // Si le code est bon, concatener et mettre en cache
108 if (function_exists($nom)) {
109 $code = squelette_traduit($skel, $source, $phpfile, $skel_code);
111 // code semantiquement faux: bug du compilateur
112 // $boucle est en fait ici la fct principale du squelette
113 $msg = _T('zbug_erreur_compilation');
114 erreur_squelette($msg, $boucle);
119 if (defined('_VAR_MODE') and _VAR_MODE
== 'debug') {
121 // Tracer ce qui vient d'etre compile
122 $GLOBALS['debug_objets']['code'][$nom . 'tout'] = $code;
124 // si c'est ce que demande le debusqueur, lui passer la main
125 if ($GLOBALS['debug_objets']['sourcefile']
126 and (_request('var_mode_objet') == $nom)
127 and (_request('var_mode_affiche') == 'code')
133 return $nom ?
$nom : false;
136 function squelette_traduit($squelette, $sourcefile, $phpfile, $boucles) {
138 // Le dernier index est '' (fonction principale)
139 $noms = substr(join(', ', array_keys($boucles)), 0, -2);
143 * Squelette : $sourcefile
144 * Date : " . gmdate("D, d M Y H:i:s", @filemtime
($sourcefile)) . " GMT
145 * Compile : " . gmdate("D, d M Y H:i:s", time()) . " GMT
146 * " . (!$boucles ?
"Pas de boucle" : ("Boucles : " . $noms)) . "
150 $code = '<' . "?php\n" . $code . join('', $boucles) . "\n?" . '>';
151 if (!defined('_VAR_NOCACHE') or !_VAR_NOCACHE
) {
152 ecrire_fichier($phpfile, $code);
158 // Le squelette compile est-il trop vieux ?
159 // http://code.spip.net/@squelette_obsolete
160 function squelette_obsolete($skel, $squelette) {
161 static $date_change = null;
162 // ne verifier la date de mes_fonctions et mes_options qu'une seule fois
164 if (is_null($date_change)) {
165 if (@file_exists
($fonc = 'mes_fonctions.php')) {
166 $date_change = @filemtime
($fonc);
168 if (defined('_FILE_OPTIONS')) {
169 $date_change = max($date_change, @filemtime
(_FILE_OPTIONS
));
174 (defined('_VAR_MODE') and in_array(_VAR_MODE
, array('recalcul', 'preview', 'debug')))
175 or !@file_exists
($skel)
176 or ((@file_exists
($squelette) ? @filemtime
($squelette) : 0)
177 > ($date = @filemtime
($skel)))
178 or ($date_change > $date)
182 // Activer l'invalideur de session
183 // http://code.spip.net/@invalideur_session
184 function invalideur_session(&$Cache, $code = null) {
185 $Cache['session'] = spip_session();
191 // http://code.spip.net/@analyse_resultat_skel
192 function analyse_resultat_skel($nom, $cache, $corps, $source = '') {
193 static $filtres = array();
196 // Recupere les < ?php header('Xx: y'); ? > pour $page['headers']
197 // note: on essaie d'attrapper aussi certains de ces entetes codes
198 // "a la main" dans les squelettes, mais evidemment sans exhaustivite
199 if (stripos($corps, 'header') !== false
201 '/(<[?]php\s+)@?header\s*\(\s*.([^:\'"]*):?\s*([^)]*)[^)]\s*\)\s*[;]?\s*[?]>/ims',
202 $corps, $regs, PREG_SET_ORDER
)
204 foreach ($regs as $r) {
205 $corps = str_replace($r[0], '', $corps);
206 # $j = Content-Type, et pas content-TYPE.
207 $j = join('-', array_map('ucwords', explode('-', strtolower($r[2]))));
209 if ($j == 'X-Spip-Filtre' and isset($headers[$j])) {
210 $headers[$j] .= "|" . $r[3];
212 $headers[$j] = $r[3];
216 // S'agit-il d'un resultat constant ou contenant du code php
218 strpos($corps, '<' . '?') === false
220 (strpos($corps, '<' . '?xml') !== false and
221 strpos(str_replace('<' . '?xml', '', $corps), '<' . '?') === false)
229 'process_ins' => $process_ins,
230 'invalideurs' => $cache,
231 'entetes' => $headers,
232 'duree' => isset($headers['X-Spip-Cache']) ?
intval($headers['X-Spip-Cache']) : 0
235 // traiter #FILTRE{} et filtres
236 if (!isset($filtres[$nom])) {
237 $filtres[$nom] = pipeline('declarer_filtres_squelettes', array('args' => $skel, 'data' => array()));
239 if (count($filtres[$nom]) or (isset($headers['X-Spip-Filtre']) and strlen($headers['X-Spip-Filtre']))) {
240 include_spip('public/sandbox');
241 $corps = sandbox_filtrer_squelette($skel, $corps,
242 strlen($headers['X-Spip-Filtre']) ?
explode('|', $headers['X-Spip-Filtre']) : array(), $filtres[$nom]);
243 unset($headers['X-Spip-Filtre']);
245 if ($process_ins == 'html') {
246 $skel['process_ins'] = (
247 strpos($corps, '<' . '?') === false
249 (strpos($corps, '<' . '?xml') !== false and
250 strpos(str_replace('<' . '?xml', '', $corps), '<' . '?') === false)
257 $skel['entetes'] = $headers;
258 $skel['texte'] = $corps;
264 // Des fonctions diverses utilisees lors du calcul d'une page ; ces fonctions
265 // bien pratiques n'ont guere de logique organisationnelle ; elles sont
266 // appelees par certaines balises au moment du calcul des pages. (Peut-on
267 // trouver un modele de donnees qui les associe physiquement au fichier
268 // definissant leur balise ???
273 * Calcul d'une introduction
275 * L'introduction est prise dans le descriptif s'il est renseigné,
276 * sinon elle est calculée depuis le texte : à ce moment là,
277 * l'introduction est prise dans le contenu entre les balises
278 * `<intro>` et `</intro>` si présentes, sinon en coupant le
279 * texte à la taille indiquée.
281 * Cette fonction est utilisée par la balise #INTRODUCTION
283 * @param string $descriptif
284 * Descriptif de l'introduction
285 * @param string $texte
286 * Texte à utiliser en absence de descriptif
287 * @param string $longueur
288 * Longueur de l'introduction
289 * @param string $connect
290 * Nom du connecteur à la base de données
291 * @param string $suite
292 * points de suite si on coupe (par defaut _INTRODUCTION_SUITE et sinon (...)
294 * Introduction calculée
296 function filtre_introduction_dist($descriptif, $texte, $longueur, $connect, $suite = null) {
297 // Si un descriptif est envoye, on l'utilise directement
298 if (strlen($descriptif)) {
299 return appliquer_traitement_champ($descriptif, 'introduction', '', array(), $connect);
302 // De preference ce qui est marque <intro>...</intro>
304 $texte = preg_replace(",(</?)intro>,i", "\\1intro>", $texte); // minuscules
305 while ($fin = strpos($texte, "</intro>")) {
306 $zone = substr($texte, 0, $fin);
307 $texte = substr($texte, $fin +
strlen("</intro>"));
308 if ($deb = strpos($zone, "<intro>") or substr($zone, 0, 7) == "<intro>") {
309 $zone = substr($zone, $deb +
7);
314 // [12025] On ne *PEUT* pas couper simplement ici car c'est du texte brut,
315 // qui inclus raccourcis et modeles
316 // un simple <articlexx> peut etre ensuite transforme en 1000 lignes ...
317 // par ailleurs le nettoyage des raccourcis ne tient pas compte
318 // des surcharges et enrichissement de propre
319 // couper doit se faire apres propre
320 //$texte = nettoyer_raccourcis_typo($intro ? $intro : $texte, $connect);
322 // Cependant pour des questions de perfs on coupe quand meme, en prenant
323 // large et en se mefiant des tableaux #1323
325 if (strlen($intro)) {
328 if (strpos("\n" . $texte, "\n|") === false
329 and strlen($texte) > 2.5 * $longueur
331 if (strpos($texte, "<multi") !== false) {
332 $texte = extraire_multi($texte);
334 $texte = couper($texte, 2 * $longueur);
338 // ne pas tenir compte des notes
339 if ($notes = charger_fonction('notes', 'inc', true)) {
340 $notes('', 'empiler');
342 // Supprimer les modèles avant le propre afin d'éviter qu'ils n'ajoutent du texte indésirable
343 // dans l'introduction.
344 $texte = supprime_img($texte, '');
345 $texte = appliquer_traitement_champ($texte, 'introduction', '', array(), $connect);
348 $notes('', 'depiler');
351 if (is_null($suite)) {
352 $suite = (defined('_INTRODUCTION_SUITE') ? _INTRODUCTION_SUITE
: ' (...)');
354 $texte = couper($texte, $longueur, $suite);
355 // comme on a coupe il faut repasser la typo (on a perdu les insecables)
356 $texte = typo($texte, true, $connect, array());
358 // et reparagrapher si necessaire (coherence avec le cas descriptif)
359 // une introduction a tojours un <p>
360 if ($GLOBALS['toujours_paragrapher']) // Fermer les paragraphes
362 $texte = paragrapher($texte, $GLOBALS['toujours_paragrapher']);
369 // Balises dynamiques
372 /** Code PHP pour inclure une balise dynamique à l'exécution d'une page */
373 define('CODE_INCLURE_BALISE', '<' . '?php
375 if ($lang_select = "%s") $lang_select = lang_select($lang_select);
376 inserer_balise_dynamique(balise_%s_dyn(%s), array(%s));
377 if ($lang_select) lang_select();
382 * Synthétise une balise dynamique : crée l'appel à l'inclusion
383 * en transmettant les arguments calculés et le contexte de compilation.
385 * @uses argumenter_squelette() Pour calculer les arguments de l'inclusion
388 * Nom de la balise dynamique
390 * Liste des arguments calculés
391 * @param string $file
392 * Chemin du fichier de squelette à inclure
393 * @param array $context_compil
394 * Tableau d'informations sur la compilation
396 * Code PHP pour inclure le squelette de la balise dynamique
398 function synthetiser_balise_dynamique($nom, $args, $file, $context_compil) {
399 if (strncmp($file, "/", 1) !== 0) {
400 $file = './" . _DIR_RACINE . "' . $file;
402 $r = sprintf(CODE_INCLURE_BALISE
,
404 $context_compil[4] ?
$context_compil[4] : '',
406 join(', ', array_map('argumenter_squelette', $args)),
407 join(', ', array_map('_q', $context_compil)));
413 * Crée le code PHP pour transmettre des arguments (généralement pour une inclusion)
415 * @param array|string $v
416 * Arguments à transmettre :
418 * - string : un simple texte à faire écrire
419 * - array : couples ('nom' => 'valeur') liste des arguments et leur valeur
422 * - Code PHP créant le tableau des arguments à transmettre,
423 * - ou texte entre quote `'` (si `$v` était une chaîne)
425 function argumenter_squelette($v) {
428 return "'" . texte_script($v) . "'";
431 foreach ($v as $k => $val) {
432 $out [] = argumenter_squelette($k) . '=>' . argumenter_squelette($val);
435 return 'array(' . join(", ", $out) . ')';
441 * Calcule et retourne le code PHP retourné par l'exécution d'une balise
444 * Vérifier les arguments et filtres et calcule le code PHP à inclure.
446 * - charge le fichier PHP de la balise dynamique dans le répertoire
447 * `balise/`, soit du nom complet de la balise, soit d'un nom générique
448 * (comme 'formulaire_.php'). Dans ce dernier cas, le nom de la balise
449 * est ajouté en premier argument.
450 * - appelle une éventuelle fonction de traitement des arguments `balise_NOM_stat()`
451 * - crée le code PHP de la balise si une fonction `balise_NOM_dyn()` (ou variantes)
452 * est effectivement trouvée.
454 * @uses synthetiser_balise_dynamique()
455 * Pour calculer le code PHP d'inclusion produit
458 * Nom de la balise dynamique
460 * Liste des arguments calculés de la balise
461 * @param array $context_compil
462 * Tableau d'informations sur la compilation
464 * Code PHP d'exécutant l'inclusion du squelette (ou texte) de la balise dynamique
466 function executer_balise_dynamique($nom, $args, $context_compil) {
468 $nomfonction_generique = "";
470 // Calculer un nom générique (ie. 'formulaire_' dans 'formulaire_editer_article')
471 if (false !== ($p = strpos($nom, "_"))) {
472 $nomfonction_generique = substr($nom, 0, $p +
1);
475 if (!$fonction_balise = charger_fonction($nomfonction, 'balise', true)) {
476 if ($nomfonction_generique and $fonction_balise = charger_fonction($nomfonction_generique, 'balise', true)) {
477 // et injecter en premier arg le nom de la balise
478 array_unshift($args, $nom);
479 $nomfonction = $nomfonction_generique;
483 if (!$fonction_balise) {
484 $msg = array('zbug_balise_inexistante', array('from' => 'CVT', 'balise' => $nom));
485 erreur_squelette($msg, $context_compil);
490 // retrouver le fichier qui a déclaré la fonction
491 // même si la fonction dynamique est déclarée dans un fichier de fonctions.
492 // Attention sous windows, getFileName() retourne un antislash.
493 $reflector = new ReflectionFunction($fonction_balise);
494 $file = str_replace('\\', '/', $reflector->getFileName());
495 if (strncmp($file, str_replace('\\', '/', _ROOT_RACINE
), strlen(_ROOT_RACINE
)) === 0) {
496 $file = substr($file, strlen(_ROOT_RACINE
));
499 // Y a-t-il une fonction de traitement des arguments ?
500 $f = 'balise_' . $nomfonction . '_stat';
502 $r = !function_exists($f) ?
$args : $f($args, $context_compil);
508 // verifier que la fonction dyn est la,
509 // sinon se replier sur la generique si elle existe
510 if (!function_exists('balise_' . $nomfonction . '_dyn')) {
511 if ($nomfonction_generique
512 and $file = include_spip("balise/" . strtolower($nomfonction_generique))
513 and function_exists('balise_' . $nomfonction_generique . '_dyn')
515 // et lui injecter en premier arg le nom de la balise
516 array_unshift($r, $nom);
517 $nomfonction = $nomfonction_generique;
518 if (!_DIR_RESTREINT
) {
519 $file = _DIR_RESTREINT_ABS
. $file;
522 $msg = array('zbug_balise_inexistante', array('from' => 'CVT', 'balise' => $nom));
523 erreur_squelette($msg, $context_compil);
529 return synthetiser_balise_dynamique($nomfonction, $r, $file, $context_compil);
534 * Retourne pour une clé primaire d'objet donnée les identifiants ayant un logo
536 * @uses type_du_logo() Pour calculer le nom du logo
538 * @param string $type
539 * Nom de la clé primaire de l'objet
541 * Liste des identifiants ayant un logo (séparés par une virgule)
543 function lister_objets_avec_logos($type) {
546 $chercher_logo = charger_fonction('chercher_logo', 'inc');
548 . type_du_logo($type)
550 . join('|', $GLOBALS['formats_logos'])
553 if ($d = opendir(_DIR_LOGOS
)) {
554 while (($f = readdir($d)) !== false) {
555 if (preg_match($type, $f, $r)) {
562 return join(',', $logos);
567 * Renvoie l'état courant des notes, le purge et en prépare un nouveau
569 * Fonction appelée par la balise `#NOTES`
571 * @see balise_NOTES_dist()
572 * @uses inc_notes_dist()
575 * Code HTML des notes
577 function calculer_notes() {
579 if ($notes = charger_fonction('notes', 'inc', true)) {
580 $r = $notes(array());
581 $notes('', 'depiler');
582 $notes('', 'empiler');
589 * Selectionner la langue de l'objet dans la boucle
591 * Applique sur un item de boucle la langue de l'élément qui est parcourru.
592 * Sauf dans les cas ou il ne le faut pas !
594 * La langue n'est pas modifiée lorsque :
595 * - la globale 'forcer_lang' est définie à true
596 * - l'objet ne définit pas de langue
597 * - le titre contient une balise multi.
599 * @param string $lang
601 * @param string $lang_select
602 * 'oui' si critère lang_select est présent, '' sinon.
603 * @param null|string $titre
607 function lang_select_public($lang, $lang_select, $titre = null) {
608 // Cas 1. forcer_lang = true et pas de critere {lang_select}
609 if (isset($GLOBALS['forcer_lang']) and $GLOBALS['forcer_lang']
610 and $lang_select !== 'oui'
612 $lang = $GLOBALS['spip_lang'];
613 } // Cas 2. l'objet n'a pas de langue definie (ou definie a '')
614 elseif (!strlen($lang)) {
615 $lang = $GLOBALS['spip_lang'];
616 } // Cas 3. l'objet est multilingue !
617 elseif ($lang_select !== 'oui'
618 and strlen($titre) > 10
619 and strpos($titre, '<multi>') !== false
620 and strpos(echappe_html($titre), '<multi>') !== false
622 $lang = $GLOBALS['spip_lang'];
625 // faire un lang_select() eventuellement sur la langue inchangee
632 // Si un tableau &doublons[articles] est passe en parametre,
633 // il faut le nettoyer car il pourrait etre injecte en SQL
634 // http://code.spip.net/@nettoyer_env_doublons
635 function nettoyer_env_doublons($envd) {
636 foreach ($envd as $table => $liste) {
638 foreach (explode(',', $liste) as $val) {
639 if ($a = intval($val) and $val === strval($a)) {
646 unset($envd[$table]);
654 * Cherche la présence d'un opérateur SELF ou SUBSELECT
656 * Cherche dans l'index 0 d'un tableau, la valeur SELF ou SUBSELECT
657 * indiquant pour une expression WHERE de boucle que nous sommes
658 * face à une sous-requête.
660 * Cherche de manière récursive également dans les autres valeurs si celles-ci
663 * @param string|array $w
664 * Description d'une condition WHERE de boucle (ou une partie de cette description)
665 * @return string|bool
666 * Opérateur trouvé (SELF ou SUBSELECT) sinon false.
668 function match_self($w) {
673 if (in_array(reset($w), array("SELF", "SUBSELECT"))) {
676 foreach (array_filter($w, 'is_array') as $sw) {
677 if ($m = match_self($sw)) {
687 * Remplace une condition décrivant une sous requête par son code
689 * @param array|string $w
690 * Description d'une condition WHERE de boucle (ou une partie de cette description)
691 * qui possède une description de sous-requête
692 * @param string $sousrequete
693 * Code PHP de la sous requête (qui doit remplacer la description)
694 * @return array|string
695 * Tableau de description du WHERE dont la description de sous-requête
696 * est remplacée par son code.
698 function remplace_sous_requete($w, $sousrequete) {
700 if (in_array(reset($w), array("SELF", "SUBSELECT"))) {
703 foreach ($w as $k => $sw) {
704 $w[$k] = remplace_sous_requete($sw, $sousrequete);
712 * Sépare les conditions de boucles simples de celles possédant des sous-requêtes.
714 * @param array $where
715 * Description d'une condition WHERE de boucle
717 * Liste de 2 tableaux :
718 * - Conditions simples (ne possédant pas de sous requêtes)
719 * - Conditions avec des sous requêtes
721 function trouver_sous_requetes($where) {
722 $where_simples = array();
723 $where_sous = array();
724 foreach ($where as $k => $w) {
725 if (match_self($w)) {
726 $where_sous[$k] = $w;
728 $where_simples[$k] = $w;
732 return array($where_simples, $where_sous);
737 * Calcule une requête et l’exécute
739 * Cette fonction est présente dans les squelettes compilés.
740 * Elle peut permettre de générer des requêtes avec jointure.
742 * @param array $select
744 * @param array $from_type
745 * @param array $where
747 * @param array $groupby
748 * @param array $orderby
749 * @param string $limit
750 * @param array $having
751 * @param string $table
753 * @param string $serveur
754 * @param bool $requeter
757 function calculer_select(
760 $from_type = array(),
773 // retirer les criteres vides:
774 // {X ?} avec X absent de l'URL
775 // {par #ENV{X}} avec X absent de l'URL
776 // IN sur collection vide (ce dernier devrait pouvoir etre fait a la compil)
778 foreach ($where as $k => $v) {
780 if ((count($v) >= 2) && ($v[0] == 'REGEXP') && ($v[2] == "'.*'")) {
782 } elseif ((count($v) >= 2) && ($v[0] == 'LIKE') && ($v[2] == "'%'")) {
785 $op = $v[0] ?
$v[0] : $v;
790 if ((!$op) or ($op == 1) or ($op == '0=0')) {
796 // evacuer les eventuels groupby vide issus d'un calcul dynamique
797 $groupby = array_diff($groupby, array(''));
799 // remplacer les sous requetes recursives au calcul
800 list($where_simples, $where_sous) = trouver_sous_requetes($where);
801 foreach ($where_sous as $k => $w) {
803 // on recupere la sous requete
804 $sous = match_self($w);
805 if ($sous[0] == 'SELF') {
806 // c'est une sous requete identique a elle meme sous la forme (SELF,$select,$where)
807 array_push($where_simples, $sous[2]);
811 ); // pour accepter une string et forcer a faire le menage car on a surement simplifie select et where
813 // trouver les jointures utiles a
814 // reinjecter dans le where de la sous requete les conditions supplementaires des jointures qui y sont mentionnees
815 // ie L1.objet='article'
816 // on construit le where une fois, puis on ajoute les where complentaires si besoin, et on reconstruit le where en fonction
819 $where[$k] = remplace_sous_requete($w, "(" . calculer_select(
820 array($sous[1] . " AS id"),
825 array(), array(), '',
826 $having, $table, $id, $serveur, false) . ")");
829 $wherestring = calculer_where_to_string($where[$k]);
830 foreach ($join as $cle => $wj) {
832 and strpos($wherestring, "{$cle}.") !== false
835 $wheresub[] = $wj[3];
836 unset($jsub[$cle][3]);
842 if ($sous[0] == 'SUBSELECT') {
843 // c'est une sous requete explicite sous la forme identique a sql_select : (SUBSELECT,$select,$from,$where,$groupby,$orderby,$limit,$having)
844 array_push($where_simples, $sous[3]); // est-ce utile dans ce cas ?
845 $where[$k] = remplace_sous_requete($w, "(" . calculer_select(
849 $sous[3] ?
(is_array($sous[3]) ?
$sous[3] : array($sous[3])) : array(),
850 #where, qui peut etre de la forme string comme dans sql_select
852 $sous[4] ?
$sous[4] : array(), #groupby
853 $sous[5] ?
$sous[5] : array(), #orderby
855 $sous[7] ?
$sous[7] : array(), #having
856 $table, $id, $serveur, false
859 array_pop($where_simples);
862 foreach ($having as $k => $v) {
863 if ((!$v) or ($v == 1) or ($v == '0=0')) {
868 // Installer les jointures.
869 // Retirer celles seulement utiles aux criteres finalement absents mais
870 // parcourir de la plus recente a la moins recente pour pouvoir eliminer Ln
871 // si elle est seulement utile a Ln+1 elle meme inutile
876 foreach (array_reverse($join, true) as $cledef => $j) {
878 // le format de join est :
879 // array(table depart, cle depart [,cle arrivee[,condition optionnelle and ...]])
880 $join[$cle] = array_values($join[$cle]); // recalculer les cles car des unset ont pu perturber
881 if (count($join[$cle]) == 2) {
882 $join[$cle][] = $join[$cle][1];
884 if (count($join[$cle]) == 3) {
887 list($t, $c, $carr, $and) = $join[$cle];
888 // si le nom de la jointure n'a pas ete specifiee, on prend Lx avec x sont rang dans la liste
889 // pour compat avec ancienne convention
890 if (is_numeric($cle)) {
894 or isset($afrom[$cle])
895 or calculer_jointnul($cle, $select)
896 or calculer_jointnul($cle, array_diff_key($join, array($cle => $join[$cle])))
897 or calculer_jointnul($cle, $having)
898 or calculer_jointnul($cle, $where_simples)
900 // corriger les references non explicites dans select
902 foreach ($select as $i => $s) {
904 $select[$i] = "$cle.$c AS $c";
908 foreach ($groupby as $i => $g) {
910 $groupby[$i] = "$cle.$c";
914 // on garde une ecriture decomposee pour permettre une simplification ulterieure si besoin
915 // sans recours a preg_match
916 // un implode(' ',..) est fait dans reinjecte_joint un peu plus bas
917 $afrom[$t][$cle] = array(
919 (isset($from_type[$cle]) ?
$from_type[$cle] : "INNER") . " JOIN",
926 ($and ?
"AND " . $and : "") .
929 if (isset($afrom[$cle])) {
930 $afrom[$t] = $afrom[$t] +
$afrom[$cle];
935 unset($join[$cledef]);
942 // Regarder si la table principale ne sert finalement a rien comme dans
943 //<BOUCLE3(MOTS){id_article}{id_mot}> class='on'</BOUCLE3>
944 //<BOUCLE2(MOTS){id_article} />#TOTAL_BOUCLE<//B2>
945 //<BOUCLE5(RUBRIQUES){id_mot}{tout} />#TOTAL_BOUCLE<//B5>
947 //<BOUCLE8(HIERARCHIE){id_rubrique}{tout}{type='Squelette'}{inverse}{0,1}{lang_select=non} />#TOTAL_BOUCLE<//B8>
948 // qui comporte plusieurs jointures
950 // <BOUCLE6(ARTICLES){id_mot=2}{statut==.*} />#TOTAL_BOUCLE<//B6>
951 // <BOUCLE7(ARTICLES){id_mot>0}{statut?} />#TOTAL_BOUCLE<//B7>
952 // penser a regarder aussi la clause orderby pour ne pas simplifier abusivement
953 // <BOUCLE9(ARTICLES){recherche truc}{par titre}>#ID_ARTICLE</BOUCLE9>
954 // penser a regarder aussi la clause groubpy pour ne pas simplifier abusivement
955 // <BOUCLE10(EVENEMENTS){id_rubrique} />#TOTAL_BOUCLE<//B10>
957 list($t, $c) = each($from);
959 $e = '/\b(' . "$t\\." . join("|" . $t . '\.', $equiv) . ')\b/';
960 if (!(strpos($t, ' ') or // jointure des le depart cf boucle_doc
961 calculer_jointnul($t, $select, $e) or
962 calculer_jointnul($t, $join, $e) or
963 calculer_jointnul($t, $where, $e) or
964 calculer_jointnul($t, $orderby, $e) or
965 calculer_jointnul($t, $groupby, $e) or
966 calculer_jointnul($t, $having, $e))
970 list($nt, $nfrom) = each($afrom[$t]);
972 $from[$nt] = $nfrom[1];
973 unset($afrom[$t][$nt]);
974 $afrom[$nt] = $afrom[$t];
976 $e = '/\b' . preg_quote($nfrom[6]) . '\b/';
979 // verifier que les deux cles sont homonymes, sinon installer un alias dans le select
980 $oldcle = explode('.', $nfrom[6]);
981 $oldcle = end($oldcle);
982 $newcle = explode('.', $nfrom[4]);
983 $newcle = end($newcle);
984 if ($newcle != $oldcle) {
985 // si l'ancienne cle etait deja dans le select avec un AS
986 // reprendre simplement ce AS
987 $as = '/\b' . preg_quote($nfrom[6]) . '\s+(AS\s+\w+)\b/';
988 if (preg_match($as, implode(',', $select), $m)) {
991 $alias = ", " . $nfrom[4] . " AS $oldcle";
994 $select = remplacer_jointnul($t . $alias, $select, $e);
995 $join = remplacer_jointnul($t, $join, $e);
996 $where = remplacer_jointnul($t, $where, $e);
997 $having = remplacer_jointnul($t, $having, $e);
998 $groupby = remplacer_jointnul($t, $groupby, $e);
999 $orderby = remplacer_jointnul($t, $orderby, $e);
1001 $from = reinjecte_joint($afrom, $from);
1003 $GLOBALS['debug']['aucasou'] = array($table, $id, $serveur, $requeter);
1004 $r = sql_select($select, $from, $where,
1005 $groupby, array_filter($orderby), $limit, $having, $serveur, $requeter);
1006 unset($GLOBALS['debug']['aucasou']);
1012 * Analogue a calculer_mysql_expression et autre (a unifier ?)
1014 * @param string|array $v
1015 * @param string $join
1018 function calculer_where_to_string($v, $join = 'AND') {
1023 if (!is_array($v)) {
1027 if (strtoupper($join) === 'AND') {
1028 return $exp . join(" $join ", array_map('calculer_where_to_string', $v));
1030 return $exp . join($join, $v);
1036 //condition suffisante (mais non necessaire) pour qu'une table soit utile
1038 // http://code.spip.net/@calculer_jointnul
1039 function calculer_jointnul($cle, $exp, $equiv = '') {
1040 if (!is_array($exp)) {
1042 $exp = preg_replace($equiv, '', $exp);
1045 return preg_match("/\\b$cle\\./", $exp);
1047 foreach ($exp as $v) {
1048 if (calculer_jointnul($cle, $v, $equiv)) {
1057 // http://code.spip.net/@reinjecte_joint
1058 function reinjecte_joint($afrom, $from) {
1059 $from_synth = array();
1060 foreach ($from as $k => $v) {
1061 $from_synth[$k] = $from[$k];
1062 if (isset($afrom[$k])) {
1063 foreach ($afrom[$k] as $kk => $vv) {
1064 $afrom[$k][$kk] = implode(' ', $afrom[$k][$kk]);
1066 $from_synth["$k@"] = implode(' ', $afrom[$k]);
1074 // http://code.spip.net/@remplacer_jointnul
1075 function remplacer_jointnul($cle, $exp, $equiv = '') {
1076 if (!is_array($exp)) {
1077 return preg_replace($equiv, $cle, $exp);
1079 foreach ($exp as $k => $v) {
1080 $exp[$k] = remplacer_jointnul($cle, $v, $equiv);
1087 // calcul du nom du squelette
1088 // http://code.spip.net/@calculer_nom_fonction_squel
1089 function calculer_nom_fonction_squel($skel, $mime_type = 'html', $connect = '') {
1090 // ne pas doublonner les squelette selon qu'ils sont calcules depuis ecrire/ ou depuis la racine
1091 if ($l = strlen(_DIR_RACINE
) and strncmp($skel, _DIR_RACINE
, $l) == 0) {
1092 $skel = substr($skel, strlen(_DIR_RACINE
));
1096 . (!$connect ?
'' : preg_replace('/\W/', "_", $connect)) . '_'
1097 . md5($GLOBALS['spip_version_code'] . ' * ' . $skel . (isset($GLOBALS['marqueur_skel']) ?
'*' . $GLOBALS['marqueur_skel'] : ''));