[SPIP] v3.2.7-->v3.2.9
[lhc/web/www.git] / www / ecrire / public / composer.php
1 <?php
2
3 /***************************************************************************\
4 * SPIP, Systeme de publication pour l'internet *
5 * *
6 * Copyright (c) 2001-2019 *
7 * Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James *
8 * *
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 \***************************************************************************/
12
13 /**
14 * Compose un squelette : compile le squelette au besoin et vérifie
15 * la validité du code compilé
16 *
17 * @package SPIP\Core\Compilateur\Composer
18 **/
19
20 if (!defined('_ECRIRE_INC_VERSION')) {
21 return;
22 }
23
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');
33
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:
37 # 1. des filtres
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
41
42 // http://code.spip.net/@public_composer_dist
43 function public_composer_dist($squelette, $mime_type, $gram, $source, $connect = '') {
44
45 $nom = calculer_nom_fonction_squel($squelette, $mime_type, $connect);
46
47 // si deja en memoire (INCLURE a repetition) c'est bon.
48 if (function_exists($nom)) {
49 return $nom;
50 }
51
52 if (defined('_VAR_MODE') and _VAR_MODE == 'debug') {
53 $GLOBALS['debug_objets']['courant'] = $nom;
54 }
55
56 $phpfile = sous_repertoire(_DIR_SKELS, '', false, true) . $nom . '.php';
57
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')
66 #}
67 }
68
69 if (file_exists($lib = $squelette . '_fonctions' . '.php')) {
70 include_once $lib;
71 }
72
73 // tester si le eval ci-dessus a mis le squelette en memoire
74
75 if (function_exists($nom)) {
76 return $nom;
77 }
78
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);
83 }
84
85 // Ne plus rien faire si le compilateur n'a pas pu operer.
86 if (!$skel_code) {
87 return false;
88 }
89
90 foreach ($skel_code as $id => $boucle) {
91 $f = $boucle->return;
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
98 $nom = '';
99 }
100 // Contexte de compil inutile a present
101 // (mais la derniere valeur de $boucle est utilisee ci-dessous)
102 $skel_code[$id] = $f;
103 }
104
105 $code = '';
106 if ($nom) {
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);
110 } else {
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);
115 $nom = '';
116 }
117 }
118
119 if (defined('_VAR_MODE') and _VAR_MODE == 'debug') {
120
121 // Tracer ce qui vient d'etre compile
122 $GLOBALS['debug_objets']['code'][$nom . 'tout'] = $code;
123
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')
128 ) {
129 erreur_squelette();
130 }
131 }
132
133 return $nom ? $nom : false;
134 }
135
136 function squelette_traduit($squelette, $sourcefile, $phpfile, $boucles) {
137
138 // Le dernier index est '' (fonction principale)
139 $noms = substr(join(', ', array_keys($boucles)), 0, -2);
140 if (CODE_COMMENTE) {
141 $code = "
142 /*
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)) . "
147 */ ";
148 }
149
150 $code = '<' . "?php\n" . $code . join('', $boucles) . "\n?" . '>';
151 if (!defined('_VAR_NOCACHE') or !_VAR_NOCACHE) {
152 ecrire_fichier($phpfile, $code);
153 }
154
155 return $code;
156 }
157
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
163 // par hit
164 if (is_null($date_change)) {
165 if (@file_exists($fonc = 'mes_fonctions.php')) {
166 $date_change = @filemtime($fonc);
167 } # compatibilite
168 if (defined('_FILE_OPTIONS')) {
169 $date_change = max($date_change, @filemtime(_FILE_OPTIONS));
170 }
171 }
172
173 return (
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)
179 );
180 }
181
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();
186
187 return $code;
188 }
189
190
191 // http://code.spip.net/@analyse_resultat_skel
192 function analyse_resultat_skel($nom, $cache, $corps, $source = '') {
193 static $filtres = array();
194 $headers = array();
195
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
200 and preg_match_all(
201 '/(<[?]php\s+)@?header\s*\(\s*.([^:\'"]*):?\s*([^)]*)[^)]\s*\)\s*[;]?\s*[?]>/ims',
202 $corps, $regs, PREG_SET_ORDER)
203 ) {
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]))));
208
209 if ($j == 'X-Spip-Filtre' and isset($headers[$j])) {
210 $headers[$j] .= "|" . $r[3];
211 } else {
212 $headers[$j] = $r[3];
213 }
214 }
215 }
216 // S'agit-il d'un resultat constant ou contenant du code php
217 $process_ins = (
218 strpos($corps, '<' . '?') === false
219 or
220 (strpos($corps, '<' . '?xml') !== false and
221 strpos(str_replace('<' . '?xml', '', $corps), '<' . '?') === false)
222 )
223 ? 'html'
224 : 'php';
225
226 $skel = array(
227 'squelette' => $nom,
228 'source' => $source,
229 'process_ins' => $process_ins,
230 'invalideurs' => $cache,
231 'entetes' => $headers,
232 'duree' => isset($headers['X-Spip-Cache']) ? intval($headers['X-Spip-Cache']) : 0
233 );
234
235 // traiter #FILTRE{} et filtres
236 if (!isset($filtres[$nom])) {
237 $filtres[$nom] = pipeline('declarer_filtres_squelettes', array('args' => $skel, 'data' => array()));
238 }
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']);
244
245 if ($process_ins == 'html') {
246 $skel['process_ins'] = (
247 strpos($corps, '<' . '?') === false
248 or
249 (strpos($corps, '<' . '?xml') !== false and
250 strpos(str_replace('<' . '?xml', '', $corps), '<' . '?') === false)
251 )
252 ? 'html'
253 : 'php';
254 }
255 }
256
257 $skel['entetes'] = $headers;
258 $skel['texte'] = $corps;
259
260 return $skel;
261 }
262
263 //
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 ???
269 //
270
271
272 /**
273 * Calcul d'une introduction
274 *
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.
280 *
281 * Cette fonction est utilisée par la balise #INTRODUCTION
282 *
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 &nbsp;(...)
293 * @return string
294 * Introduction calculée
295 **/
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);
300 }
301
302 // De preference ce qui est marque <intro>...</intro>
303 $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);
310 }
311 $intro .= $zone;
312 }
313
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);
321
322 // Cependant pour des questions de perfs on coupe quand meme, en prenant
323 // large et en se mefiant des tableaux #1323
324
325 if (strlen($intro)) {
326 $texte = $intro;
327 } else {
328 if (strpos("\n" . $texte, "\n|") === false
329 and strlen($texte) > 2.5 * $longueur
330 ) {
331 if (strpos($texte, "<multi") !== false) {
332 $texte = extraire_multi($texte);
333 }
334 $texte = couper($texte, 2 * $longueur);
335 }
336 }
337
338 // ne pas tenir compte des notes
339 if ($notes = charger_fonction('notes', 'inc', true)) {
340 $notes('', 'empiler');
341 }
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);
346
347 if ($notes) {
348 $notes('', 'depiler');
349 }
350
351 if (is_null($suite) and defined('_INTRODUCTION_SUITE')) {
352 $suite = _INTRODUCTION_SUITE;
353 }
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());
357
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
361 {
362 $texte = paragrapher($texte, $GLOBALS['toujours_paragrapher']);
363 }
364
365 return $texte;
366 }
367
368 //
369 // Balises dynamiques
370 //
371
372 /** Code PHP pour inclure une balise dynamique à l'exécution d'une page */
373 define('CODE_INCLURE_BALISE', '<' . '?php
374 include_once("%s");
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();
378 ?'
379 . '>');
380
381 /**
382 * Synthétise une balise dynamique : crée l'appel à l'inclusion
383 * en transmettant les arguments calculés et le contexte de compilation.
384 *
385 * @uses argumenter_squelette() Pour calculer les arguments de l'inclusion
386 *
387 * @param string $nom
388 * Nom de la balise dynamique
389 * @param array $args
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
395 * @return string
396 * Code PHP pour inclure le squelette de la balise dynamique
397 **/
398 function synthetiser_balise_dynamique($nom, $args, $file, $context_compil) {
399 if (strncmp($file, "/", 1) !== 0) {
400 $file = './" . _DIR_RACINE . "' . $file;
401 }
402
403 $lang = $context_compil[4];
404 if (preg_match(",\W,", $lang)) {
405 $lang = '';
406 }
407
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].')';
411 }
412 $args = join(', ', $args);
413
414 $r = sprintf(CODE_INCLURE_BALISE,
415 $file,
416 $lang,
417 $nom,
418 $args,
419 join(', ', array_map('_q', $context_compil)));
420
421 return $r;
422 }
423
424 /**
425 * Crée le code PHP pour transmettre des arguments (généralement pour une inclusion)
426 *
427 * @param array|string $v
428 * Arguments à transmettre :
429 *
430 * - string : un simple texte à faire écrire
431 * - array : couples ('nom' => 'valeur') liste des arguments et leur valeur
432 * @return string
433 *
434 * - Code PHP créant le tableau des arguments à transmettre,
435 * - ou texte entre quote `'` (si `$v` était une chaîne)
436 **/
437 function argumenter_squelette($v) {
438
439 if (!is_array($v)) {
440 return "'" . texte_script($v) . "'";
441 } else {
442 $out = array();
443 foreach ($v as $k => $val) {
444 $out [] = argumenter_squelette($k) . '=>' . argumenter_squelette($val);
445 }
446
447 return 'array(' . join(", ", $out) . ')';
448 }
449 }
450
451
452 /**
453 * Calcule et retourne le code PHP retourné par l'exécution d'une balise
454 * dynamique.
455 *
456 * Vérifier les arguments et filtres et calcule le code PHP à inclure.
457 *
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.
465 *
466 * @uses synthetiser_balise_dynamique()
467 * Pour calculer le code PHP d'inclusion produit
468 *
469 * @param string $nom
470 * Nom de la balise dynamique
471 * @param array $args
472 * Liste des arguments calculés de la balise
473 * @param array $context_compil
474 * Tableau d'informations sur la compilation
475 * @return string
476 * Code PHP d'exécutant l'inclusion du squelette (ou texte) de la balise dynamique
477 **/
478 function executer_balise_dynamique($nom, $args, $context_compil) {
479 $nomfonction = $nom;
480 $nomfonction_generique = "";
481
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;
490 }
491
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);
495 }
496
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;
502 }
503 }
504
505 if (!$fonction_balise) {
506 $msg = array('zbug_balise_inexistante', array('from' => 'CVT', 'balise' => $nom));
507 erreur_squelette($msg, $context_compil);
508
509 return '';
510 }
511
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));
519 }
520
521 // Y a-t-il une fonction de traitement des arguments ?
522 $f = 'balise_' . $nomfonction . '_stat';
523
524 $r = !function_exists($f) ? $args : $f($args, $context_compil);
525
526 if (!is_array($r)) {
527 return $r;
528 }
529
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')
536 ) {
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;
542 }
543 } else {
544 $msg = array('zbug_balise_inexistante', array('from' => 'CVT', 'balise' => $nom));
545 erreur_squelette($msg, $context_compil);
546
547 return '';
548 }
549 }
550
551 if ($appel_php_depuis_modele) {
552 $context_compil['appel_php_depuis_modele'] = true;
553 }
554 return synthetiser_balise_dynamique($nomfonction, $r, $file, $context_compil);
555
556 }
557
558 /**
559 * Retourne pour une clé primaire d'objet donnée les identifiants ayant un logo
560 *
561 * @uses type_du_logo() Pour calculer le nom du logo
562 *
563 * @param string $type
564 * Nom de la clé primaire de l'objet
565 * @return string
566 * Liste des identifiants ayant un logo (séparés par une virgule)
567 **/
568 function lister_objets_avec_logos($type) {
569
570 $logos = array();
571 $chercher_logo = charger_fonction('chercher_logo', 'inc');
572 $type = '/'
573 . type_du_logo($type)
574 . "on(\d+)\.("
575 . join('|', $GLOBALS['formats_logos'])
576 . ")$/";
577
578 if ($d = opendir(_DIR_LOGOS)) {
579 while (($f = readdir($d)) !== false) {
580 if (preg_match($type, $f, $r)) {
581 $logos[] = $r[1];
582 }
583 }
584 }
585 @closedir($d);
586
587 return join(',', $logos);
588 }
589
590
591 /**
592 * Renvoie l'état courant des notes, le purge et en prépare un nouveau
593 *
594 * Fonction appelée par la balise `#NOTES`
595 *
596 * @see balise_NOTES_dist()
597 * @uses inc_notes_dist()
598 *
599 * @return string
600 * Code HTML des notes
601 **/
602 function calculer_notes() {
603 $r = '';
604 if ($notes = charger_fonction('notes', 'inc', true)) {
605 $r = $notes(array());
606 $notes('', 'depiler');
607 $notes('', 'empiler');
608 }
609
610 return $r;
611 }
612
613 /**
614 * Selectionner la langue de l'objet dans la boucle
615 *
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 !
618 *
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.
623 *
624 * @param string $lang
625 * Langue de l'objet
626 * @param string $lang_select
627 * 'oui' si critère lang_select est présent, '' sinon.
628 * @param null|string $titre
629 * Titre de l'objet
630 * @return null;
631 **/
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'
636 ) {
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
646 ) {
647 $lang = $GLOBALS['spip_lang'];
648 }
649
650 // faire un lang_select() eventuellement sur la langue inchangee
651 lang_select($lang);
652
653 return;
654 }
655
656
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) {
662 $n = '';
663 foreach (explode(',', $liste) as $val) {
664 if ($a = intval($val) and $val === strval($a)) {
665 $n .= ',' . $val;
666 }
667 }
668 if (strlen($n)) {
669 $envd[$table] = $n;
670 } else {
671 unset($envd[$table]);
672 }
673 }
674
675 return $envd;
676 }
677
678 /**
679 * Cherche la présence d'un opérateur SELF ou SUBSELECT
680 *
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.
684 *
685 * Cherche de manière récursive également dans les autres valeurs si celles-ci
686 * sont des tableaux
687 *
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.
692 **/
693 function match_self($w) {
694 if (is_string($w)) {
695 return false;
696 }
697 if (is_array($w)) {
698 if (in_array(reset($w), array("SELF", "SUBSELECT"))) {
699 return $w;
700 }
701 foreach (array_filter($w, 'is_array') as $sw) {
702 if ($m = match_self($sw)) {
703 return $m;
704 }
705 }
706 }
707
708 return false;
709 }
710
711 /**
712 * Remplace une condition décrivant une sous requête par son code
713 *
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.
722 **/
723 function remplace_sous_requete($w, $sousrequete) {
724 if (is_array($w)) {
725 if (in_array(reset($w), array("SELF", "SUBSELECT"))) {
726 return $sousrequete;
727 }
728 foreach ($w as $k => $sw) {
729 $w[$k] = remplace_sous_requete($sw, $sousrequete);
730 }
731 }
732
733 return $w;
734 }
735
736 /**
737 * Sépare les conditions de boucles simples de celles possédant des sous-requêtes.
738 *
739 * @param array $where
740 * Description d'une condition WHERE de boucle
741 * @return array
742 * Liste de 2 tableaux :
743 * - Conditions simples (ne possédant pas de sous requêtes)
744 * - Conditions avec des sous requêtes
745 **/
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;
752 } else {
753 $where_simples[$k] = $w;
754 }
755 }
756
757 return array($where_simples, $where_sous);
758 }
759
760
761 /**
762 * Calcule une requête et l’exécute
763 *
764 * Cette fonction est présente dans les squelettes compilés.
765 * Elle peut permettre de générer des requêtes avec jointure.
766 *
767 * @param array $select
768 * @param array $from
769 * @param array $from_type
770 * @param array $where
771 * @param array $join
772 * @param array $groupby
773 * @param array $orderby
774 * @param string $limit
775 * @param array $having
776 * @param string $table
777 * @param string $id
778 * @param string $serveur
779 * @param bool $requeter
780 * @return resource
781 */
782 function calculer_select(
783 $select = array(),
784 $from = array(),
785 $from_type = array(),
786 $where = array(),
787 $join = array(),
788 $groupby = array(),
789 $orderby = array(),
790 $limit = '',
791 $having = array(),
792 $table = '',
793 $id = '',
794 $serveur = '',
795 $requeter = true
796 ) {
797
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)
802 $menage = false;
803 foreach ($where as $k => $v) {
804 if (is_array($v)) {
805 if ((count($v) >= 2) && ($v[0] == 'REGEXP') && ($v[2] == "'.*'")) {
806 $op = false;
807 } elseif ((count($v) >= 2) && ($v[0] == 'LIKE') && ($v[2] == "'%'")) {
808 $op = false;
809 } else {
810 $op = $v[0] ? $v[0] : $v;
811 }
812 } else {
813 $op = $v;
814 }
815 if ((!$op) or ($op == 1) or ($op == '0=0')) {
816 unset($where[$k]);
817 $menage = true;
818 }
819 }
820
821 // evacuer les eventuels groupby vide issus d'un calcul dynamique
822 $groupby = array_diff($groupby, array(''));
823
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) {
827 $menage = true;
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]);
833 $wheresub = array(
834 $sous[2],
835 '0=0'
836 ); // pour accepter une string et forcer a faire le menage car on a surement simplifie select et where
837 $jsub = $join;
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
842 $i = 0;
843 do {
844 $where[$k] = remplace_sous_requete($w, "(" . calculer_select(
845 array($sous[1] . " AS id"),
846 $from,
847 $from_type,
848 $wheresub,
849 $jsub,
850 array(), array(), '',
851 $having, $table, $id, $serveur, false) . ")");
852 if (!$i) {
853 $i = 1;
854 $wherestring = calculer_where_to_string($where[$k]);
855 foreach ($join as $cle => $wj) {
856 if (count($wj) == 4
857 and strpos($wherestring, "{$cle}.") !== false
858 ) {
859 $i = 0;
860 $wheresub[] = $wj[3];
861 unset($jsub[$cle][3]);
862 }
863 }
864 }
865 } while ($i++ < 1);
866 }
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(
871 $sous[1], # select
872 $sous[2], #from
873 array(), #from_type
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
876 array(), #join
877 $sous[4] ? $sous[4] : array(), #groupby
878 $sous[5] ? $sous[5] : array(), #orderby
879 $sous[6], #limit
880 $sous[7] ? $sous[7] : array(), #having
881 $table, $id, $serveur, false
882 ) . ")");
883 }
884 array_pop($where_simples);
885 }
886
887 foreach ($having as $k => $v) {
888 if ((!$v) or ($v == 1) or ($v == '0=0')) {
889 unset($having[$k]);
890 }
891 }
892
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
897
898 $afrom = array();
899 $equiv = array();
900 $k = count($join);
901 foreach (array_reverse($join, true) as $cledef => $j) {
902 $cle = $cledef;
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];
908 }
909 if (count($join[$cle]) == 3) {
910 $join[$cle][] = '';
911 }
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)) {
916 $cle = "L$k";
917 }
918 if (!$menage
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)
924 ) {
925 // corriger les references non explicites dans select
926 // ou groupby
927 foreach ($select as $i => $s) {
928 if ($s == $c) {
929 $select[$i] = "$cle.$c AS $c";
930 break;
931 }
932 }
933 foreach ($groupby as $i => $g) {
934 if ($g == $c) {
935 $groupby[$i] = "$cle.$c";
936 break;
937 }
938 }
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(
943 "\n" .
944 (isset($from_type[$cle]) ? $from_type[$cle] : "INNER") . " JOIN",
945 $from[$cle],
946 "AS $cle",
947 "ON (",
948 "$cle.$c",
949 "=",
950 "$t.$carr",
951 ($and ? "AND " . $and : "") .
952 ")"
953 );
954 if (isset($afrom[$cle])) {
955 $afrom[$t] = $afrom[$t] + $afrom[$cle];
956 unset($afrom[$cle]);
957 }
958 $equiv[] = $carr;
959 } else {
960 unset($join[$cledef]);
961 }
962 unset($from[$cle]);
963 $k--;
964 }
965
966 if (count($afrom)) {
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>
971 // ou dans
972 //<BOUCLE8(HIERARCHIE){id_rubrique}{tout}{type='Squelette'}{inverse}{0,1}{lang_select=non} />#TOTAL_BOUCLE<//B8>
973 // qui comporte plusieurs jointures
974 // ou dans
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>
981
982 list($t, $c) = each($from);
983 reset($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))
992 && count($afrom[$t])
993 ) {
994 reset($afrom[$t]);
995 list($nt, $nfrom) = each($afrom[$t]);
996 unset($from[$t]);
997 $from[$nt] = $nfrom[1];
998 unset($afrom[$t][$nt]);
999 $afrom[$nt] = $afrom[$t];
1000 unset($afrom[$t]);
1001 $e = '/\b' . preg_quote($nfrom[6]) . '\b/';
1002 $t = $nfrom[4];
1003 $alias = "";
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)) {
1014 $alias = "";
1015 } else {
1016 $alias = ", " . $nfrom[4] . " AS $oldcle";
1017 }
1018 }
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);
1025 }
1026 $from = reinjecte_joint($afrom, $from);
1027 }
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']);
1032
1033 return $r;
1034 }
1035
1036 /**
1037 * Analogue a calculer_mysql_expression et autre (a unifier ?)
1038 *
1039 * @param string|array $v
1040 * @param string $join
1041 * @return string
1042 */
1043 function calculer_where_to_string($v, $join = 'AND') {
1044 if (empty($v)) {
1045 return '';
1046 }
1047
1048 if (!is_array($v)) {
1049 return $v;
1050 } else {
1051 $exp = "";
1052 if (strtoupper($join) === 'AND') {
1053 return $exp . join(" $join ", array_map('calculer_where_to_string', $v));
1054 } else {
1055 return $exp . join($join, $v);
1056 }
1057 }
1058 }
1059
1060
1061 //condition suffisante (mais non necessaire) pour qu'une table soit utile
1062
1063 // http://code.spip.net/@calculer_jointnul
1064 function calculer_jointnul($cle, $exp, $equiv = '') {
1065 if (!is_array($exp)) {
1066 if ($equiv) {
1067 $exp = preg_replace($equiv, '', $exp);
1068 }
1069
1070 return preg_match("/\\b$cle\\./", $exp);
1071 } else {
1072 foreach ($exp as $v) {
1073 if (calculer_jointnul($cle, $v, $equiv)) {
1074 return true;
1075 }
1076 }
1077
1078 return false;
1079 }
1080 }
1081
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]);
1090 }
1091 $from_synth["$k@"] = implode(' ', $afrom[$k]);
1092 unset($afrom[$k]);
1093 }
1094 }
1095
1096 return $from_synth;
1097 }
1098
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);
1103 } else {
1104 foreach ($exp as $k => $v) {
1105 $exp[$k] = remplacer_jointnul($cle, $v, $equiv);
1106 }
1107
1108 return $exp;
1109 }
1110 }
1111
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));
1118 }
1119
1120 return $mime_type
1121 . (!$connect ? '' : preg_replace('/\W/', "_", $connect)) . '_'
1122 . md5($GLOBALS['spip_version_code'] . ' * ' . $skel . (isset($GLOBALS['marqueur_skel']) ? '*' . $GLOBALS['marqueur_skel'] : ''));
1123 }