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