[SPIP] v3.2.1-->v3.2.2
[lhc/web/www.git] / www / ecrire / public / compiler.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 /**
15 * Fichier principal du compilateur de squelettes
16 *
17 * @package SPIP\Core\Compilateur\Compilation
18 **/
19
20 if (!defined('_ECRIRE_INC_VERSION')) {
21 return;
22 }
23
24 /** Repérer un code ne calculant rien, meme avec commentaire */
25 define('CODE_MONOTONE', ",^(\n//[^\n]*\n)?\(?'([^'])*'\)?$,");
26 /** Indique s'il faut commenter le code produit */
27 define('CODE_COMMENTE', true);
28
29 // definition des structures de donnees
30 include_spip('public/interfaces');
31
32 // Definition de la structure $p, et fonctions de recherche et de reservation
33 // dans l'arborescence des boucles
34 include_spip('public/references');
35
36 // production du code qui peut etre securisee
37 include_spip('public/sandbox');
38
39 // definition des boucles
40 include_spip('public/boucles');
41
42 // definition des criteres
43 include_spip('public/criteres');
44
45 // definition des balises
46 include_spip('public/balises');
47
48 // Gestion des jointures
49 include_spip('public/jointures');
50
51 // Les 2 ecritures INCLURE{A1,A2,A3...} et INCLURE(A1){A2}{A3}... sont admises
52 // Preferer la premiere.
53 // Les Ai sont de la forme Vi=Ei ou bien Vi qui veut alors dire Vi=Vi
54 // Le resultat est un tableau indexe par les Vi
55 // Toutefois, si le premier argument n'est pas de la forme Vi=Ei
56 // il est conventionnellement la valeur de l'index 1.
57 // pour la balise #INCLURE
58 // mais pas pour <INCLURE> dont le fond est defini explicitement.
59
60
61 // http://code.spip.net/@argumenter_inclure
62 function argumenter_inclure(
63 $params,
64 $rejet_filtres,
65 $p,
66 &$boucles,
67 $id_boucle,
68 $echap = true,
69 $lang = '',
70 $fond1 = false
71 ) {
72 $l = array();
73 $erreur_p_i_i = '';
74 if (!is_array($params)) {
75 return $l;
76 }
77 foreach ($params as $k => $couple) {
78 // la liste d'arguments d'inclusion peut se terminer par un filtre
79 $filtre = array_shift($couple);
80 if ($filtre) {
81 break;
82 }
83 foreach ($couple as $n => $val) {
84 $var = $val[0];
85 if ($var->type != 'texte') {
86 if ($n or $k or $fond1) {
87 $erreur_p_i_i = array(
88 'zbug_parametres_inclus_incorrects',
89 array('param' => $var->nom_champ)
90 );
91 erreur_squelette($erreur_p_i_i, $p);
92 break;
93 } else {
94 $l[1] = calculer_liste($val, $p->descr, $boucles, $id_boucle);
95 }
96 } else {
97 preg_match(",^([^=]*)(=?)(.*)$,m", $var->texte, $m);
98 $m = array_pad($m, 3, null);
99 $var = $m[1];
100 $auto = false;;
101 if ($m[2]) {
102 $v = $m[3];
103 if (preg_match(',^[\'"](.*)[\'"]$,', $v, $m)) {
104 $v = $m[1];
105 }
106 $val[0] = new Texte;
107 $val[0]->texte = $v;
108 } elseif ($k or $n or $fond1) {
109 $auto = true;
110 } else {
111 $var = 1;
112 }
113
114 if ($var == 'lang') {
115 $lang = !$auto
116 ? calculer_liste($val, $p->descr, $boucles, $id_boucle)
117 : '$GLOBALS["spip_lang"]';
118 } else {
119 $val = $auto
120 ? index_pile($id_boucle, $var, $boucles)
121 : calculer_liste($val, $p->descr, $boucles, $id_boucle);
122 if ($var !== 1) {
123 $val = ($echap ? "\'$var\' => ' . argumenter_squelette(" : "'$var' => ")
124 . $val . ($echap ? ") . '" : " ");
125 } else {
126 $val = $echap ? "'.$val.'" : $val;
127 }
128 $l[$var] = $val;
129 }
130 }
131 }
132 }
133 if ($erreur_p_i_i) {
134 return false;
135 }
136 // Cas particulier de la langue : si {lang=xx} est definie, on
137 // la passe, sinon on passe la langue courante au moment du calcul
138 // sauf si on n'en veut pas
139 if ($lang === false) {
140 return $l;
141 }
142 if (!$lang) {
143 $lang = '$GLOBALS["spip_lang"]';
144 }
145 $l['lang'] = ($echap ? "\'lang\' => ' . argumenter_squelette(" : "'lang' => ") . $lang . ($echap ? ") . '" : " ");
146
147 return $l;
148 }
149
150 /**
151 * Code d'appel à un <INCLURE()>
152 *
153 * Code PHP pour un squelette (aussi pour #INCLURE, #MODELE #LES_AUTEURS)
154 */
155 define('CODE_RECUPERER_FOND', 'recuperer_fond(%s, %s, array(%s), %s)');
156
157 /**
158 * Compile une inclusion <INCLURE> ou #INCLURE
159 *
160 * @param Inclure $p
161 * Description de l'inclusion (AST au niveau de l'inclure)
162 * @param array $boucles
163 * AST du squelette
164 * @param string $id_boucle
165 * Identifiant de la boucle contenant l'inclure
166 * @return string
167 * Code PHP appelant l'inclusion
168 **/
169 function calculer_inclure($p, &$boucles, $id_boucle) {
170
171 $_contexte = argumenter_inclure($p->param, false, $p, $boucles, $id_boucle, true, '', true);
172 if (is_string($p->texte)) {
173 $fichier = $p->texte;
174 $code = "\"".str_replace('"','\"',$fichier)."\"";
175
176 } else {
177 $code = calculer_liste($p->texte, $p->descr, $boucles, $id_boucle);
178 if ($code and preg_match("/^'([^']*)'/s", $code, $r)) {
179 $fichier = $r[1];
180 } else {
181 $fichier = '';
182 }
183 }
184 if (!$code or $code === '""') {
185 $erreur_p_i_i = array(
186 'zbug_parametres_inclus_incorrects',
187 array('param' => $code)
188 );
189 erreur_squelette($erreur_p_i_i, $p);
190
191 return false;
192 }
193 $compil = texte_script(memoriser_contexte_compil($p));
194
195 if (is_array($_contexte)) {
196 // Critere d'inclusion {env} (et {self} pour compatibilite ascendante)
197 if ($env = (isset($_contexte['env']) || isset($_contexte['self']))) {
198 unset($_contexte['env']);
199 }
200
201 // noter les doublons dans l'appel a public.php
202 if (isset($_contexte['doublons'])) {
203 $_contexte['doublons'] = "\\'doublons\\' => '.var_export(\$doublons,true).'";
204 }
205
206 if ($ajax = isset($_contexte['ajax'])) {
207 $ajax = preg_replace(",=>(.*)$,ims", '=> ($v=(\\1))?$v:true', $_contexte['ajax']);
208 unset($_contexte['ajax']);
209 }
210
211 $_contexte = join(",\n\t", $_contexte);
212 } else {
213 return false;
214 } // j'aurais voulu toucher le fond ...
215
216 $contexte = 'array(' . $_contexte . ')';
217
218 if ($env) {
219 $contexte = "array_merge('.var_export(\$Pile[0],1).',$contexte)";
220 }
221
222 // s'il y a une extension .php, ce n'est pas un squelette
223 if ($fichier and preg_match('/^.+[.]php$/s', $fichier)) {
224 $code = sandbox_composer_inclure_php($fichier, $p, $contexte);
225 } else {
226 $_options[] = "\"compil\"=>array($compil)";
227 if ($ajax) {
228 $_options[] = $ajax;
229 }
230 $code = " ' . argumenter_squelette($code) . '";
231 $code = "echo " . sprintf(CODE_RECUPERER_FOND, $code, $contexte, implode(',', $_options),
232 "_request(\"connect\")") . ';';
233 }
234
235 return "\n'<'.'" . "?php " . $code . "\n?'." . "'>'";
236 }
237
238
239 /**
240 * Gérer les statuts declarés pour cette table
241 *
242 * S'il existe des statuts sur cette table, déclarés dans la description
243 * d'un objet éditorial, applique leurs contraintes
244 *
245 * @param Boucle $boucle
246 * Descrition de la boucle
247 * @param bool $echapper
248 * true pour échapper le code créé
249 * @param bool $ignore_previsu
250 * true pour ne tester que le cas publie et ignorer l'eventuel var_mode=preview de la page
251 */
252 function instituer_boucle(&$boucle, $echapper = true, $ignore_previsu = false) {
253 /*
254 $show['statut'][] = array(
255 'champ'=>'statut', // champ de la table sur lequel porte le filtrage par le statut
256 'publie'=>'publie', // valeur ou liste de valeurs, qui definissent l'objet comme publie.
257 'previsu'=>'publie,prop', // valeur ou liste de valeurs qui sont visibles en previsu
258 'post_date'=>'date', // un champ de date pour la prise en compte des post_dates, ou rien sinon
259 'exception'=>'statut', // liste des modificateurs qui annulent le filtrage par statut
260 // si plusieurs valeurs : array('statut','tout','lien')
261 );
262
263 Pour 'publier' ou 'previsu', si la chaine commence par un "!" on exclu au lieu de filtrer sur les valeurs donnees
264 si la chaine est vide, on ne garde rien si elle est seulement "!" on n'exclu rien
265
266 Si le statut repose sur une jointure, 'champ' est alors un tableau du format suivant :
267 'champ'=>array(
268 array(table1, cle1),
269 ...
270 array(tablen, clen),
271 champstatut
272 )
273
274 champstatut est alors le champ statut sur la tablen
275 dans les jointures, clen peut etre un tableau pour une jointure complexe : array('id_objet','id_article','objet','article')
276 */
277 $id_table = $boucle->id_table;
278 $show = $boucle->show;
279 if (isset($show['statut']) and $show['statut']) {
280 foreach ($show['statut'] as $k => $s) {
281 // Restreindre aux elements publies si pas de {statut} ou autre dans les criteres
282 $filtrer = true;
283 if (isset($s['exception'])) {
284 foreach (is_array($s['exception']) ? $s['exception'] : array($s['exception']) as $m) {
285 if (isset($boucle->modificateur[$m]) or isset($boucle->modificateur['criteres'][$m])) {
286 $filtrer = false;
287 break;
288 }
289 }
290 }
291
292 if ($filtrer) {
293 if (is_array($s['champ'])) {
294 $statut = preg_replace(',\W,', '', array_pop($s['champ'])); // securite
295 $jointures = array();
296 // indiquer la description de chaque table dans le tableau de jointures,
297 // ce qui permet d'eviter certains GROUP BY inutiles.
298 $trouver_table = charger_fonction('trouver_table', 'base');
299 foreach ($s['champ'] as $j) {
300 $id = reset($j);
301 $def = $trouver_table($id);
302 $jointures[] = array('', array($id, $def), end($j));
303 }
304 $jointures[0][0] = $id_table;
305 if (!array_search($id, $boucle->from)) {
306 include_spip('public/jointures');
307 fabrique_jointures($boucle, $jointures, true, $boucle->show, $id_table, '', $echapper);
308 }
309 // trouver l'alias de la table d'arrivee qui porte le statut
310 $id = array_search($id, $boucle->from);
311 } else {
312 $id = $id_table;
313 $statut = preg_replace(',\W,', '', $s['champ']); // securite
314 }
315 $mstatut = $id . '.' . $statut;
316
317 $arg_ignore_previsu = ($ignore_previsu ? ",true" : '');
318 include_spip('public/quete');
319 if (isset($s['post_date']) and $s['post_date']
320 and $GLOBALS['meta']["post_dates"] == 'non'
321 ) {
322 $date = $id . '.' . preg_replace(',\W,', '', $s['post_date']); // securite
323 array_unshift($boucle->where,
324 $echapper ?
325 "\nquete_condition_postdates('$date'," . _q($boucle->sql_serveur) . "$arg_ignore_previsu)"
326 :
327 quete_condition_postdates($date, $boucle->sql_serveur, $ignore_previsu)
328 );
329 }
330 array_unshift($boucle->where,
331 $echapper ?
332 "\nquete_condition_statut('$mstatut',"
333 . _q($s['previsu']) . ","
334 . _q($s['publie']) . ","
335 . _q($boucle->sql_serveur) . "$arg_ignore_previsu)"
336 :
337 quete_condition_statut($mstatut, $s['previsu'], $s['publie'], $boucle->sql_serveur, $ignore_previsu)
338 );
339 }
340 }
341 }
342 }
343
344 /**
345 * Produit le corps PHP d'une boucle Spip.
346 *
347 * Ce corps remplit une variable $t0 retournée en valeur.
348 * Ici on distingue boucles recursives et boucle à requête SQL
349 * et on insère le code d'envoi au debusqueur du resultat de la fonction.
350 *
351 * @param string $id_boucle
352 * Identifiant de la boucle
353 * @param array $boucles
354 * AST du squelette
355 * @return string
356 * Code PHP compilé de la boucle
357 */
358 function calculer_boucle($id_boucle, &$boucles) {
359
360 $boucle = &$boucles[$id_boucle];
361 instituer_boucle($boucle);
362 $boucles[$id_boucle] = pipeline('post_boucle', $boucles[$id_boucle]);
363
364 // en mode debug memoriser les premiers passages dans la boucle,
365 // mais pas tous, sinon ca pete.
366 if (_request('var_mode_affiche') != 'resultat') {
367 $trace = '';
368 } else {
369 $trace = $boucles[$id_boucle]->descr['nom'] . $id_boucle;
370 $trace = "if (count(@\$GLOBALS['debug_objets']['resultat']['$trace'])<3)
371 \$GLOBALS['debug_objets']['resultat']['$trace'][] = \$t0;";
372 }
373
374 return ($boucles[$id_boucle]->type_requete == TYPE_RECURSIF)
375 ? calculer_boucle_rec($id_boucle, $boucles, $trace)
376 : calculer_boucle_nonrec($id_boucle, $boucles, $trace);
377 }
378
379
380 /**
381 * Compilation d'une boucle recursive.
382 *
383 * @internal
384 * Il suffit (ET IL FAUT) sauvegarder les valeurs des arguments passes par
385 * reference, car par definition un tel passage ne les sauvegarde pas
386 *
387 * @param string $id_boucle
388 * Identifiant de la boucle
389 * @param array $boucles
390 * AST du squelette
391 * @param string $trace
392 * Code PHP (en mode debug uniquement) servant à conserver une
393 * trace des premières valeurs de la boucle afin de pouvoir
394 * les afficher dans le débugueur ultérieurement
395 * @return string
396 * Code PHP compilé de la boucle récursive
397 **/
398 function calculer_boucle_rec($id_boucle, &$boucles, $trace) {
399 $nom = $boucles[$id_boucle]->param[0];
400
401 return
402 // Numrows[$nom] peut ne pas être encore defini
403 "\n\t\$save_numrows = (isset(\$Numrows['$nom']) ? \$Numrows['$nom'] : array());"
404 . "\n\t\$t0 = " . $boucles[$id_boucle]->return . ";"
405 . "\n\t\$Numrows['$nom'] = (\$save_numrows);"
406 . $trace
407 . "\n\treturn \$t0;";
408 }
409
410 /**
411 * Compilation d'une boucle non recursive.
412 *
413 * La constante donne le cadre systématique du code:
414 *
415 * - %s1: initialisation des arguments de calculer_select
416 * - %s2: appel de calculer_select en donnant un contexte pour les cas d'erreur
417 * - %s3: initialisation du sous-tableau Numrows[id_boucle]
418 * - %s4: sauvegarde de la langue et calcul des invariants de boucle sur elle
419 * - %s5: boucle while sql_fetch ou str_repeat si corps monotone
420 * - %s6: restauration de la langue
421 * - %s7: liberation de la ressource, en tenant compte du serveur SQL
422 * - %s8: code de trace eventuel avant le retour
423 **/
424 define('CODE_CORPS_BOUCLE', '%s
425 if (defined("_BOUCLE_PROFILER")) $timer = time()+(float)microtime();
426 $t0 = "";
427 // REQUETE
428 $iter = IterFactory::create(
429 "%s",
430 %s,
431 array(%s)
432 );
433 if (!$iter->err()) {
434 %s%s$SP++;
435 // RESULTATS
436 %s
437 %s$iter->free();
438 }%s
439 if (defined("_BOUCLE_PROFILER")
440 AND 1000*($timer = (time()+(float)microtime())-$timer) > _BOUCLE_PROFILER)
441 spip_log(intval(1000*$timer)."ms %s","profiler"._LOG_AVERTISSEMENT);
442 return $t0;'
443 );
444
445 /**
446 * Compilation d'une boucle (non recursive).
447 *
448 * @param string $id_boucle
449 * Identifiant de la boucle
450 * @param array $boucles
451 * AST du squelette
452 * @param string $trace
453 * Code PHP (en mode debug uniquement) servant à conserver une
454 * trace des premières valeurs de la boucle afin de pouvoir
455 * les afficher dans le débugueur ultérieurement
456 * @return string
457 * Code PHP compilé de la boucle récursive
458 **/
459 function calculer_boucle_nonrec($id_boucle, &$boucles, $trace) {
460
461 $boucle = &$boucles[$id_boucle];
462 $return = $boucle->return;
463 $type_boucle = $boucle->type_requete;
464 $primary = $boucle->primary;
465 $constant = preg_match(CODE_MONOTONE, str_replace("\\'", '', $return));
466 $flag_cpt = $boucle->mode_partie || $boucle->cptrows;
467 $corps = '';
468
469 // faudrait expanser le foreach a la compil, car y en a souvent qu'un
470 // et puis faire un [] plutot qu'un "','."
471 if ($boucle->doublons) {
472 $corps .= "\n\t\t\tforeach(" . $boucle->doublons . ' as $k) $doublons[$k] .= "," . ' .
473 index_pile($id_boucle, $primary, $boucles)
474 . "; // doublons\n";
475 }
476
477 // La boucle doit-elle selectionner la langue ?
478 // - par defaut, les boucles suivantes le font
479 // (sauf si forcer_lang==true ou si le titre contient <multi>).
480 // - a moins d'une demande explicite via {!lang_select}
481 if (!$constant && $boucle->lang_select != 'non' &&
482 (($boucle->lang_select == 'oui') ||
483 in_array($type_boucle, array(
484 'articles',
485 'rubriques',
486 'hierarchie',
487 'breves'
488 )))
489 ) {
490 // Memoriser la langue avant la boucle et la restituer apres
491 // afin que le corps de boucle affecte la globale directement
492 $init_lang = "lang_select(\$GLOBALS['spip_lang']);\n\t";
493 $fin_lang = "lang_select();\n\t";
494 $fin_lang_select_public = "\n\t\tlang_select();";
495
496 $corps .=
497 "\n\t\tlang_select_public("
498 . index_pile($id_boucle, 'lang', $boucles)
499 . ", '" . $boucle->lang_select . "'"
500 . (in_array($type_boucle, array(
501 'articles',
502 'rubriques',
503 'hierarchie',
504 'breves'
505 )) ? ', ' . index_pile($id_boucle, 'titre', $boucles) : '')
506 . ');';
507 } else {
508 $init_lang = '';
509 $fin_lang = '';
510 $fin_lang_select_public = '';
511 // sortir les appels au traducteur (invariants de boucle)
512 if (strpos($return, '?php') === false
513 and preg_match_all("/\W(_T[(]'[^']*'[)])/", $return, $r)
514 ) {
515 $i = 1;
516 foreach ($r[1] as $t) {
517 $init_lang .= "\n\t\$l$i = $t;";
518 $return = str_replace($t, "\$l$i", $return);
519 $i++;
520 }
521 }
522 }
523
524 // gestion optimale des separateurs et des boucles constantes
525 if (count($boucle->separateur)) {
526 $code_sep = ("'" . str_replace("'", "\'", join('', $boucle->separateur)) . "'");
527 }
528
529 $corps .=
530 ((!$boucle->separateur) ?
531 (($constant && !$corps && !$flag_cpt) ? $return :
532 (($return === "''") ? '' :
533 ("\n\t\t" . '$t0 .= ' . $return . ";"))) :
534 ("\n\t\t\$t1 " .
535 ((strpos($return, '$t1.') === 0) ?
536 (".=" . substr($return, 4)) :
537 ('= ' . $return)) .
538 ";\n\t\t" .
539 '$t0 .= ((strlen($t1) && strlen($t0)) ? ' . $code_sep . " : '') . \$t1;"));
540
541 // Calculer les invalideurs si c'est une boucle non constante et si on
542 // souhaite invalider ces elements
543 if (!$constant and $primary) {
544 include_spip('inc/invalideur');
545 if (function_exists($i = 'calcul_invalideurs')) {
546 $corps = $i($corps, $primary, $boucles, $id_boucle);
547 }
548 }
549
550 // gerer le compteur de boucle
551 // avec ou sans son utilisation par les criteres {1/3} {1,4} {n-2,1}...
552
553 if ($boucle->partie or $boucle->cptrows) {
554 $corps = "\n\t\t\$Numrows['$id_boucle']['compteur_boucle']++;"
555 . $boucle->partie
556 . $corps;
557 }
558
559 // depiler la lang de la boucle si besoin
560 $corps .= $fin_lang_select_public;
561
562 // si le corps est une constante, ne pas appeler le serveur N fois!
563
564 if (preg_match(CODE_MONOTONE, str_replace("\\'", '', $corps), $r)) {
565 if (!isset($r[2]) or (!$r[2])) {
566 if (!$boucle->numrows) {
567 return "\n\t\$t0 = '';";
568 } else {
569 $corps = "";
570 }
571 } else {
572 $boucle->numrows = true;
573 $corps = "\n\t\$t0 = str_repeat($corps, \$Numrows['$id_boucle']['total']);";
574 }
575 } else {
576 $corps = "while (\$Pile[\$SP]=\$iter->fetch()) {\n$corps\n }";
577 }
578
579 $count = '';
580 if (!$boucle->select) {
581 if (!$boucle->numrows or $boucle->limit or $boucle->mode_partie or $boucle->group) {
582 $count = '1';
583 } else {
584 $count = 'count(*)';
585 }
586 $boucles[$id_boucle]->select[] = $count;
587 }
588
589 if ($flag_cpt) {
590 $nums = "\n\t// COMPTEUR\n\t"
591 . "\$Numrows['$id_boucle']['compteur_boucle'] = 0;\n\t";
592 } else {
593 $nums = '';
594 }
595
596 if ($boucle->numrows or $boucle->mode_partie) {
597 $nums .= "\$Numrows['$id_boucle']['total'] = @intval(\$iter->count());"
598 . $boucle->mode_partie
599 . "\n\t";
600 }
601
602 // Ne calculer la requete que maintenant
603 // car ce qui precede appelle index_pile qui influe dessus
604
605 $init = (($init = $boucles[$id_boucle]->doublons)
606 ? ("\n\t$init = array();") : '')
607 . calculer_requete_sql($boucles[$id_boucle]);
608
609 $contexte = memoriser_contexte_compil($boucle);
610
611 $a = sprintf(CODE_CORPS_BOUCLE,
612 $init,
613 $boucle->iterateur,
614 "\$command",
615 $contexte,
616 $nums,
617 $init_lang,
618 $corps,
619 $fin_lang,
620 $trace,
621 'BOUCLE' . $id_boucle . ' @ ' . ($boucle->descr['sourcefile'])
622 );
623
624 # var_dump($a);exit;
625 return $a;
626 }
627
628
629 /**
630 * Calcule le code PHP d'une boucle contenant les informations qui produiront une requête SQL
631 *
632 * Le code produit est un tableau associatif $command contenant les informations
633 * pour que la boucle produise ensuite sa requête, tel que `$command['from'] = 'spip_articles';`
634 *
635 * @param Boucle $boucle
636 * AST de la boucle
637 * @return string
638 * Code PHP compilé définissant les informations de requête
639 **/
640 function calculer_requete_sql($boucle) {
641 $init = array();
642 $init[] = calculer_dec('table', "'" . $boucle->id_table . "'");
643 $init[] = calculer_dec('id', "'" . $boucle->id_boucle . "'");
644 # En absence de champ c'est un decompte :
645 $init[] = calculer_dec('from', calculer_from($boucle));
646 $init[] = calculer_dec('type', calculer_from_type($boucle));
647 $init[] = calculer_dec('groupby',
648 'array(' . (($g = join("\",\n\t\t\"", $boucle->group)) ? '"' . $g . '"' : '') . ")");
649 $init[] = calculer_dec('select', 'array("' . join("\",\n\t\t\"", $boucle->select) . "\")");
650 $init[] = calculer_dec('orderby', 'array(' . calculer_order($boucle) . ")");
651 $init[] = calculer_dec('where', calculer_dump_array($boucle->where));
652 $init[] = calculer_dec('join', calculer_dump_join($boucle->join));
653 $init[] = calculer_dec('limit',
654 (strpos($boucle->limit, 'intval') === false ?
655 "'" . $boucle->limit . "'"
656 :
657 $boucle->limit));
658 $init[] = calculer_dec('having', calculer_dump_array($boucle->having));
659 $s = $d = "";
660 // l'index 0 de $i indique si l'affectation est statique (contenu)
661 // ou recalculée à chaque passage (vide)
662 foreach ($init as $i) {
663 if (reset($i)) {
664 $s .= "\n\t\t" . end($i);
665 } # statique
666 else {
667 $d .= "\n\t" . end($i);
668 } # dynamique
669 }
670
671 return ($boucle->hierarchie ? "\n\t$boucle->hierarchie" : '')
672 . $boucle->in
673 . $boucle->hash
674 . "\n\t" . 'if (!isset($command[\'table\'])) {'
675 . $s
676 . "\n\t}"
677 . $d;
678 }
679
680 /**
681 * Retourne une chaîne des informations du contexte de compilation
682 *
683 * Retourne la source, le nom, l'identifiant de boucle, la ligne, la langue
684 * de l'élément dans une chaîne.
685 *
686 * @see reconstruire_contexte_compil()
687 *
688 * @param Object $p
689 * Objet de l'AST dont on mémorise le contexte
690 * @return string
691 * Informations du contexte séparés par des virgules,
692 * qui peut être utilisé pour la production d'un tableau array()
693 **/
694 function memoriser_contexte_compil($p) {
695 return join(',', array(
696 _q(isset($p->descr['sourcefile']) ? $p->descr['sourcefile'] : ''),
697 _q(isset($p->descr['nom']) ? $p->descr['nom'] : ''),
698 _q(isset($p->id_boucle) ? $p->id_boucle : null),
699 intval($p->ligne),
700 '$GLOBALS[\'spip_lang\']'
701 ));
702 }
703
704 /**
705 * Reconstruit un contexte de compilation
706 *
707 * Pour un tableau d'information de contexte donné,
708 * retourne un objet Contexte (objet générique de l'AST)
709 * avec ces informations
710 *
711 * @see memoriser_contexte_compil()
712 *
713 * @param array $context_compil
714 * Tableau des informations du contexte
715 * @return Contexte
716 * Objet Contexte
717 **/
718 function reconstruire_contexte_compil($context_compil) {
719 if (!is_array($context_compil)) {
720 return $context_compil;
721 }
722 $p = new Contexte;
723 $p->descr = array(
724 'sourcefile' => $context_compil[0],
725 'nom' => $context_compil[1]
726 );
727 $p->id_boucle = $context_compil[2];
728 $p->ligne = $context_compil[3];
729 $p->lang = $context_compil[4];
730
731 return $p;
732 }
733
734 /**
735 * Calcule le code d'affectation d'une valeur à une commande de boucle
736 *
737 * Décrit le code qui complète le tableau $command qui servira entre autres
738 * à l'itérateur. Pour un nom de commande donnée et un code PHP décrivant
739 * ou récupérant une valeur, on retourne le code PHP qui fait l'affectation.
740 *
741 * L'index 0 du tableau retourné indique, lorsqu'il n'est pas vide, que l'affectation
742 * de la variable pourra être statique (c'est à dire qu'il ne dépend
743 * pas d'une quelconque variable PHP), et donc attribué une fois pour toutes
744 * quelque soit le nombre d'appels de la boucle.
745 *
746 * @param string $nom
747 * Nom de la commande
748 * @param string $val
749 * Code PHP décrivant la valeur à affecter
750 * @return array
751 *
752 * - index 0 : Code pour une affectation statique. Si non rempli, la propriété devra
753 * être ré-affectée à chaque appel de la boucle.
754 * - index 1 : Code de l'affectation
755 **/
756 function calculer_dec($nom, $val) {
757 $static = 'if (!isset($command[\'' . $nom . '\'])) ';
758 // si une variable apparait dans le calcul de la clause
759 // il faut la re-evaluer a chaque passage
760 if (
761 strpos($val, '$') !== false
762 /*
763 OR strpos($val, 'sql_') !== false
764 OR (
765 $test = str_replace(array("array(",'\"',"\'"),array("","",""),$val) // supprimer les array( et les echappements de guillemets
766 AND strpos($test,"(")!==FALSE // si pas de parenthese ouvrante, pas de fonction, on peut sortir
767 AND $test = preg_replace(",'[^']*',UimsS","",$test) // supprimer les chaines qui peuvent contenir des fonctions SQL qui ne genent pas
768 AND preg_match(",\w+\s*\(,UimsS",$test,$regs) // tester la presence de fonctions restantes
769 )*/
770 ) {
771 $static = "";
772 }
773
774 return array($static, '$command[\'' . $nom . '\'] = ' . $val . ';');
775 }
776
777 /**
778 * Calcule l'expression PHP décrivant un tableau complexe (ou une chaîne)
779 *
780 * Lorsqu'un tableau est transmis, reconstruit de quoi créer le tableau
781 * en code PHP (une sorte de var_export) en appelant pour chaque valeur
782 * cette fonction de manière récursive.
783 *
784 * Si le premier index (0) du tableau est "'?'", retourne un code
785 * de test entre les 3 autres valeurs (v1 ? v2 : v3). Les valeurs
786 * pouvant être des tableaux aussi.
787 *
788 * @param mixed $a
789 * Les données dont on veut construire un équivalent de var_export
790 * @return string
791 * Expression PHP décrivant un texte ou un tableau
792 **/
793 function calculer_dump_array($a) {
794 if (!is_array($a)) {
795 return $a;
796 }
797 $res = "";
798 if ($a and $a[0] == "'?'") {
799 return ("(" . calculer_dump_array($a[1]) .
800 " ? " . calculer_dump_array($a[2]) .
801 " : " . calculer_dump_array($a[3]) .
802 ")");
803 } else {
804 foreach ($a as $v) {
805 $res .= ", " . calculer_dump_array($v);
806 }
807
808 return "\n\t\t\tarray(" . substr($res, 2) . ')';
809 }
810 }
811
812 // http://code.spip.net/@calculer_dump_join
813 function calculer_dump_join($a) {
814 $res = "";
815 foreach ($a as $k => $v) {
816 $res .= ", '$k' => array(" . implode(',', $v) . ")";
817 }
818
819 return 'array(' . substr($res, 2) . ')';
820 }
821
822 /**
823 * Calcule l'expression PHP décrivant les informations FROM d'une boucle
824 *
825 * @param Boucle $boucle
826 * Description de la boucle
827 * @return string
828 * Code PHP construisant un tableau des alias et noms des tables du FROM
829 **/
830 function calculer_from(&$boucle) {
831 $res = "";
832 foreach ($boucle->from as $k => $v) {
833 $res .= ",'$k' => '$v'";
834 }
835
836 return 'array(' . substr($res, 1) . ')';
837 }
838
839 /**
840 * Calcule l'expression PHP décrivant des informations de type de jointure
841 * pour un alias de table connu dans le FROM
842 *
843 * @param Boucle $boucle
844 * Description de la boucle
845 * @return string
846 * Code PHP construisant un tableau des alias et type de jointure du FROM
847 **/
848 function calculer_from_type(&$boucle) {
849 $res = "";
850 foreach ($boucle->from_type as $k => $v) {
851 $res .= ",'$k' => '$v'";
852 }
853
854 return 'array(' . substr($res, 1) . ')';
855 }
856
857 // http://code.spip.net/@calculer_order
858 function calculer_order(&$boucle) {
859 if (!$order = $boucle->order
860 and !$order = $boucle->default_order
861 ) {
862 $order = array();
863 }
864
865 /*if (isset($boucle->modificateur['collate'])){
866 $col = "." . $boucle->modificateur['collate'];
867 foreach($order as $k=>$o)
868 if (strpos($order[$k],'COLLATE')===false)
869 $order[$k].= $col;
870 }*/
871
872 return join(', ', $order);
873 }
874
875 // Production du code PHP a partir de la sequence livree par le phraseur
876 // $boucles est passe par reference pour affectation par index_pile.
877 // Retourne une expression PHP,
878 // (qui sera argument d'un Return ou la partie droite d'une affectation).
879
880 // http://code.spip.net/@calculer_liste
881 function calculer_liste($tableau, $descr, &$boucles, $id_boucle = '') {
882 if (!$tableau) {
883 return "''";
884 }
885 if (!isset($descr['niv'])) {
886 $descr['niv'] = 0;
887 }
888 $codes = compile_cas($tableau, $descr, $boucles, $id_boucle);
889 if ($codes === false) {
890 return false;
891 }
892 $n = count($codes);
893 if (!$n) {
894 return "''";
895 }
896 $tab = str_repeat("\t", $descr['niv']);
897 if (_request('var_mode_affiche') != 'validation') {
898 if ($n == 1) {
899 return $codes[0];
900 } else {
901 $res = '';
902 foreach ($codes as $code) {
903 if (!preg_match("/^'[^']*'$/", $code)
904 or substr($res, -1, 1) !== "'"
905 ) {
906 $res .= " .\n$tab$code";
907 } else {
908 $res = substr($res, 0, -1) . substr($code, 1);
909 }
910 }
911
912 return '(' . substr($res, 2 + $descr['niv']) . ')';
913 }
914 } else {
915 $nom = $descr['nom'] . $id_boucle . ($descr['niv'] ? $descr['niv'] : '');
916
917 return "join('', array_map('array_shift', \$GLOBALS['debug_objets']['sequence']['$nom'] = array(" . join(" ,\n$tab",
918 $codes) . ")))";
919 }
920 }
921
922 define('_REGEXP_COND_VIDE_NONVIDE', "/^[(](.*)[?]\s*''\s*:\s*('[^']+')\s*[)]$/");
923 define('_REGEXP_COND_NONVIDE_VIDE', "/^[(](.*)[?]\s*('[^']+')\s*:\s*''\s*[)]$/");
924 define('_REGEXP_CONCAT_NON_VIDE', "/^(.*)[.]\s*'[^']+'\s*$/");
925
926 // http://code.spip.net/@compile_cas
927 function compile_cas($tableau, $descr, &$boucles, $id_boucle) {
928
929 $codes = array();
930 // cas de la boucle recursive
931 if (is_array($id_boucle)) {
932 $id_boucle = $id_boucle[0];
933 }
934 $type = !$id_boucle ? '' : $boucles[$id_boucle]->type_requete;
935 $tab = str_repeat("\t", ++$descr['niv']);
936 $mode = _request('var_mode_affiche');
937 $err_e_c = '';
938 // chaque commentaire introduit dans le code doit commencer
939 // par un caractere distinguant le cas, pour exploitation par debug.
940 foreach ($tableau as $p) {
941
942 switch ($p->type) {
943 // texte seul
944 case 'texte':
945 $code = sandbox_composer_texte($p->texte, $p);
946 $commentaire = strlen($p->texte) . " signes";
947 $avant = '';
948 $apres = '';
949 $altern = "''";
950 break;
951
952 case 'polyglotte':
953 $code = "";
954 foreach ($p->traductions as $k => $v) {
955 $code .= ",'" .
956 str_replace(array("\\", "'"), array("\\\\", "\\'"), $k) .
957 "' => '" .
958 str_replace(array("\\", "'"), array("\\\\", "\\'"), $v) .
959 "'";
960 }
961 $code = "choisir_traduction(array(" .
962 substr($code, 1) .
963 "))";
964 $commentaire = '&';
965 $avant = '';
966 $apres = '';
967 $altern = "''";
968 break;
969
970 // inclure
971 case 'include':
972 $p->descr = $descr;
973 $code = calculer_inclure($p, $boucles, $id_boucle);
974 if ($code === false) {
975 $err_e_c = true;
976 $code = "''";
977 } else {
978 $commentaire = '<INCLURE ' . addslashes(str_replace("\n", ' ', $code)) . '>';
979 $avant = '';
980 $apres = '';
981 $altern = "''";
982 }
983 break;
984
985 // boucle
986 case TYPE_RECURSIF:
987 $nom = $p->id_boucle;
988 $newdescr = $descr;
989 $newdescr['id_mere'] = $nom;
990 $newdescr['niv']++;
991 $avant = calculer_liste($p->avant,
992 $newdescr, $boucles, $id_boucle);
993 $apres = calculer_liste($p->apres,
994 $newdescr, $boucles, $id_boucle);
995 $newdescr['niv']--;
996 $altern = calculer_liste($p->altern,
997 $newdescr, $boucles, $id_boucle);
998 if (($avant === false) or ($apres === false) or ($altern === false)) {
999 $err_e_c = true;
1000 $code = "''";
1001 } else {
1002 $code = 'BOUCLE' .
1003 str_replace("-", "_", $nom) . $descr['nom'] .
1004 '($Cache, $Pile, $doublons, $Numrows, $SP)';
1005 $commentaire = "?$nom";
1006 if (!$boucles[$nom]->milieu
1007 and $boucles[$nom]->type_requete <> TYPE_RECURSIF
1008 ) {
1009 if ($altern != "''") {
1010 $code .= "\n. $altern";
1011 }
1012 if ($avant <> "''" or $apres <> "''") {
1013 spip_log("boucle $nom toujours vide, code superflu dans $descr[sourcefile]");
1014 }
1015 $avant = $apres = $altern = "''";
1016 } else {
1017 if ($altern != "''") {
1018 $altern = "($altern)";
1019 }
1020 }
1021 }
1022 break;
1023
1024 case 'idiome':
1025 $l = array();
1026 $code = '';
1027 foreach ($p->arg as $k => $v) {
1028 $_v = calculer_liste($v, $descr, $boucles, $id_boucle);
1029 if ($k) {
1030 $l[] = _q($k) . ' => ' . $_v;
1031 } else {
1032 $code = $_v;
1033 }
1034 }
1035 // Si le module n'est pas fourni, l'expliciter sauf si calculé
1036 if ($p->module) {
1037 $m = $p->module . ':' . $p->nom_champ;
1038 } elseif ($p->nom_champ) {
1039 $m = MODULES_IDIOMES . ':' . $p->nom_champ;
1040 } else {
1041 $m = '';
1042 }
1043
1044 $code = (!$code ? "'$m'" :
1045 ($m ? "'$m' . $code" :
1046 ("(strpos(\$x=$code, ':') ? \$x : ('" . MODULES_IDIOMES . ":' . \$x))")))
1047 . (!$l ? '' : (", array(" . implode(",\n", $l) . ")"));
1048 $code = "_T($code)";
1049 if ($p->param) {
1050 $p->id_boucle = $id_boucle;
1051 $p->boucles = &$boucles;
1052 $code = compose_filtres($p, $code);
1053 }
1054 $commentaire = ":";
1055 $avant = '';
1056 $apres = '';
1057 $altern = "''";
1058 break;
1059
1060 case 'champ':
1061
1062 // cette structure pourrait etre completee des le phrase' (a faire)
1063 $p->id_boucle = $id_boucle;
1064 $p->boucles = &$boucles;
1065 $p->descr = $descr;
1066 #$p->interdire_scripts = true;
1067 $p->type_requete = $type;
1068
1069 $code = calculer_champ($p);
1070 $commentaire = '#' . $p->nom_champ . $p->etoile;
1071 $avant = calculer_liste($p->avant,
1072 $descr, $boucles, $id_boucle);
1073 $apres = calculer_liste($p->apres,
1074 $descr, $boucles, $id_boucle);
1075 $altern = "''";
1076 // Si la valeur est destinee a une comparaison a ''
1077 // forcer la conversion en une chaine par strval
1078 // si ca peut etre autre chose qu'une chaine
1079 if (($avant != "''" or $apres != "''")
1080 and $code[0] != "'"
1081 # AND (strpos($code,'interdire_scripts') !== 0)
1082 and !preg_match(_REGEXP_COND_VIDE_NONVIDE, $code)
1083 and !preg_match(_REGEXP_COND_NONVIDE_VIDE, $code)
1084 and !preg_match(_REGEXP_CONCAT_NON_VIDE, $code)
1085 ) {
1086 $code = "strval($code)";
1087 }
1088 break;
1089
1090 default:
1091 // Erreur de construction de l'arbre de syntaxe abstraite
1092 $code = "''";
1093 $p->descr = $descr;
1094 $err_e_c = _T('zbug_erreur_compilation');
1095 erreur_squelette($err_e_c, $p);
1096 } // switch
1097
1098 if ($code != "''") {
1099 $code = compile_retour($code, $avant, $apres, $altern, $tab, $descr['niv']);
1100 $codes[] = (($mode == 'validation') ?
1101 "array($code, '$commentaire', " . $p->ligne . ")"
1102 : (($mode == 'code') ?
1103 "\n// $commentaire\n$code" :
1104 $code));
1105 }
1106 } // foreach
1107
1108 return $err_e_c ? false : $codes;
1109 }
1110
1111 // production d'une expression conditionnelle ((v=EXP) ? (p . v .s) : a)
1112 // mais si EXP est de la forme (t ? 'C' : '') on produit (t ? (p . C . s) : a)
1113 // de meme si EXP est de la forme (t ? '' : 'C')
1114
1115 // http://code.spip.net/@compile_retour
1116 function compile_retour($code, $avant, $apres, $altern, $tab, $n) {
1117 if ($avant == "''") {
1118 $avant = '';
1119 }
1120 if ($apres == "''") {
1121 $apres = '';
1122 }
1123 if (!$avant and !$apres and ($altern === "''")) {
1124 return $code;
1125 }
1126
1127 if (preg_match(_REGEXP_CONCAT_NON_VIDE, $code)) {
1128 $t = $code;
1129 $cond = '';
1130 } elseif (preg_match(_REGEXP_COND_VIDE_NONVIDE, $code, $r)) {
1131 $t = $r[2];
1132 $cond = '!' . $r[1];
1133 } else {
1134 if (preg_match(_REGEXP_COND_NONVIDE_VIDE, $code, $r)) {
1135 $t = $r[2];
1136 $cond = $r[1];
1137 } else {
1138 $t = '$t' . $n;
1139 $cond = "($t = $code)!==''";
1140 }
1141 }
1142
1143 $res = (!$avant ? "" : "$avant . ") .
1144 $t .
1145 (!$apres ? "" : " . $apres");
1146
1147 if ($res !== $t) {
1148 $res = "($res)";
1149 }
1150
1151 return !$cond ? $res : "($cond ?\n\t$tab$res :\n\t$tab$altern)";
1152 }
1153
1154
1155 function compile_inclure_doublons($lexemes) {
1156 foreach ($lexemes as $v) {
1157 if ($v->type === 'include' and $v->param) {
1158 foreach ($v->param as $r) {
1159 if (trim($r[0]) === 'doublons') {
1160 return true;
1161 }
1162 }
1163 }
1164 }
1165
1166 return false;
1167 }
1168
1169 // Prend en argument le texte d'un squelette, le nom de son fichier d'origine,
1170 // sa grammaire et un nom. Retourne False en cas d'erreur,
1171 // sinon retourne un tableau de fonctions PHP compilees a evaluer,
1172 // notamment une fonction portant ce nom et calculant une page.
1173 // Pour appeler la fonction produite, lui fournir 2 tableaux de 1 e'le'ment:
1174 // - 1er: element 'cache' => nom (du fichier ou` mettre la page)
1175 // - 2e: element 0 contenant un environnement ('id_article => $id_article, etc)
1176 // Elle retournera alors un tableau de 5 e'le'ments:
1177 // - 'texte' => page HTML, application du squelette a` l'environnement;
1178 // - 'squelette' => le nom du squelette
1179 // - 'process_ins' => 'html' ou 'php' selon la pre'sence de PHP dynamique
1180 // - 'invalideurs' => de'pendances de cette page, pour invalider son cache.
1181 // - 'entetes' => tableau des entetes http
1182 // En cas d'erreur, elle retournera un tableau des 2 premiers elements seulement
1183
1184 // http://code.spip.net/@public_compiler_dist
1185 function public_compiler_dist($squelette, $nom, $gram, $sourcefile, $connect = '') {
1186 // Pre-traitement : reperer le charset du squelette, et le convertir
1187 // Bonus : supprime le BOM
1188 include_spip('inc/charsets');
1189 $squelette = transcoder_page($squelette);
1190
1191 // rendre inertes les echappements de #[](){}<>
1192 $i = 0;
1193 while (false !== strpos($squelette, $inerte = '-INERTE' . $i)) {
1194 $i++;
1195 }
1196 $squelette = preg_replace_callback(',\\\\([#[()\]{}<>]),',
1197 create_function('$a', "return '$inerte-'.ord(\$a[1]).'-';"), $squelette, -1, $esc);
1198
1199 $descr = array(
1200 'nom' => $nom,
1201 'gram' => $gram,
1202 'sourcefile' => $sourcefile,
1203 'squelette' => $squelette
1204 );
1205
1206 // Phraser le squelette, selon sa grammaire
1207
1208 $boucles = array();
1209 $f = charger_fonction('phraser_' . $gram, 'public');
1210
1211 $squelette = $f($squelette, '', $boucles, $descr);
1212
1213 $boucles = compiler_squelette($squelette, $boucles, $nom, $descr, $sourcefile, $connect);
1214
1215 // restituer les echappements
1216 if ($esc) {
1217 foreach ($boucles as $i => $boucle) {
1218 $boucles[$i]->return = preg_replace_callback(",$inerte-(\d+)-,", create_function('$a', 'return chr($a[1]);'),
1219 $boucle->return);
1220 $boucles[$i]->descr['squelette'] = preg_replace_callback(",$inerte-(\d+)-,",
1221 create_function('$a', 'return "\\\\".chr($a[1]);'),
1222 $boucle->descr['squelette']);
1223 }
1224 }
1225
1226 $debug = ($boucles and defined('_VAR_MODE') and _VAR_MODE == 'debug');
1227 if ($debug) {
1228 include_spip('public/decompiler');
1229 foreach ($boucles as $id => $boucle) {
1230 if ($id) {
1231 $decomp = "\n/* BOUCLE " .
1232 $boucle->type_requete .
1233 " " .
1234 str_replace('*/', '* /', public_decompiler($boucle, $gram, 0, 'criteres')) .
1235 " */\n";
1236 } else {
1237 $decomp = ("\n/*\n" .
1238 str_replace('*/', '* /', public_decompiler($squelette, $gram))
1239 . "\n*/");
1240 }
1241 $boucles[$id]->return = $decomp . $boucle->return;
1242 $GLOBALS['debug_objets']['code'][$nom . $id] = $boucle->return;
1243 }
1244 }
1245
1246 return $boucles;
1247 }
1248
1249 // Point d'entree pour arbre de syntaxe abstraite fourni en premier argument
1250 // Autres specifications comme ci-dessus
1251
1252 function compiler_squelette($squelette, $boucles, $nom, $descr, $sourcefile, $connect = '') {
1253 static $trouver_table;
1254 spip_timer('calcul_skel');
1255
1256 if (defined('_VAR_MODE') and _VAR_MODE == 'debug') {
1257 $GLOBALS['debug_objets']['squelette'][$nom] = $descr['squelette'];
1258 $GLOBALS['debug_objets']['sourcefile'][$nom] = $sourcefile;
1259
1260 if (!isset($GLOBALS['debug_objets']['principal'])) {
1261 $GLOBALS['debug_objets']['principal'] = $nom;
1262 }
1263 }
1264 foreach ($boucles as $id => $boucle) {
1265 $GLOBALS['debug_objets']['boucle'][$nom . $id] = $boucle;
1266 }
1267 $descr['documents'] = compile_inclure_doublons($squelette);
1268
1269 // Demander la description des tables une fois pour toutes
1270 if (!$trouver_table) {
1271 $trouver_table = charger_fonction('trouver_table', 'base');
1272 }
1273
1274 // reperer si les doublons sont demandes
1275 // pour un inclure ou une boucle document
1276 // c'est utile a la fonction champs_traitements
1277 foreach ($boucles as $id => $boucle) {
1278 if (!($type = $boucle->type_requete)) {
1279 continue;
1280 }
1281 if (!$descr['documents'] and (
1282 (($type == 'documents') and $boucle->doublons) or
1283 compile_inclure_doublons($boucle->avant) or
1284 compile_inclure_doublons($boucle->apres) or
1285 compile_inclure_doublons($boucle->milieu) or
1286 compile_inclure_doublons($boucle->altern))
1287 ) {
1288 $descr['documents'] = true;
1289 }
1290 if ($type != TYPE_RECURSIF) {
1291 if (!$boucles[$id]->sql_serveur and $connect) {
1292 $boucles[$id]->sql_serveur = $connect;
1293 }
1294
1295 // chercher dans les iterateurs du repertoire iterateur/
1296 if ($g = charger_fonction(
1297 preg_replace('/\W/', '_', $boucle->type_requete), 'iterateur', true)
1298 ) {
1299 $boucles[$id] = $g($boucle);
1300
1301 // sinon, en cas de requeteur d'un type predefini,
1302 // utiliser les informations donnees par le requeteur
1303 // cas "php:xx" et "data:xx".
1304 } else {
1305 if ($boucle->sql_serveur and $requeteur = charger_fonction($boucle->sql_serveur, 'requeteur', true)) {
1306 $requeteur($boucles, $boucle, $id);
1307
1308 // utiliser la description des champs transmis
1309 } else {
1310 $show = $trouver_table($type, $boucles[$id]->sql_serveur);
1311 // si la table n'existe pas avec le connecteur par defaut,
1312 // c'est peut etre une table qui necessite son connecteur dedie fourni
1313 // permet une ecriture allegee (GEO) -> (geo:GEO)
1314 if (!$show
1315 and $show = $trouver_table($type, strtolower($type))
1316 ) {
1317 $boucles[$id]->sql_serveur = strtolower($type);
1318 }
1319 if ($show) {
1320 $boucles[$id]->show = $show;
1321 // recopie les infos les plus importantes
1322 $boucles[$id]->primary = isset($show['key']["PRIMARY KEY"]) ? $show['key']["PRIMARY KEY"] : '';
1323 $boucles[$id]->id_table = $x = preg_replace(",^spip_,", "", $show['id_table']);
1324 $boucles[$id]->from[$x] = $nom_table = $show['table'];
1325 $boucles[$id]->iterateur = 'SQL';
1326
1327 $boucles[$id]->descr = &$descr;
1328 if ((!$boucles[$id]->jointures)
1329 and is_array($show['tables_jointures'])
1330 and count($x = $show['tables_jointures'])
1331 ) {
1332 $boucles[$id]->jointures = $x;
1333 }
1334 if ($boucles[$id]->jointures_explicites) {
1335 $jointures = preg_split("/\s+/", $boucles[$id]->jointures_explicites);
1336 while ($j = array_pop($jointures)) {
1337 array_unshift($boucles[$id]->jointures, $j);
1338 }
1339 }
1340 } else {
1341 // Pas une erreur si la table est optionnelle
1342 if ($boucles[$id]->table_optionnelle) {
1343 $boucles[$id]->type_requete = '';
1344 } else {
1345 $boucles[$id]->type_requete = false;
1346 $boucle = $boucles[$id];
1347 $x = (!$boucle->sql_serveur ? '' :
1348 ($boucle->sql_serveur . ":")) .
1349 $type;
1350 $msg = array(
1351 'zbug_table_inconnue',
1352 array('table' => $x)
1353 );
1354 erreur_squelette($msg, $boucle);
1355 }
1356 }
1357 }
1358 }
1359 }
1360 }
1361
1362 // Commencer par reperer les boucles appelees explicitement
1363 // car elles indexent les arguments de maniere derogatoire
1364 foreach ($boucles as $id => $boucle) {
1365 if ($boucle->type_requete == TYPE_RECURSIF and $boucle->param) {
1366 $boucles[$id]->descr = &$descr;
1367 $rec = &$boucles[$boucle->param[0]];
1368 if (!$rec) {
1369 $msg = array(
1370 'zbug_boucle_recursive_undef',
1371 array('nom' => $boucle->param[0])
1372 );
1373 erreur_squelette($msg, $boucle);
1374 $boucles[$id]->type_requete = false;
1375 } else {
1376 $rec->externe = $id;
1377 $descr['id_mere'] = $id;
1378 $boucles[$id]->return =
1379 calculer_liste(array($rec),
1380 $descr,
1381 $boucles,
1382 $boucle->param);
1383 }
1384 }
1385 }
1386 foreach ($boucles as $id => $boucle) {
1387 $id = strval($id); // attention au type dans index_pile
1388 $type = $boucle->type_requete;
1389 if ($type and $type != TYPE_RECURSIF) {
1390 $res = '';
1391 if ($boucle->param) {
1392 // retourne un tableau en cas d'erreur
1393 $res = calculer_criteres($id, $boucles);
1394 }
1395 $descr['id_mere'] = $id;
1396 $boucles[$id]->return =
1397 calculer_liste($boucle->milieu,
1398 $descr,
1399 $boucles,
1400 $id);
1401 // Si les criteres se sont mal compiles
1402 // ne pas tenter d'assembler le code final
1403 // (mais compiler le corps pour detection d'erreurs)
1404 if (is_array($res)) {
1405 $boucles[$id]->type_requete = false;
1406 }
1407 }
1408 }
1409
1410 // idem pour la racine
1411 $descr['id_mere'] = '';
1412 $corps = calculer_liste($squelette, $descr, $boucles);
1413
1414
1415 // Calcul du corps de toutes les fonctions PHP,
1416 // en particulier les requetes SQL et TOTAL_BOUCLE
1417 // de'terminables seulement maintenant
1418
1419 foreach ($boucles as $id => $boucle) {
1420 $boucle = $boucles[$id] = pipeline('pre_boucle', $boucle);
1421 if ($boucle->return === false) {
1422 $corps = false;
1423 continue;
1424 }
1425 // appeler la fonction de definition de la boucle
1426
1427 if ($req = $boucle->type_requete) {
1428 // boucle personnalisée ?
1429 $table = strtoupper($boucle->type_requete);
1430 $serveur = strtolower($boucle->sql_serveur);
1431 if (
1432 // fonction de boucle avec serveur & table
1433 (!$serveur or
1434 ((!function_exists($f = "boucle_" . $serveur . "_" . $table))
1435 and (!function_exists($f = $f . "_dist"))
1436 )
1437 )
1438 // fonction de boucle avec table
1439 and (!function_exists($f = "boucle_" . $table))
1440 and (!function_exists($f = $f . "_dist"))
1441 ) {
1442 // fonction de boucle standard
1443 if (!function_exists($f = 'boucle_DEFAUT')) {
1444 $f = 'boucle_DEFAUT_dist';
1445 }
1446 }
1447
1448 $req = "\n\n\tstatic \$command = array();\n\t" .
1449 "static \$connect;\n\t" .
1450 "\$command['connect'] = \$connect = " .
1451 _q($boucle->sql_serveur) .
1452 ";" .
1453 $f($id, $boucles);
1454 } else {
1455 $req = ("\n\treturn '';");
1456 }
1457
1458 $boucles[$id]->return =
1459 "\n\nfunction BOUCLE" . strtr($id, "-", "_") . $nom .
1460 '(&$Cache, &$Pile, &$doublons, &$Numrows, $SP) {' .
1461 $req .
1462 "\n}\n";
1463 }
1464
1465 // Au final, si le corps ou un critere au moins s'est mal compile
1466 // retourner False, sinon inserer leur decompilation
1467 if (is_bool($corps)) {
1468 return false;
1469 }
1470
1471 $principal = "\nfunction " . $nom . '($Cache, $Pile, $doublons = array(), $Numrows = array(), $SP = 0) {
1472 '
1473 // reporter de maniere securisee les doublons inclus
1474 . '
1475 if (isset($Pile[0]["doublons"]) AND is_array($Pile[0]["doublons"]))
1476 $doublons = nettoyer_env_doublons($Pile[0]["doublons"]);
1477
1478 $connect = ' .
1479 _q($connect) . ';
1480 $page = ' .
1481 // ATTENTION, le calcul de l'expression $corps affectera $Cache
1482 // c'est pourquoi on l'affecte a la variable auxiliaire $page.
1483 // avant de referencer $Cache
1484 $corps . ";
1485
1486 return analyse_resultat_skel(" . var_export($nom, true)
1487 . ", \$Cache, \$page, " . var_export($sourcefile, true) . ");
1488 }";
1489
1490 $secondes = spip_timer('calcul_skel');
1491 spip_log("COMPIL ($secondes) [$sourcefile] $nom.php");
1492 // $connect n'est pas sûr : on nettoie
1493 $connect = preg_replace(',[^\w],', '', $connect);
1494
1495 // Assimiler la fct principale a une boucle anonyme, pour retourner un resultat simple
1496 $code = new Boucle;
1497 $code->descr = $descr;
1498 $code->return = '
1499 //
1500 // Fonction principale du squelette ' .
1501 $sourcefile .
1502 ($connect ? " pour $connect" : '') .
1503 (!CODE_COMMENTE ? '' : "\n// Temps de compilation total: $secondes") .
1504 "\n//\n" .
1505 $principal;
1506
1507 $boucles[''] = $code;
1508
1509 return $boucles;
1510 }
1511
1512
1513 /**
1514 * Requeteur pour les boucles (php:nom_iterateur)
1515 *
1516 * Analyse si le nom d'iterateur correspond bien a une classe PHP existante
1517 * et dans ce cas charge la boucle avec cet iterateur.
1518 * Affichera une erreur dans le cas contraire.
1519 *
1520 * @param $boucles Liste des boucles
1521 * @param $boucle La boucle parcourue
1522 * @param $id L'identifiant de la boucle parcourue
1523 *
1524 **/
1525 function requeteur_php_dist(&$boucles, &$boucle, &$id) {
1526 if (class_exists($boucle->type_requete)) {
1527 $g = charger_fonction('php', 'iterateur');
1528 $boucles[$id] = $g($boucle, $boucle->type_requete);
1529 } else {
1530 $x = $boucle->type_requete;
1531 $boucle->type_requete = false;
1532 $msg = array(
1533 'zbug_iterateur_inconnu',
1534 array('iterateur' => $x)
1535 );
1536 erreur_squelette($msg, $boucle);
1537 }
1538 }
1539
1540
1541 /**
1542 * Requeteur pour les boucles (data:type de donnee)
1543 * note: (DATA) tout court ne passe pas par ici.
1544 *
1545 * Analyse si le type de donnee peut etre traite
1546 * et dans ce cas charge la boucle avec cet iterateur.
1547 * Affichera une erreur dans le cas contraire.
1548 *
1549 * @param $boucles Liste des boucles
1550 * @param $boucle La boucle parcourue
1551 * @param $id L'identifiant de la boucle parcourue
1552 *
1553 **/
1554 function requeteur_data_dist(&$boucles, &$boucle, &$id) {
1555 include_spip('iterateur/data');
1556 if ($h = charger_fonction($boucle->type_requete . '_to_array', 'inc', true)) {
1557 $g = charger_fonction('data', 'iterateur');
1558 $boucles[$id] = $g($boucle);
1559 // from[0] stocke le type de data (rss, yql, ...)
1560 $boucles[$id]->from[] = $boucle->type_requete;
1561
1562 } else {
1563 $x = $boucle->type_requete;
1564 $boucle->type_requete = false;
1565 $msg = array(
1566 'zbug_requeteur_inconnu',
1567 array(
1568 'requeteur' => 'data',
1569 'type' => $x
1570 )
1571 );
1572 erreur_squelette($msg, $boucle);
1573 }
1574 }