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