3 /***************************************************************************\
4 * SPIP, Systeme de publication pour l'internet *
6 * Copyright (c) 2001-2019 *
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) and defined('_INTRODUCTION_SUITE')) {
352 $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;
403 $lang = $context_compil[4];
404 if (preg_match(",\W,", $lang)) {
408 $args = array_map('argumenter_squelette', $args);
409 if (!empty($context_compil['appel_php_depuis_modele'])) {
410 $args[0] = 'arguments_balise_dyn_depuis_modele('.$args[0].')';
412 $args = join(', ', $args);
414 $r = sprintf(CODE_INCLURE_BALISE
,
419 join(', ', array_map('_q', $context_compil)));
425 * Crée le code PHP pour transmettre des arguments (généralement pour une inclusion)
427 * @param array|string $v
428 * Arguments à transmettre :
430 * - string : un simple texte à faire écrire
431 * - array : couples ('nom' => 'valeur') liste des arguments et leur valeur
434 * - Code PHP créant le tableau des arguments à transmettre,
435 * - ou texte entre quote `'` (si `$v` était une chaîne)
437 function argumenter_squelette($v) {
440 return "'" . texte_script($v) . "'";
443 foreach ($v as $k => $val) {
444 $out [] = argumenter_squelette($k) . '=>' . argumenter_squelette($val);
447 return 'array(' . join(", ", $out) . ')';
453 * Calcule et retourne le code PHP retourné par l'exécution d'une balise
456 * Vérifier les arguments et filtres et calcule le code PHP à inclure.
458 * - charge le fichier PHP de la balise dynamique dans le répertoire
459 * `balise/`, soit du nom complet de la balise, soit d'un nom générique
460 * (comme 'formulaire_.php'). Dans ce dernier cas, le nom de la balise
461 * est ajouté en premier argument.
462 * - appelle une éventuelle fonction de traitement des arguments `balise_NOM_stat()`
463 * - crée le code PHP de la balise si une fonction `balise_NOM_dyn()` (ou variantes)
464 * est effectivement trouvée.
466 * @uses synthetiser_balise_dynamique()
467 * Pour calculer le code PHP d'inclusion produit
470 * Nom de la balise dynamique
472 * Liste des arguments calculés de la balise
473 * @param array $context_compil
474 * Tableau d'informations sur la compilation
476 * Code PHP d'exécutant l'inclusion du squelette (ou texte) de la balise dynamique
478 function executer_balise_dynamique($nom, $args, $context_compil) {
480 $nomfonction_generique = "";
482 $appel_php_depuis_modele = false;
483 if (is_array($context_compil)
484 and !is_numeric($context_compil[3])
485 and empty($context_compil[0])
486 and empty($context_compil[1])
487 and empty($context_compil[2])
488 and empty($context_compil[3])) {
489 $appel_php_depuis_modele = true;
492 // Calculer un nom générique (ie. 'formulaire_' dans 'formulaire_editer_article')
493 if (false !== ($p = strpos($nom, "_"))) {
494 $nomfonction_generique = substr($nom, 0, $p +
1);
497 if (!$fonction_balise = charger_fonction($nomfonction, 'balise', true)) {
498 if ($nomfonction_generique and $fonction_balise = charger_fonction($nomfonction_generique, 'balise', true)) {
499 // et injecter en premier arg le nom de la balise
500 array_unshift($args, $nom);
501 $nomfonction = $nomfonction_generique;
505 if (!$fonction_balise) {
506 $msg = array('zbug_balise_inexistante', array('from' => 'CVT', 'balise' => $nom));
507 erreur_squelette($msg, $context_compil);
512 // retrouver le fichier qui a déclaré la fonction
513 // même si la fonction dynamique est déclarée dans un fichier de fonctions.
514 // Attention sous windows, getFileName() retourne un antislash.
515 $reflector = new ReflectionFunction($fonction_balise);
516 $file = str_replace('\\', '/', $reflector->getFileName());
517 if (strncmp($file, str_replace('\\', '/', _ROOT_RACINE
), strlen(_ROOT_RACINE
)) === 0) {
518 $file = substr($file, strlen(_ROOT_RACINE
));
521 // Y a-t-il une fonction de traitement des arguments ?
522 $f = 'balise_' . $nomfonction . '_stat';
524 $r = !function_exists($f) ?
$args : $f($args, $context_compil);
530 // verifier que la fonction dyn est la,
531 // sinon se replier sur la generique si elle existe
532 if (!function_exists('balise_' . $nomfonction . '_dyn')) {
533 if ($nomfonction_generique
534 and $file = include_spip("balise/" . strtolower($nomfonction_generique))
535 and function_exists('balise_' . $nomfonction_generique . '_dyn')
537 // et lui injecter en premier arg le nom de la balise
538 array_unshift($r, $nom);
539 $nomfonction = $nomfonction_generique;
540 if (!_DIR_RESTREINT
) {
541 $file = _DIR_RESTREINT_ABS
. $file;
544 $msg = array('zbug_balise_inexistante', array('from' => 'CVT', 'balise' => $nom));
545 erreur_squelette($msg, $context_compil);
551 if ($appel_php_depuis_modele) {
552 $context_compil['appel_php_depuis_modele'] = true;
554 return synthetiser_balise_dynamique($nomfonction, $r, $file, $context_compil);
559 * Retourne pour une clé primaire d'objet donnée les identifiants ayant un logo
561 * @uses type_du_logo() Pour calculer le nom du logo
563 * @param string $type
564 * Nom de la clé primaire de l'objet
566 * Liste des identifiants ayant un logo (séparés par une virgule)
568 function lister_objets_avec_logos($type) {
571 $chercher_logo = charger_fonction('chercher_logo', 'inc');
573 . type_du_logo($type)
575 . join('|', $GLOBALS['formats_logos'])
578 if ($d = opendir(_DIR_LOGOS
)) {
579 while (($f = readdir($d)) !== false) {
580 if (preg_match($type, $f, $r)) {
587 return join(',', $logos);
592 * Renvoie l'état courant des notes, le purge et en prépare un nouveau
594 * Fonction appelée par la balise `#NOTES`
596 * @see balise_NOTES_dist()
597 * @uses inc_notes_dist()
600 * Code HTML des notes
602 function calculer_notes() {
604 if ($notes = charger_fonction('notes', 'inc', true)) {
605 $r = $notes(array());
606 $notes('', 'depiler');
607 $notes('', 'empiler');
614 * Selectionner la langue de l'objet dans la boucle
616 * Applique sur un item de boucle la langue de l'élément qui est parcourru.
617 * Sauf dans les cas ou il ne le faut pas !
619 * La langue n'est pas modifiée lorsque :
620 * - la globale 'forcer_lang' est définie à true
621 * - l'objet ne définit pas de langue
622 * - le titre contient une balise multi.
624 * @param string $lang
626 * @param string $lang_select
627 * 'oui' si critère lang_select est présent, '' sinon.
628 * @param null|string $titre
632 function lang_select_public($lang, $lang_select, $titre = null) {
633 // Cas 1. forcer_lang = true et pas de critere {lang_select}
634 if (isset($GLOBALS['forcer_lang']) and $GLOBALS['forcer_lang']
635 and $lang_select !== 'oui'
637 $lang = $GLOBALS['spip_lang'];
638 } // Cas 2. l'objet n'a pas de langue definie (ou definie a '')
639 elseif (!strlen($lang)) {
640 $lang = $GLOBALS['spip_lang'];
641 } // Cas 3. l'objet est multilingue !
642 elseif ($lang_select !== 'oui'
643 and strlen($titre) > 10
644 and strpos($titre, '<multi>') !== false
645 and strpos(echappe_html($titre), '<multi>') !== false
647 $lang = $GLOBALS['spip_lang'];
650 // faire un lang_select() eventuellement sur la langue inchangee
657 // Si un tableau &doublons[articles] est passe en parametre,
658 // il faut le nettoyer car il pourrait etre injecte en SQL
659 // http://code.spip.net/@nettoyer_env_doublons
660 function nettoyer_env_doublons($envd) {
661 foreach ($envd as $table => $liste) {
663 foreach (explode(',', $liste) as $val) {
664 if ($a = intval($val) and $val === strval($a)) {
671 unset($envd[$table]);
679 * Cherche la présence d'un opérateur SELF ou SUBSELECT
681 * Cherche dans l'index 0 d'un tableau, la valeur SELF ou SUBSELECT
682 * indiquant pour une expression WHERE de boucle que nous sommes
683 * face à une sous-requête.
685 * Cherche de manière récursive également dans les autres valeurs si celles-ci
688 * @param string|array $w
689 * Description d'une condition WHERE de boucle (ou une partie de cette description)
690 * @return string|bool
691 * Opérateur trouvé (SELF ou SUBSELECT) sinon false.
693 function match_self($w) {
698 if (in_array(reset($w), array("SELF", "SUBSELECT"))) {
701 foreach (array_filter($w, 'is_array') as $sw) {
702 if ($m = match_self($sw)) {
712 * Remplace une condition décrivant une sous requête par son code
714 * @param array|string $w
715 * Description d'une condition WHERE de boucle (ou une partie de cette description)
716 * qui possède une description de sous-requête
717 * @param string $sousrequete
718 * Code PHP de la sous requête (qui doit remplacer la description)
719 * @return array|string
720 * Tableau de description du WHERE dont la description de sous-requête
721 * est remplacée par son code.
723 function remplace_sous_requete($w, $sousrequete) {
725 if (in_array(reset($w), array("SELF", "SUBSELECT"))) {
728 foreach ($w as $k => $sw) {
729 $w[$k] = remplace_sous_requete($sw, $sousrequete);
737 * Sépare les conditions de boucles simples de celles possédant des sous-requêtes.
739 * @param array $where
740 * Description d'une condition WHERE de boucle
742 * Liste de 2 tableaux :
743 * - Conditions simples (ne possédant pas de sous requêtes)
744 * - Conditions avec des sous requêtes
746 function trouver_sous_requetes($where) {
747 $where_simples = array();
748 $where_sous = array();
749 foreach ($where as $k => $w) {
750 if (match_self($w)) {
751 $where_sous[$k] = $w;
753 $where_simples[$k] = $w;
757 return array($where_simples, $where_sous);
762 * Calcule une requête et l’exécute
764 * Cette fonction est présente dans les squelettes compilés.
765 * Elle peut permettre de générer des requêtes avec jointure.
767 * @param array $select
769 * @param array $from_type
770 * @param array $where
772 * @param array $groupby
773 * @param array $orderby
774 * @param string $limit
775 * @param array $having
776 * @param string $table
778 * @param string $serveur
779 * @param bool $requeter
782 function calculer_select(
785 $from_type = array(),
798 // retirer les criteres vides:
799 // {X ?} avec X absent de l'URL
800 // {par #ENV{X}} avec X absent de l'URL
801 // IN sur collection vide (ce dernier devrait pouvoir etre fait a la compil)
803 foreach ($where as $k => $v) {
805 if ((count($v) >= 2) && ($v[0] == 'REGEXP') && ($v[2] == "'.*'")) {
807 } elseif ((count($v) >= 2) && ($v[0] == 'LIKE') && ($v[2] == "'%'")) {
810 $op = $v[0] ?
$v[0] : $v;
815 if ((!$op) or ($op == 1) or ($op == '0=0')) {
821 // evacuer les eventuels groupby vide issus d'un calcul dynamique
822 $groupby = array_diff($groupby, array(''));
824 // remplacer les sous requetes recursives au calcul
825 list($where_simples, $where_sous) = trouver_sous_requetes($where);
826 foreach ($where_sous as $k => $w) {
828 // on recupere la sous requete
829 $sous = match_self($w);
830 if ($sous[0] == 'SELF') {
831 // c'est une sous requete identique a elle meme sous la forme (SELF,$select,$where)
832 array_push($where_simples, $sous[2]);
836 ); // pour accepter une string et forcer a faire le menage car on a surement simplifie select et where
838 // trouver les jointures utiles a
839 // reinjecter dans le where de la sous requete les conditions supplementaires des jointures qui y sont mentionnees
840 // ie L1.objet='article'
841 // on construit le where une fois, puis on ajoute les where complentaires si besoin, et on reconstruit le where en fonction
844 $where[$k] = remplace_sous_requete($w, "(" . calculer_select(
845 array($sous[1] . " AS id"),
850 array(), array(), '',
851 $having, $table, $id, $serveur, false) . ")");
854 $wherestring = calculer_where_to_string($where[$k]);
855 foreach ($join as $cle => $wj) {
857 and strpos($wherestring, "{$cle}.") !== false
860 $wheresub[] = $wj[3];
861 unset($jsub[$cle][3]);
867 if ($sous[0] == 'SUBSELECT') {
868 // c'est une sous requete explicite sous la forme identique a sql_select : (SUBSELECT,$select,$from,$where,$groupby,$orderby,$limit,$having)
869 array_push($where_simples, $sous[3]); // est-ce utile dans ce cas ?
870 $where[$k] = remplace_sous_requete($w, "(" . calculer_select(
874 $sous[3] ?
(is_array($sous[3]) ?
$sous[3] : array($sous[3])) : array(),
875 #where, qui peut etre de la forme string comme dans sql_select
877 $sous[4] ?
$sous[4] : array(), #groupby
878 $sous[5] ?
$sous[5] : array(), #orderby
880 $sous[7] ?
$sous[7] : array(), #having
881 $table, $id, $serveur, false
884 array_pop($where_simples);
887 foreach ($having as $k => $v) {
888 if ((!$v) or ($v == 1) or ($v == '0=0')) {
893 // Installer les jointures.
894 // Retirer celles seulement utiles aux criteres finalement absents mais
895 // parcourir de la plus recente a la moins recente pour pouvoir eliminer Ln
896 // si elle est seulement utile a Ln+1 elle meme inutile
901 foreach (array_reverse($join, true) as $cledef => $j) {
903 // le format de join est :
904 // array(table depart, cle depart [,cle arrivee[,condition optionnelle and ...]])
905 $join[$cle] = array_values($join[$cle]); // recalculer les cles car des unset ont pu perturber
906 if (count($join[$cle]) == 2) {
907 $join[$cle][] = $join[$cle][1];
909 if (count($join[$cle]) == 3) {
912 list($t, $c, $carr, $and) = $join[$cle];
913 // si le nom de la jointure n'a pas ete specifiee, on prend Lx avec x sont rang dans la liste
914 // pour compat avec ancienne convention
915 if (is_numeric($cle)) {
919 or isset($afrom[$cle])
920 or calculer_jointnul($cle, $select)
921 or calculer_jointnul($cle, array_diff_key($join, array($cle => $join[$cle])))
922 or calculer_jointnul($cle, $having)
923 or calculer_jointnul($cle, $where_simples)
925 // corriger les references non explicites dans select
927 foreach ($select as $i => $s) {
929 $select[$i] = "$cle.$c AS $c";
933 foreach ($groupby as $i => $g) {
935 $groupby[$i] = "$cle.$c";
939 // on garde une ecriture decomposee pour permettre une simplification ulterieure si besoin
940 // sans recours a preg_match
941 // un implode(' ',..) est fait dans reinjecte_joint un peu plus bas
942 $afrom[$t][$cle] = array(
944 (isset($from_type[$cle]) ?
$from_type[$cle] : "INNER") . " JOIN",
951 ($and ?
"AND " . $and : "") .
954 if (isset($afrom[$cle])) {
955 $afrom[$t] = $afrom[$t] +
$afrom[$cle];
960 unset($join[$cledef]);
967 // Regarder si la table principale ne sert finalement a rien comme dans
968 //<BOUCLE3(MOTS){id_article}{id_mot}> class='on'</BOUCLE3>
969 //<BOUCLE2(MOTS){id_article} />#TOTAL_BOUCLE<//B2>
970 //<BOUCLE5(RUBRIQUES){id_mot}{tout} />#TOTAL_BOUCLE<//B5>
972 //<BOUCLE8(HIERARCHIE){id_rubrique}{tout}{type='Squelette'}{inverse}{0,1}{lang_select=non} />#TOTAL_BOUCLE<//B8>
973 // qui comporte plusieurs jointures
975 // <BOUCLE6(ARTICLES){id_mot=2}{statut==.*} />#TOTAL_BOUCLE<//B6>
976 // <BOUCLE7(ARTICLES){id_mot>0}{statut?} />#TOTAL_BOUCLE<//B7>
977 // penser a regarder aussi la clause orderby pour ne pas simplifier abusivement
978 // <BOUCLE9(ARTICLES){recherche truc}{par titre}>#ID_ARTICLE</BOUCLE9>
979 // penser a regarder aussi la clause groubpy pour ne pas simplifier abusivement
980 // <BOUCLE10(EVENEMENTS){id_rubrique} />#TOTAL_BOUCLE<//B10>
982 list($t, $c) = each($from);
984 $e = '/\b(' . "$t\\." . join("|" . $t . '\.', $equiv) . ')\b/';
985 if (!(strpos($t, ' ') or // jointure des le depart cf boucle_doc
986 calculer_jointnul($t, $select, $e) or
987 calculer_jointnul($t, $join, $e) or
988 calculer_jointnul($t, $where, $e) or
989 calculer_jointnul($t, $orderby, $e) or
990 calculer_jointnul($t, $groupby, $e) or
991 calculer_jointnul($t, $having, $e))
995 list($nt, $nfrom) = each($afrom[$t]);
997 $from[$nt] = $nfrom[1];
998 unset($afrom[$t][$nt]);
999 $afrom[$nt] = $afrom[$t];
1001 $e = '/\b' . preg_quote($nfrom[6]) . '\b/';
1004 // verifier que les deux cles sont homonymes, sinon installer un alias dans le select
1005 $oldcle = explode('.', $nfrom[6]);
1006 $oldcle = end($oldcle);
1007 $newcle = explode('.', $nfrom[4]);
1008 $newcle = end($newcle);
1009 if ($newcle != $oldcle) {
1010 // si l'ancienne cle etait deja dans le select avec un AS
1011 // reprendre simplement ce AS
1012 $as = '/\b' . preg_quote($nfrom[6]) . '\s+(AS\s+\w+)\b/';
1013 if (preg_match($as, implode(',', $select), $m)) {
1016 $alias = ", " . $nfrom[4] . " AS $oldcle";
1019 $select = remplacer_jointnul($t . $alias, $select, $e);
1020 $join = remplacer_jointnul($t, $join, $e);
1021 $where = remplacer_jointnul($t, $where, $e);
1022 $having = remplacer_jointnul($t, $having, $e);
1023 $groupby = remplacer_jointnul($t, $groupby, $e);
1024 $orderby = remplacer_jointnul($t, $orderby, $e);
1026 $from = reinjecte_joint($afrom, $from);
1028 $GLOBALS['debug']['aucasou'] = array($table, $id, $serveur, $requeter);
1029 $r = sql_select($select, $from, $where,
1030 $groupby, array_filter($orderby), $limit, $having, $serveur, $requeter);
1031 unset($GLOBALS['debug']['aucasou']);
1037 * Analogue a calculer_mysql_expression et autre (a unifier ?)
1039 * @param string|array $v
1040 * @param string $join
1043 function calculer_where_to_string($v, $join = 'AND') {
1048 if (!is_array($v)) {
1052 if (strtoupper($join) === 'AND') {
1053 return $exp . join(" $join ", array_map('calculer_where_to_string', $v));
1055 return $exp . join($join, $v);
1061 //condition suffisante (mais non necessaire) pour qu'une table soit utile
1063 // http://code.spip.net/@calculer_jointnul
1064 function calculer_jointnul($cle, $exp, $equiv = '') {
1065 if (!is_array($exp)) {
1067 $exp = preg_replace($equiv, '', $exp);
1070 return preg_match("/\\b$cle\\./", $exp);
1072 foreach ($exp as $v) {
1073 if (calculer_jointnul($cle, $v, $equiv)) {
1082 // http://code.spip.net/@reinjecte_joint
1083 function reinjecte_joint($afrom, $from) {
1084 $from_synth = array();
1085 foreach ($from as $k => $v) {
1086 $from_synth[$k] = $from[$k];
1087 if (isset($afrom[$k])) {
1088 foreach ($afrom[$k] as $kk => $vv) {
1089 $afrom[$k][$kk] = implode(' ', $afrom[$k][$kk]);
1091 $from_synth["$k@"] = implode(' ', $afrom[$k]);
1099 // http://code.spip.net/@remplacer_jointnul
1100 function remplacer_jointnul($cle, $exp, $equiv = '') {
1101 if (!is_array($exp)) {
1102 return preg_replace($equiv, $cle, $exp);
1104 foreach ($exp as $k => $v) {
1105 $exp[$k] = remplacer_jointnul($cle, $v, $equiv);
1112 // calcul du nom du squelette
1113 // http://code.spip.net/@calculer_nom_fonction_squel
1114 function calculer_nom_fonction_squel($skel, $mime_type = 'html', $connect = '') {
1115 // ne pas doublonner les squelette selon qu'ils sont calcules depuis ecrire/ ou depuis la racine
1116 if ($l = strlen(_DIR_RACINE
) and strncmp($skel, _DIR_RACINE
, $l) == 0) {
1117 $skel = substr($skel, strlen(_DIR_RACINE
));
1121 . (!$connect ?
'' : preg_replace('/\W/', "_", $connect)) . '_'
1122 . md5($GLOBALS['spip_version_code'] . ' * ' . $skel . (isset($GLOBALS['marqueur_skel']) ?
'*' . $GLOBALS['marqueur_skel'] : ''));