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