[SPIP] +2.1.12
[velocampus/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-2011 *
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 // Fichier principal du compilateur de squelettes
15 //
16
17 if (!defined('_ECRIRE_INC_VERSION')) return;
18
19 // reperer un code ne calculant rien, meme avec commentaire
20 define('CODE_MONOTONE', ",^(\n//[^\n]*\n)?\(?'([^'])*'\)?$,");
21 // s'il faut commenter le code produit
22 define('CODE_COMMENTE', true);
23
24 // definition des structures de donnees
25 include_spip('public/interfaces');
26
27 // Definition de la structure $p, et fonctions de recherche et de reservation
28 // dans l'arborescence des boucles
29 include_spip('public/references');
30
31 // definition des boucles
32 include_spip('public/boucles');
33
34 // definition des criteres
35 include_spip('public/criteres');
36
37 // definition des balises
38 include_spip('public/balises');
39
40 // Gestion des jointures
41 include_spip('public/jointures');
42
43 // Les 2 ecritures INCLURE{A1,A2,A3...} et INCLURE(A1){A2}{A3}... sont admises
44 // Preferer la premiere.
45 // Les Ai sont de la forme Vi=Ei ou bien Vi qui veut alors dire Vi=Vi
46 // Le resultat est un tableau indexe par les Vi
47 // Toutefois, si le premier argument n'est pas de la forme Vi=Ei
48 // il est conventionnellement la valeur de l'index 1.
49 // pour la balise #INCLURE
50 // mais pas pour <INCLURE> dont le fond est defini explicitement.
51
52
53 // http://doc.spip.org/@argumenter_inclure
54 function argumenter_inclure($params, $rejet_filtres, $p, &$boucles, $id_boucle, $echap=true, $lang = '', $fond1=false){
55 $l = array();
56 $erreur_p_i_i = '';
57 if (!is_array($params)) return $l;
58 foreach($params as $k => $couple) {
59 // la liste d'arguments d'inclusion peut se terminer par un filtre
60 $filtre = array_shift($couple);
61 if ($filtre) break;
62 foreach($couple as $n => $val) {
63 $var = $val[0];
64 if ($var->type != 'texte') {
65 if ($n OR $k OR $fond1) {
66 $erreur_p_i_i = array('zbug_parametres_inclus_incorrects',
67 array('param' => $var->nom_champ));
68 erreur_squelette($erreur_p_i_i, $p);
69 } else $l[1] = calculer_liste($val, $p->descr, $boucles, $id_boucle);
70 break;
71 } else {
72 preg_match(",^([^=]*)(=?)(.*)$,", $var->texte,$m);
73 $var = $m[1];
74 $auto = false;;
75 if ($m[2]) {
76 $v = $m[3];
77 if (preg_match(',^[\'"](.*)[\'"]$,', $v, $m)) $v = $m[1];
78 $val[0] = new Texte;
79 $val[0]->texte = $v;
80 } elseif ($k OR $n OR $fond1) {
81 $auto = true;
82 } else $var = 1;
83
84 if ($var == 'lang') {
85 $lang = !$auto
86 ? calculer_liste($val, $p->descr, $boucles, $id_boucle)
87 : '$GLOBALS["spip_lang"]';
88 } else {
89 $val = $auto
90 ? index_pile($id_boucle, $var, $boucles)
91 : calculer_liste($val, $p->descr, $boucles, $id_boucle);
92 if ($var !== 1)
93 $val = ($echap?"\'$var\' => ' . argumenter_squelette(":"'$var' => ")
94 . $val . ($echap? ") . '":" ");
95 else $val = $echap ? "'.$val.'" : $val;
96 $l[$var] = $val;
97 }
98 }
99 }
100 }
101 if ($erreur_p_i_i) return false;
102 // Cas particulier de la langue : si {lang=xx} est definie, on
103 // la passe, sinon on passe la langue courante au moment du calcul
104 // sauf si on n'en veut pas
105 if ($lang === false) return $l;
106 if (!$lang) $lang = '$GLOBALS["spip_lang"]';
107 $l['lang'] = ($echap?"\'lang\' => ' . argumenter_squelette(":"'lang' => ") . $lang . ($echap?") . '":" ");
108
109 return $l;
110 }
111
112 //
113 // Calculer un <INCLURE()>
114 // La constante ci-dessous donne le code general quand il s'agit d'un script.
115
116 define('CODE_INCLURE_SCRIPT', 'if (($path = %s) AND is_readable($path))
117 include $path;
118 else erreur_squelette(array("fichier_introuvable", array("fichier" => "%s")), array(%s));'
119 );
120
121 // // et celle-ci pour un squelette (aussi pour #INCLURE, #MODELE #LES_AUTEURS)
122
123 define('CODE_RECUPERER_FOND', 'recuperer_fond(%s, %s, array(%s), %s)');
124
125 // http://doc.spip.org/@calculer_inclure
126 function calculer_inclure($p, &$boucles, $id_boucle) {
127
128 $_contexte = argumenter_inclure($p->param, false, $p, $boucles, $id_boucle, true, '', true);
129 if (is_string($p->texte)) {
130 $fichier = $p->texte;
131 $code = "\"$fichier\"";
132
133 } else {
134 $code = calculer_liste($p->texte, $p->descr, $boucles, $id_boucle);
135 if ($code AND preg_match("/^'([^']*)'/s", $code, $r))
136 $fichier = $r[1];
137 else $fichier = '';
138 }
139 if (!$code OR $code === '""') {
140 $erreur_p_i_i = array('zbug_parametres_inclus_incorrects',
141 array('param' => $code));
142 erreur_squelette($erreur_p_i_i, $p);
143 return false;
144 }
145 $compil = texte_script(memoriser_contexte_compil($p));
146
147 if (is_array($_contexte)) {
148 // Critere d'inclusion {env} (et {self} pour compatibilite ascendante)
149 if ($env = (isset($_contexte['env'])|| isset($_contexte['self']))) {
150 unset($_contexte['env']);
151 }
152
153 // noter les doublons dans l'appel a public.php
154 if (isset($_contexte['doublons'])) {
155 $_contexte['doublons'] = "\\'doublons\\' => '.var_export(\$doublons,true).'";
156 }
157
158 if ($ajax = isset($_contexte['ajax']))
159 unset($_contexte['ajax']);
160
161 $_contexte = join(",\n\t", $_contexte);
162 }
163 else
164 return false; // j'aurais voulu toucher le fond ...
165
166 $contexte = 'array(' . $_contexte .')';
167
168 if ($env) {
169 $contexte = "array_merge('.var_export(\$Pile[0],1).',$contexte)";
170 }
171
172 // s'il y a une extension .php, ce n'est pas un squelette
173 if (preg_match('/^.+[.]php$/s', $fichier)) {
174 // si inexistant, on essaiera a l'execution
175 if ($path = find_in_path($fichier))
176 $path = "\"$path\"";
177 else $path = "find_in_path(\"$fichier\")";
178
179 $code = sprintf(CODE_INCLURE_SCRIPT, $path, $fichier, $compil);
180 } else {
181 $_options[] = "\"compil\"=>array($compil)";
182 if ($ajax)
183 $_options[] = "\"ajax\"=>true";
184 $code = " ' . argumenter_squelette($code) . '";
185 $code = "echo " . sprintf(CODE_RECUPERER_FOND, $code, $contexte, implode(',',$_options), "_request(\"connect\")") . ';';
186 }
187
188 return "\n'<'.'". "?php ". $code . "\n?'." . "'>'";
189 }
190
191 //
192 // calculer_boucle() produit le corps PHP d'une boucle Spip.
193 // ce corps remplit une variable $t0 retournee en valeur.
194 // Ici on distingue boucles recursives et boucle a requete SQL
195 // et on insere le code d'envoi au debusqueur du resultat de la fonction.
196
197 // http://doc.spip.org/@calculer_boucle
198 function calculer_boucle($id_boucle, &$boucles) {
199
200 $boucles[$id_boucle] = pipeline('post_boucle', $boucles[$id_boucle]);
201
202 // en mode debug memoriser les premiers passages dans la boucle,
203 // mais pas tous, sinon ca pete.
204 if (_request('var_mode_affiche') != 'resultat')
205 $trace = '';
206 else {
207 $trace = $boucles[$id_boucle]->descr['nom'] . $id_boucle;
208 $trace = "if (count(@\$GLOBALS['debug_objets']['resultat']['$trace'])<3)
209 \$GLOBALS['debug_objets']['resultat']['$trace'][] = \$t0;";
210 }
211 return ($boucles[$id_boucle]->type_requete == 'boucle')
212 ? calculer_boucle_rec($id_boucle, $boucles, $trace)
213 : calculer_boucle_nonrec($id_boucle, $boucles, $trace);
214 }
215
216 // compil d'une boucle recursive.
217 // il suffit (ET IL FAUT) sauvegarder les valeurs des arguments passes par
218 // reference, car par definition un tel passage ne les sauvegarde pas
219
220 // http://doc.spip.org/@calculer_boucle_rec
221 function calculer_boucle_rec($id_boucle, &$boucles, $trace) {
222 $nom = $boucles[$id_boucle]->param[0];
223 return "\n\t\$save_numrows = (\$Numrows['$nom']);"
224 . "\n\t\$t0 = " . $boucles[$id_boucle]->return . ";"
225 . "\n\t\$Numrows['$nom'] = (\$save_numrows);"
226 . $trace
227 . "\n\treturn \$t0;";
228 }
229
230 // Compilation d'une boucle non recursive.
231 // Ci-dessous la constante donnant le cadre systematique du code:
232 // %s1: initialisation des arguments de calculer_select
233 // %s2: appel de calculer_select en donnant un contexte pour les cas d'erreur
234 // %s3: initialisation du sous-tableau Numrows[id_boucle]
235 // %s4: sauvegarde de la langue et calcul des invariants de boucle sur elle
236 // %s5: boucle while sql_fetch ou str_repeat si corps monotone
237 // %s6: restauration de la langue
238 // %s7: liberation de la ressource, en tenant compte du serveur SQL
239 // %s8: code de trace eventuel avant le retour
240
241 define('CODE_CORPS_BOUCLE', '%s
242 $t0 = "";
243 // REQUETE
244 $result = calculer_select($select, $from, $type, $where, $join, $groupby, $orderby, $limit, $having, $table, $id, $connect,
245 array(%s));
246 if ($result) {
247 %s%s$SP++;
248 // RESULTATS
249 %s
250 %s@sql_free($result%s);
251 }%s
252 return $t0;'
253 );
254
255 // http://doc.spip.org/@calculer_boucle_nonrec
256 function calculer_boucle_nonrec($id_boucle, &$boucles, $trace) {
257
258 $boucle = &$boucles[$id_boucle];
259 $return = $boucle->return;
260 $type_boucle = $boucle->type_requete;
261 $primary = $boucle->primary;
262 $constant = preg_match(CODE_MONOTONE, str_replace("\\'",'', $return));
263 $flag_cpt = $boucle->mode_partie ||$boucle->cptrows;
264 $corps = '';
265
266 // faudrait expanser le foreach a la compil, car y en a souvent qu'un
267 // et puis faire un [] plutot qu'un "','."
268 if ($boucle->doublons)
269 $corps .= "\n\t\t\tforeach(" . $boucle->doublons . ' as $k) $doublons[$k] .= "," . ' .
270 index_pile($id_boucle, $primary, $boucles)
271 . "; // doublons\n";
272
273 // La boucle doit-elle selectionner la langue ?
274 // -. par defaut, les boucles suivantes le font
275 // (sauf si forcer_lang==true ou si le titre contient <multi>).
276 // - . a moins d'une demande explicite via {!lang_select}
277 if (!$constant && $boucle->lang_select != 'non' &&
278 (($boucle->lang_select == 'oui') ||
279 in_array($type_boucle, array(
280 'articles', 'rubriques', 'hierarchie', 'breves'
281 )))
282 ) {
283 // Memoriser la langue avant la boucle et la restituer apres
284 // afin que le corps de boucle affecte la globale directement
285 $init_lang = "lang_select(\$GLOBALS['spip_lang']);\n\t";
286 $fin_lang = "lang_select();\n\t";
287
288 $corps .=
289 "\n\t\tlang_select_public("
290 . index_pile($id_boucle, 'lang', $boucles)
291 . ", '".$boucle->lang_select."'"
292 . (in_array($type_boucle, array(
293 'articles', 'rubriques', 'hierarchie', 'breves'
294 )) ? ', '.index_pile($id_boucle, 'titre', $boucles) : '')
295 . ');';
296 }
297 else {
298 $init_lang = '';
299 $fin_lang = '';
300 // sortir les appels au traducteur (invariants de boucle)
301 if (strpos($return, '?php') === false
302 AND preg_match_all("/\W(_T[(]'[^']*'[)])/", $return, $r)) {
303 $i = 1;
304 foreach($r[1] as $t) {
305 $init_lang .= "\n\t\$l$i = $t;";
306 $return = str_replace($t, "\$l$i", $return);
307 $i++;
308 }
309 }
310 }
311
312 // gestion optimale des separateurs et des boucles constantes
313 if (count($boucle->separateur))
314 $code_sep = ("'" . str_replace("'","\'",join('',$boucle->separateur)) . "'");
315
316 $corps .=
317 ((!$boucle->separateur) ?
318 (($constant && !$corps && !$flag_cpt) ? $return :
319 (($return==="''") ? '' :
320 ("\n\t\t" . '$t0 .= ' . $return . ";"))) :
321 ("\n\t\t\$t1 " .
322 ((strpos($return, '$t1.') === 0) ?
323 (".=" . substr($return,4)) :
324 ('= ' . $return)) .
325 ";\n\t\t" .
326 '$t0 .= (($t1 && $t0) ? ' . $code_sep . " : '') . \$t1;"));
327
328 // Calculer les invalideurs si c'est une boucle non constante et si on
329 // souhaite invalider ces elements
330 if (!$constant AND $primary) {
331 include_spip('inc/invalideur');
332 if (function_exists($i = 'calcul_invalideurs'))
333 $corps = $i($corps, $primary, $boucles, $id_boucle);
334 }
335
336 // gerer le compteur de boucle
337 // avec ou sans son utilisation par les criteres {1/3} {1,4} {n-2,1}...
338
339 if ($boucle->partie OR $boucle->cptrows)
340 $corps = "\n\t\t\$Numrows['$id_boucle']['compteur_boucle']++;"
341 . $boucle->partie
342 . $corps;
343
344 $serveur = !$boucle->sql_serveur ? ''
345 : (', ' . _q($boucle->sql_serveur));
346
347 // si le corps est une constante, ne pas appeler le serveur N fois!
348
349 if (preg_match(CODE_MONOTONE,str_replace("\\'",'',$corps), $r)) {
350 if (!isset($r[2]) OR (!$r[2])) {
351 if (!$boucle->numrows)
352 return "\n\t\$t0 = '';";
353 else
354 $corps = "";
355 } else {
356 $boucle->numrows = true;
357 $corps = "\n\t\$t0 = str_repeat($corps, \$Numrows['$id_boucle']['total']);";
358 }
359 } else $corps = "while (\$Pile[\$SP] = @sql_fetch(\$result$serveur)) {\n$corps\n }";
360
361 $count = '';
362 if (!$boucle->select) {
363 if (!$boucle->numrows OR $boucle->limit OR $boucle_mode_partie OR $boucle->group)
364 $count = '1';
365 else $count = 'count(*)';
366 $boucles[$id_boucle]->select[]= $count;
367 }
368
369 if ($flag_cpt)
370 $nums = "\n\t// COMPTEUR\n\t"
371 . "\$Numrows['$id_boucle']['compteur_boucle'] = 0;\n\t";
372 else $nums = '';
373
374 if ($boucle->numrows OR $boucle->mode_partie) {
375 if ($count == 'count(*)')
376 $count = "array_shift(sql_fetch(\$result$serveur))";
377 else $count = "sql_count(\$result$serveur)";
378 $nums .= "\$Numrows['$id_boucle']['total'] = @intval($count);"
379 . $boucle->mode_partie
380 . "\n\t";
381 }
382
383 // Ne calculer la requete que maintenant
384 // car ce qui precede appelle index_pile qui influe dessus
385
386 $init = (($init = $boucles[$id_boucle]->doublons)
387 ? ("\n\t$init = array();") : '')
388 . calculer_requete_sql($boucles[$id_boucle]);
389
390 $contexte = memoriser_contexte_compil($boucle);
391
392 return sprintf(CODE_CORPS_BOUCLE, $init, $contexte, $nums, $init_lang, $corps, $fin_lang, $serveur, $trace);
393 }
394
395
396 // http://doc.spip.org/@calculer_requete_sql
397 function calculer_requete_sql($boucle)
398 {
399 return ($boucle->hierarchie ? "\n\t$boucle->hierarchie" : '')
400 . $boucle->in
401 . $boucle->hash
402 . calculer_dec('$table', "'" . $boucle->id_table ."'")
403 . calculer_dec('$id', "'" . $boucle->id_boucle ."'")
404 # En absence de champ c'est un decompte :
405 . calculer_dec('$from', calculer_from($boucle))
406 . calculer_dec('$type', calculer_from_type($boucle))
407 . calculer_dec('$groupby', 'array(' . (($g=join("\",\n\t\t\"",$boucle->group))?'"'.$g.'"':'') . ")")
408 . calculer_dec('$select', 'array("' . join("\",\n\t\t\"", $boucle->select). "\")")
409 . calculer_dec('$orderby', 'array(' . calculer_order($boucle) . ")")
410 . calculer_dec('$where', calculer_dump_array($boucle->where))
411 . calculer_dec('$join', calculer_dump_join($boucle->join))
412 . calculer_dec('$limit', (strpos($boucle->limit, 'intval') === false ?
413 "'".$boucle->limit."'" :
414 $boucle->limit))
415 . calculer_dec('$having', calculer_dump_array($boucle->having));
416 }
417
418 function memoriser_contexte_compil($p) {
419 return join(',', array(
420 _q($p->descr['sourcefile']),
421 _q($p->descr['nom']),
422 @_q($p->id_boucle),
423 intval($p->ligne),
424 '$GLOBALS[\'spip_lang\']'));
425 }
426
427 function reconstruire_contexte_compil($context_compil)
428 {
429 if (!is_array($context_compil)) return $context_compil;
430 include_spip('public/interfaces');
431 $p = new Contexte;
432 $p->descr = array('sourcefile' => $context_compil[0],
433 'nom' => $context_compil[1]);
434 $p->id_boucle = $context_compil[2];
435 $p->ligne = $context_compil[3];
436 $p->lang = $context_compil[4];
437 return $p;
438 }
439
440 // http://doc.spip.org/@calculer_dec
441 function calculer_dec($nom, $val)
442 {
443 $static = "static ";
444 if (
445 strpos($val, '$') !== false
446 OR strpos($val, 'sql_') !== false
447 OR (
448 $test = str_replace(array("array(",'\"',"\'"),array("","",""),$val) // supprimer les array( et les echappements de guillemets
449 AND strpos($test,"(")!==FALSE // si pas de parenthese ouvrante, pas de fonction, on peut sortir
450 AND $test = preg_replace(",'[^']*',UimsS","",$test) // supprimer les chaines qui peuvent contenir des fonctions SQL qui ne genent pas
451 AND preg_match(",\w+\s*\(,UimsS",$test,$regs) // tester la presence de fonctions restantes
452 )
453 ){
454 $static = "";
455 }
456 return "\n\t" . $static . $nom . ' = ' . $val . ';';
457 }
458
459 // http://doc.spip.org/@calculer_dump_array
460 function calculer_dump_array($a)
461 {
462 if (!is_array($a)) return $a ;
463 $res = "";
464 if ($a AND $a[0] == "'?'")
465 return ("(" . calculer_dump_array($a[1]) .
466 " ? " . calculer_dump_array($a[2]) .
467 " : " . calculer_dump_array($a[3]) .
468 ")");
469 else {
470 foreach($a as $v) $res .= ", " . calculer_dump_array($v);
471 return "\n\t\t\tarray(" . substr($res,2) . ')';
472 }
473 }
474
475 // http://doc.spip.org/@calculer_dump_join
476 function calculer_dump_join($a)
477 {
478 $res = "";
479 foreach($a as $k => $v)
480 $res .= ", '$k' => array(".implode(',',$v).")";
481 return 'array(' . substr($res,2) . ')';
482 }
483
484 // http://doc.spip.org/@calculer_from
485 function calculer_from(&$boucle)
486 {
487 $res = "";
488 foreach($boucle->from as $k => $v) $res .= ",'$k' => '$v'";
489 return 'array(' . substr($res,1) . ')';
490 }
491
492 // http://doc.spip.org/@calculer_from_type
493 function calculer_from_type(&$boucle)
494 {
495 $res = "";
496 foreach($boucle->from_type as $k => $v) $res .= ",'$k' => '$v'";
497 return 'array(' . substr($res,1) . ')';
498 }
499
500 // http://doc.spip.org/@calculer_order
501 function calculer_order(&$boucle)
502 {
503 if (!$order = $boucle->order
504 AND !$order = $boucle->default_order)
505 $order = array();
506
507 /*if (isset($boucle->modificateur['collate'])){
508 $col = "." . $boucle->modificateur['collate'];
509 foreach($order as $k=>$o)
510 if (strpos($order[$k],'COLLATE')===false)
511 $order[$k].= $col;
512 }*/
513 return join(', ', $order);
514 }
515
516 // Production du code PHP a partir de la sequence livree par le phraseur
517 // $boucles est passe par reference pour affectation par index_pile.
518 // Retourne une expression PHP,
519 // (qui sera argument d'un Return ou la partie droite d'une affectation).
520
521 // http://doc.spip.org/@calculer_liste
522 function calculer_liste($tableau, $descr, &$boucles, $id_boucle='') {
523 if (!$tableau) return "''";
524 if (!isset($descr['niv'])) $descr['niv'] = 0;
525 $codes = compile_cas($tableau, $descr, $boucles, $id_boucle);
526 if ($codes === false) return false;
527 $n = count($codes);
528 if (!$n) return "''";
529 $tab = str_repeat("\t", $descr['niv']);
530 if (_request('var_mode_affiche') != 'validation') {
531 if ($n==1)
532 return $codes[0];
533 else {
534 $res = '';
535 foreach($codes as $code) {
536 if (!preg_match("/^'[^']*'$/", $code)
537 OR substr($res,-1,1)!=="'")
538 $res .= " .\n$tab$code";
539 else {
540 $res = substr($res,0,-1) . substr($code,1);
541 }
542 }
543 return '(' . substr($res,2+$descr['niv']) . ')';
544 }
545 } else {
546 $nom = $descr['nom'] . $id_boucle . ($descr['niv']?$descr['niv']:'');
547 return "join('', array_map('array_shift', \$GLOBALS['debug_objets']['sequence']['$nom'] = array(" . join(" ,\n$tab", $codes) . ")))";
548 }
549 }
550
551 define('_REGEXP_COND_VIDE_NONVIDE',"/^[(](.*)[?]\s*''\s*:\s*('[^']+')\s*[)]$/");
552 define('_REGEXP_COND_NONVIDE_VIDE',"/^[(](.*)[?]\s*('[^']+')\s*:\s*''\s*[)]$/");
553 define('_REGEXP_CONCAT_NON_VIDE', "/^(.*)[.]\s*'[^']+'\s*$/");
554
555 // http://doc.spip.org/@compile_cas
556 function compile_cas($tableau, $descr, &$boucles, $id_boucle) {
557
558 $codes = array();
559 // cas de la boucle recursive
560 if (is_array($id_boucle))
561 $id_boucle = $id_boucle[0];
562 $type = !$id_boucle ? '' : $boucles[$id_boucle]->type_requete;
563 $tab = str_repeat("\t", ++$descr['niv']);
564 $mode = _request('var_mode_affiche');
565 $err_e_c = '';
566 // chaque commentaire introduit dans le code doit commencer
567 // par un caractere distinguant le cas, pour exploitation par debug.
568 foreach ($tableau as $p) {
569
570 switch($p->type) {
571 // texte seul
572 case 'texte':
573 $code = "'".str_replace(array("\\","'"),array("\\\\","\\'"), $p->texte)."'";
574
575 $commentaire= strlen($p->texte) . " signes";
576 $avant='';
577 $apres='';
578 $altern = "''";
579 break;
580
581 case 'polyglotte':
582 $code = "";
583 foreach($p->traductions as $k => $v) {
584 $code .= ",'" .
585 str_replace(array("\\","'"),array("\\\\","\\'"), $k) .
586 "' => '" .
587 str_replace(array("\\","'"),array("\\\\","\\'"), $v) .
588 "'";
589 }
590 $code = "choisir_traduction(array(" .
591 substr($code,1) .
592 "))";
593 $commentaire= '&';
594 $avant='';
595 $apres='';
596 $altern = "''";
597 break;
598
599 // inclure
600 case 'include':
601 $p->descr = $descr;
602 $code = calculer_inclure($p, $boucles, $id_boucle);
603 if ($code === false) {
604 $err_e_c = true;
605 $code = "''";
606 } else {
607 $commentaire = '<INCLURE ' . addslashes(str_replace("\n", ' ', $code)) . '>';
608 $avant='';
609 $apres='';
610 $altern = "''";
611 }
612 break;
613
614 // boucle
615 case 'boucle':
616 $nom = $p->id_boucle;
617 $newdescr = $descr;
618 $newdescr['id_mere'] = $nom;
619 $newdescr['niv']++;
620 $avant = calculer_liste($p->avant,
621 $newdescr, $boucles, $id_boucle);
622 $apres = calculer_liste($p->apres,
623 $newdescr, $boucles, $id_boucle);
624 $newdescr['niv']--;
625 $altern = calculer_liste($p->altern,
626 $newdescr, $boucles, $id_boucle);
627 if (($avant === false) OR ($apres === false) OR ($altern === false)) {
628 $err_e_c = true;
629 $code = "''";
630 } else {
631 $code = 'BOUCLE' .
632 str_replace("-","_", $nom) . $descr['nom'] .
633 '($Cache, $Pile, $doublons, $Numrows, $SP)';
634 $commentaire= "?$nom";
635 if (!$boucles[$nom]->milieu
636 AND $boucles[$nom]->type_requete <> 'boucle') {
637 if ($altern != "''") $code .= "\n. $altern";
638 if ($avant<>"''" OR $apres<>"''")
639 spip_log("boucle $nom toujours vide, code superflu dans $id");
640 $avant = $apres = $altern = "''";
641 } else if ($altern != "''") $altern = "($altern)";
642 }
643 break;
644
645 case 'idiome':
646 $l = array();
647 foreach ($p->arg as $k => $v) {
648 if ($k) $l[]= _q($k).' => '.calculer_liste($v,$descr,$boucles,$id_boucle);
649 }
650 $l = !$l ? '' : (", array(".implode(",\n",$l).")");
651 $code = "_T('" . $p->module . ":" .$p->nom_champ . "'$l)";
652 if ($p->param) {
653 $p->id_boucle = $id_boucle;
654 $p->boucles = &$boucles;
655 $code = compose_filtres($p, $code);
656 }
657 $commentaire = ":";
658 $avant='';
659 $apres='';
660 $altern = "''";
661 break;
662
663 case 'champ':
664
665 // cette structure pourrait etre completee des le phrase' (a faire)
666 $p->id_boucle = $id_boucle;
667 $p->boucles = &$boucles;
668 $p->descr = $descr;
669 #$p->interdire_scripts = true;
670 $p->type_requete = $type;
671
672 $code = calculer_champ($p);
673 $commentaire = '#' . $p->nom_champ . $p->etoile;
674 $avant = calculer_liste($p->avant,
675 $descr, $boucles, $id_boucle);
676 $apres = calculer_liste($p->apres,
677 $descr, $boucles, $id_boucle);
678 $altern = "''";
679 // Si la valeur est destinee a une comparaison a ''
680 // forcer la conversion en une chaine par strval
681 // si ca peut etre autre chose qu'une chaine
682 if (($avant != "''" OR $apres != "''")
683 AND $code[0]!= "'"
684 # AND (strpos($code,'interdire_scripts') !== 0)
685 AND !preg_match(_REGEXP_COND_VIDE_NONVIDE, $code)
686 AND !preg_match(_REGEXP_COND_NONVIDE_VIDE, $code)
687 AND !preg_match(_REGEXP_CONCAT_NON_VIDE, $code))
688 $code = "strval($code)";
689 break;
690
691 default:
692 // Erreur de construction de l'arbre de syntaxe abstraite
693 $code = "''";
694 $p->descr = $descr;
695 $err_e_c = array('zbug_erreur_compilation');
696 erreur_squelette($err_e_c, $p);
697 } // switch
698
699 if ($code != "''") {
700 $code = compile_retour($code, $avant, $apres, $altern, $tab, $descr['niv']);
701 $codes[]= (($mode == 'validation') ?
702 "array($code, '$commentaire', " . $p->ligne . ")"
703 : (($mode == 'code') ?
704 "\n// $commentaire\n$code" :
705 $code));
706 }
707 } // foreach
708
709 return $err_e_c ? false : $codes;
710 }
711
712 // production d'une expression conditionnelle ((v=EXP) ? (p . v .s) : a)
713 // mais si EXP est de la forme (t ? 'C' : '') on produit (t ? (p . C . s) : a)
714 // de meme si EXP est de la forme (t ? '' : 'C')
715
716 // http://doc.spip.org/@compile_retour
717 function compile_retour($code, $avant, $apres, $altern, $tab, $n)
718 {
719 if ($avant == "''") $avant = '';
720 if ($apres == "''") $apres = '';
721 if (!$avant AND !$apres AND ($altern==="''")) return $code;
722
723 if (preg_match(_REGEXP_CONCAT_NON_VIDE, $code)) {
724 $t = $code;
725 $cond = '';
726 } elseif (preg_match(_REGEXP_COND_VIDE_NONVIDE,$code, $r)) {
727 $t = $r[2];
728 $cond = '!' . $r[1];
729 } else if (preg_match(_REGEXP_COND_NONVIDE_VIDE,$code, $r)) {
730 $t = $r[2];
731 $cond = $r[1];
732 } else {
733 $t = '$t' . $n;
734 $cond = "($t = $code)!==''";
735 }
736
737 $res = (!$avant ? "" : "$avant . ") .
738 $t .
739 (!$apres ? "" : " . $apres");
740
741 if ($res !== $t) $res = "($res)";
742 return !$cond ? $res : "($cond ?\n\t$tab$res :\n\t$tab$altern)";
743 }
744
745
746 function compile_inclure_doublons($lexemes)
747 {
748 foreach($lexemes as $v)
749 if($v->type === 'include' AND $v->param)
750 foreach($v->param as $r)
751 if (trim($r[0]) === 'doublons')
752 return true;
753 return false;
754 }
755
756 // Prend en argument le texte d'un squelette, le nom de son fichier d'origine,
757 // sa grammaire et un nom. Retourne False en cas d'erreur,
758 // sinon retourne un tableau de fonctions PHP compilees a evaluer,
759 // notamment une fonction portant ce nom et calculant une page.
760 // Pour appeler la fonction produite, lui fournir 2 tableaux de 1 e'le'ment:
761 // - 1er: element 'cache' => nom (du fichier ou` mettre la page)
762 // - 2e: element 0 contenant un environnement ('id_article => $id_article, etc)
763 // Elle retournera alors un tableau de 5 e'le'ments:
764 // - 'texte' => page HTML, application du squelette a` l'environnement;
765 // - 'squelette' => le nom du squelette
766 // - 'process_ins' => 'html' ou 'php' selon la pre'sence de PHP dynamique
767 // - 'invalideurs' => de'pendances de cette page, pour invalider son cache.
768 // - 'entetes' => tableau des entetes http
769 // En cas d'erreur, elle retournera un tableau des 2 premiers elements seulement
770
771 // http://doc.spip.org/@public_compiler_dist
772 function public_compiler_dist($squelette, $nom, $gram, $sourcefile, $connect=''){
773 // Pre-traitement : reperer le charset du squelette, et le convertir
774 // Bonus : supprime le BOM
775 include_spip('inc/charsets');
776 $squelette = transcoder_page($squelette);
777
778 $descr = array('nom' => $nom,
779 'gram' => $gram,
780 'sourcefile' => $sourcefile,
781 'squelette' => $squelette);
782
783 // Phraser le squelette, selon sa grammaire
784
785 $boucles = array();
786 $f = charger_fonction('phraser_' . $gram, 'public');
787
788 $squelette = $f($squelette, '', $boucles, $descr);
789
790 return compiler_squelette($squelette, $boucles, $nom, $descr, $sourcefile, $connect);
791 }
792
793 // Point d'entree pour arbre de syntaxe abstraite fourni en premier argument
794 // Autres specifications comme ci-dessus
795
796 function compiler_squelette($squelette, $boucles, $nom, $descr, $sourcefile, $connect=''){
797 global $tables_jointures;
798 static $trouver_table;
799 spip_timer('calcul_skel');
800
801 if (isset($GLOBALS['var_mode']) AND $GLOBALS['var_mode'] == 'debug') {
802 $GLOBALS['debug_objets']['squelette'][$nom] = $descr['squelette'];
803 $GLOBALS['debug_objets']['sourcefile'][$nom] = $sourcefile;
804
805 if (!isset($GLOBALS['debug_objets']['principal']))
806 $GLOBALS['debug_objets']['principal'] = $nom;
807 }
808 foreach ($boucles as $id => $boucle) {
809 $GLOBALS['debug_objets']['boucle'][$nom.$id] = $boucle;
810 }
811 $descr['documents'] = compile_inclure_doublons($squelette);
812
813 // Demander la description des tables une fois pour toutes
814 // et reperer si les doublons sont demandes
815 // pour un inclure ou une boucle document
816 // c'est utile a la fonction champs_traitements
817 if (!$trouver_table)
818 $trouver_table = charger_fonction('trouver_table', 'base');
819
820 foreach($boucles as $id => $boucle) {
821 if (!($type = $boucle->type_requete)) continue;
822 if (!$descr['documents'] AND (
823 (($type == 'documents') AND $boucle->doublons) OR
824 compile_inclure_doublons($boucle->avant) OR
825 compile_inclure_doublons($boucle->apres) OR
826 compile_inclure_doublons($boucle->milieu) OR
827 compile_inclure_doublons($boucle->altern)))
828 $descr['documents'] = true;
829 if ($type != 'boucle') {
830 if (!$boucles[$id]->sql_serveur AND $connect)
831 $boucles[$id]->sql_serveur = $connect;
832 $show = $trouver_table($type, $boucles[$id]->sql_serveur);
833 // si la table n'existe pas avec le connecteur par defaut,
834 // c'est peut etre une table qui necessite son connecteur dedie fourni
835 // permet une ecriture allegee (GEO) -> (geo:GEO)
836 if (!$show AND $show=$trouver_table($type, strtolower($type)))
837 $boucles[$id]->sql_serveur = strtolower($type);
838 if ($show) {
839 $boucles[$id]->show = $show;
840 // recopie les infos les plus importantes
841 $boucles[$id]->primary = $show['key']["PRIMARY KEY"];
842 $boucles[$id]->id_table = $x = $show['id_table'];
843 $boucles[$id]->from[$x] = $nom_table = $show['table'];
844
845 $boucles[$id]->descr = &$descr;
846 if ((!$boucles[$id]->jointures)
847 AND (isset($tables_jointures[$nom_table]))
848 AND is_array($x = $tables_jointures[$nom_table]))
849 $boucles[$id]->jointures = $x;
850 if ($boucles[$id]->jointures_explicites){
851 $jointures = preg_split("/\s+/",$boucles[$id]->jointures_explicites);
852 while ($j=array_pop($jointures))
853 array_unshift($boucles[$id]->jointures,$j);
854 }
855 } else {
856 // Pas une erreur si la table est optionnelle
857 if ($boucles[$id]->table_optionnelle)
858 $boucles[$id]->type_requete = '';
859 else {
860 $boucles[$id]->type_requete = false;
861 $boucle = $boucles[$id];
862 $x = (!$boucle->sql_serveur ? '' :
863 ($boucle->sql_serveur . ":")) .
864 $type;
865 $msg = array('zbug_table_inconnue',
866 array('table' => $x));
867 erreur_squelette($msg, $boucle);
868 }
869 }
870 }
871 }
872
873 // Commencer par reperer les boucles appelees explicitement
874 // car elles indexent les arguments de maniere derogatoire
875 foreach($boucles as $id => $boucle) {
876 if ($boucle->type_requete == 'boucle' AND $boucle->param) {
877 $boucles[$id]->descr = &$descr;
878 $rec = &$boucles[$boucle->param[0]];
879 if (!$rec) {
880 $msg = array('zbug_boucle_recursive_undef',
881 array('nom' => $boucle->param[0]));
882 erreur_squelette($msg, $boucle);
883 $boucles[$id]->type_requete = false;
884 } else {
885 $rec->externe = $id;
886 $descr['id_mere'] = $id;
887 $boucles[$id]->return =
888 calculer_liste(array($rec),
889 $descr,
890 $boucles,
891 $boucle->param);
892 }
893 }
894 }
895 foreach($boucles as $id => $boucle) {
896 $id = strval($id); // attention au type dans index_pile
897 $type = $boucle->type_requete;
898 if ($type AND $type != 'boucle') {
899 $crit = !$boucle->param ? true : calculer_criteres($id, $boucles);
900 $descr['id_mere'] = $id;
901 $boucles[$id]->return =
902 calculer_liste($boucle->milieu,
903 $descr,
904 $boucles,
905 $id);
906 // Si les criteres se sont mal compiles
907 // ne pas tenter d'assembler le code final
908 // (mais compiler le corps pour detection d'erreurs)
909 if (is_array($crit))
910 $boucles[$id]->type_requete = false;
911 }
912 }
913
914 // idem pour la racine
915 $descr['id_mere'] = '';
916 $corps = calculer_liste($squelette, $descr, $boucles);
917 $debug = (isset($GLOBALS['var_mode']) AND $GLOBALS['var_mode']=='debug');
918
919 if ($debug) {
920 include_spip('public/decompiler');
921 include_spip('public/format_' . _EXTENSION_SQUELETTES);
922 }
923 // Calcul du corps de toutes les fonctions PHP,
924 // en particulier les requetes SQL et TOTAL_BOUCLE
925 // de'terminables seulement maintenant
926
927 foreach($boucles as $id => $boucle) {
928 $boucle = $boucles[$id] = pipeline('pre_boucle', $boucle);
929 if ($boucle->return === false) continue;
930 // appeler la fonction de definition de la boucle
931
932 if ($req = $boucle->type_requete) {
933 $f = 'boucle_'.strtoupper($req);
934 // si pas de definition perso, definition spip
935 if (!function_exists($f)) $f = $f.'_dist';
936 // laquelle a une definition par defaut
937 if (!function_exists($f)) $f = 'boucle_DEFAUT';
938 if (!function_exists($f)) $f = 'boucle_DEFAUT_dist';
939 $req = "\n\n\tstatic \$connect = " .
940 _q($boucle->sql_serveur) .
941 ";" .
942 $f($id, $boucles);
943 } else $req = ("\n\treturn '';");
944
945 $boucles[$id]->return =
946 "function BOUCLE" . strtr($id,"-","_") . $nom .
947 '(&$Cache, &$Pile, &$doublons, &$Numrows, $SP) {' .
948 $req .
949 "\n}\n\n";
950
951 if ($debug)
952 $GLOBALS['debug_objets']['code'][$nom.$id] = $boucles[$id]->return;
953 }
954
955 // Au final, si le corps ou un critere au moins s'est mal compile
956 // retourner False, sinon inserer leur decompilation
957 if (is_bool($corps)) return false;
958 foreach($boucles as $id => $boucle) {
959 if ($boucle->return === false) return false;
960 $boucle->return = "\n\n/* BOUCLE " .
961 $boucle->type_requete .
962 " " .
963 (!$debug ? '' :
964 str_replace('*/', '* /',
965 decompiler_criteres($boucle->param,
966 $boucle->criteres))) .
967 " */\n\n " .
968 $boucle->return;
969 }
970
971 $secondes = spip_timer('calcul_skel');
972 spip_log("COMPIL ($secondes) [$sourcefile] $nom.php");
973
974 // Assimiler la fct principale a une boucle anonyme, c'est plus simple
975 $code = new Boucle;
976 $code->descr = $descr;
977 $code->return = '
978 //
979 // Fonction principale du squelette ' .
980 $sourcefile .
981 ($connect ? " pour $connect" : '') .
982 (!CODE_COMMENTE ? '' : "\n// Temps de compilation total: $secondes") .
983 "\n//" .
984 (!$debug ? '' : ("\n/*\n" .
985 str_replace('*/', '* /', public_decompiler($squelette))
986 . "\n*/")) . "
987
988 function " . $nom . '($Cache, $Pile, $doublons=array(), $Numrows=array(), $SP=0) {
989
990 '
991 // reporter de maniere securisee les doublons inclus
992 .'
993 if (isset($Pile[0]["doublons"]) AND is_array($Pile[0]["doublons"]))
994 $doublons = nettoyer_env_doublons($Pile[0]["doublons"]);
995
996 $connect = ' .
997 _q($connect) . ';
998 $page = ' .
999 // ATTENTION, le calcul de l'expression $corps affectera $Cache
1000 // c'est pourquoi on l'affecte a la variable auxiliaire $page.
1001 // avant de referencer $Cache
1002 $corps . ";
1003
1004 return analyse_resultat_skel(".var_export($nom,true)
1005 .", \$Cache, \$page, ".var_export($sourcefile,true).");
1006 }";
1007
1008 $boucles[''] = $code;
1009 return $boucles;
1010 }
1011
1012 ?>