3 /***************************************************************************\
4 * SPIP, Systeme de publication pour l'internet *
6 * Copyright (c) 2001-2014 *
7 * Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James *
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 \***************************************************************************/
15 * Fichier principal du compilateur de squelettes
17 * @package SPIP\Compilateur\Compilation
20 if (!defined('_ECRIRE_INC_VERSION')) return;
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);
27 // definition des structures de donnees
28 include_spip('public/interfaces');
30 // Definition de la structure $p, et fonctions de recherche et de reservation
31 // dans l'arborescence des boucles
32 include_spip('public/references');
34 // production du code qui peut etre securisee
35 include_spip('public/sandbox');
37 // definition des boucles
38 include_spip('public/boucles');
40 // definition des criteres
41 include_spip('public/criteres');
43 // definition des balises
44 include_spip('public/balises');
46 // Gestion des jointures
47 include_spip('public/jointures');
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.
59 // http://doc.spip.org/@argumenter_inclure
60 function argumenter_inclure($params, $rejet_filtres, $p, &$boucles, $id_boucle, $echap=true, $lang = '', $fond1=false){
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);
68 foreach($couple as $n => $val) {
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);
77 else $l[1] = calculer_liste($val, $p->descr
, $boucles, $id_boucle);
79 preg_match(",^([^=]*)(=?)(.*)$,m", $var->texte
,$m);
84 if (preg_match(',^[\'"](.*)[\'"]$,', $v, $m)) $v = $m[1];
87 } elseif ($k OR $n OR $fond1) {
93 ?
calculer_liste($val, $p->descr
, $boucles, $id_boucle)
94 : '$GLOBALS["spip_lang"]';
97 ?
index_pile($id_boucle, $var, $boucles)
98 : calculer_liste($val, $p->descr
, $boucles, $id_boucle);
100 $val = ($echap?
"\'$var\' => ' . argumenter_squelette(":"'$var' => ")
101 . $val . ($echap?
") . '":" ");
102 else $val = $echap ?
"'.$val.'" : $val;
108 if ($erreur_p_i_i) return false;
109 // Cas particulier de la langue : si {lang=xx} est definie, on
110 // la passe, sinon on passe la langue courante au moment du calcul
111 // sauf si on n'en veut pas
112 if ($lang === false) return $l;
113 if (!$lang) $lang = '$GLOBALS["spip_lang"]';
114 $l['lang'] = ($echap?
"\'lang\' => ' . argumenter_squelette(":"'lang' => ") . $lang . ($echap?
") . '":" ");
120 * Code d'appel à un <INCLURE()>
122 * Code PHP pour un squelette (aussi pour #INCLURE, #MODELE #LES_AUTEURS)
124 define('CODE_RECUPERER_FOND', 'recuperer_fond(%s, %s, array(%s), %s)');
127 * Compile une inclusion <INCLURE> ou #INCLURE
130 * Description de l'inclusion (AST au niveau de l'inclure)
131 * @param array $boucles
133 * @param string $id_boucle
134 * Identifiant de la boucle contenant l'inclure
136 * Code PHP appelant l'inclusion
138 function calculer_inclure($p, &$boucles, $id_boucle) {
140 $_contexte = argumenter_inclure($p->param
, false, $p, $boucles, $id_boucle, true, '', true);
141 if (is_string($p->texte
)) {
142 $fichier = $p->texte
;
143 $code = "\"$fichier\"";
146 $code = calculer_liste($p->texte
, $p->descr
, $boucles, $id_boucle);
147 if ($code AND preg_match("/^'([^']*)'/s", $code, $r))
151 if (!$code OR $code === '""') {
152 $erreur_p_i_i = array('zbug_parametres_inclus_incorrects',
153 array('param' => $code));
154 erreur_squelette($erreur_p_i_i, $p);
157 $compil = texte_script(memoriser_contexte_compil($p));
159 if (is_array($_contexte)) {
160 // Critere d'inclusion {env} (et {self} pour compatibilite ascendante)
161 if ($env = (isset($_contexte['env'])||
isset($_contexte['self']))) {
162 unset($_contexte['env']);
165 // noter les doublons dans l'appel a public.php
166 if (isset($_contexte['doublons'])) {
167 $_contexte['doublons'] = "\\'doublons\\' => '.var_export(\$doublons,true).'";
170 if ($ajax = isset($_contexte['ajax'])){
171 $ajax = preg_replace(",=>(.*)$,ims",'=> ($v=(\\1))?$v:true',$_contexte['ajax']);
172 unset($_contexte['ajax']);
175 $_contexte = join(",\n\t", $_contexte);
178 return false; // j'aurais voulu toucher le fond ...
180 $contexte = 'array(' . $_contexte .')';
183 $contexte = "array_merge('.var_export(\$Pile[0],1).',$contexte)";
186 // s'il y a une extension .php, ce n'est pas un squelette
187 if (preg_match('/^.+[.]php$/s', $fichier)) {
188 $code = sandbox_composer_inclure_php($fichier, $p, $contexte);
190 $_options[] = "\"compil\"=>array($compil)";
193 $code = " ' . argumenter_squelette($code) . '";
194 $code = "echo " . sprintf(CODE_RECUPERER_FOND
, $code, $contexte, implode(',',$_options), "_request(\"connect\")") . ';';
197 return "\n'<'.'". "?php ". $code . "\n?'." . "'>'";
202 * Gérer les statuts declarés pour cette table
204 * S'il existe des statuts sur cette table, déclarés dans la description
205 * d'un objet éditorial, applique leurs contraintes
207 * @param Boucle $boucle
208 * Descrition de la boucle
209 * @param bool $echapper
210 * true pour échapper le code créé
211 * @param bool $ignore_previsu
212 * true pour ne tester que le cas publie et ignorer l'eventuel var_mode=preview de la page
214 function instituer_boucle(&$boucle, $echapper=true, $ignore_previsu=false){
216 $show['statut'][] = array(
217 'champ'=>'statut', // champ de la table sur lequel porte le filtrage par le statut
218 'publie'=>'publie', // valeur ou liste de valeurs, qui definissent l'objet comme publie.
219 'previsu'=>'publie,prop', // valeur ou liste de valeurs qui sont visibles en previsu
220 'post_date'=>'date', // un champ de date pour la prise en compte des post_dates, ou rien sinon
221 'exception'=>'statut', // liste des modificateurs qui annulent le filtrage par statut
222 // si plusieurs valeurs : array('statut','tout','lien')
225 Pour 'publier' ou 'previsu', si la chaine commence par un "!" on exclu au lieu de filtrer sur les valeurs donnees
226 si la chaine est vide, on ne garde rien si elle est seulement "!" on n'exclu rien
228 Si le statut repose sur une jointure, 'champ' est alors un tableau du format suivant :
236 champstatut est alors le champ statut sur la tablen
237 dans les jointures, clen peut etre un tableau pour une jointure complexe : array('id_objet','id_article','objet','article')
239 $id_table = $boucle->id_table
;
240 $show = $boucle->show
;
241 if (isset($show['statut']) AND $show['statut']){
242 foreach($show['statut'] as $k=>$s){
243 // Restreindre aux elements publies si pas de {statut} ou autre dans les criteres
245 if (isset($s['exception'])) {
246 foreach(is_array($s['exception'])?
$s['exception']:array($s['exception']) as $m) {
247 if (isset($boucle->modificateur
[$m]) OR isset($boucle->modificateur
['criteres'][$m])) {
255 if (is_array($s['champ'])){
256 $statut = preg_replace(',\W,','',array_pop($s['champ'])); // securite
257 $jointures = array();
258 // indiquer la description de chaque table dans le tableau de jointures,
259 // ce qui permet d'eviter certains GROUP BY inutiles.
260 $trouver_table = charger_fonction('trouver_table', 'base');
261 foreach($s['champ'] as $j) {
263 $def = $trouver_table($id);
264 $jointures[] = array('',array($id,$def),end($j));
266 $jointures[0][0] = $id_table;
267 if (!array_search($id, $boucle->from
)){
268 include_spip('public/jointures');
269 fabrique_jointures($boucle, $jointures, true, $boucle->show
, $id_table, '', $echapper);
271 // trouver l'alias de la table d'arrivee qui porte le statut
272 $id = array_search($id, $boucle->from
);
276 $statut = preg_replace(',\W,','',$s['champ']); // securite
278 $mstatut = $id .'.'.$statut;
280 $arg_ignore_previsu=($ignore_previsu?
",true":'');
281 include_spip('public/quete');
282 if (isset($s['post_date']) AND $s['post_date']
283 AND $GLOBALS['meta']["post_dates"] == 'non'){
284 $date = $id.'.'.preg_replace(',\W,','',$s['post_date']); // securite
285 array_unshift($boucle->where
,
287 "\nquete_condition_postdates('$date',"._q($boucle->sql_serveur
)."$arg_ignore_previsu)"
289 quete_condition_postdates($date,$boucle->sql_serveur
,$ignore_previsu)
292 array_unshift($boucle->where
,
294 "\nquete_condition_statut('$mstatut',"
295 . _q($s['previsu']).","
296 ._q($s['publie']).","
297 ._q($boucle->sql_serveur
)."$arg_ignore_previsu)"
299 quete_condition_statut($mstatut,$s['previsu'],$s['publie'],$boucle->sql_serveur
,$ignore_previsu)
307 * Produit le corps PHP d'une boucle Spip.
309 * Ce corps remplit une variable $t0 retournée en valeur.
310 * Ici on distingue boucles recursives et boucle à requête SQL
311 * et on insère le code d'envoi au debusqueur du resultat de la fonction.
313 * @param string $id_boucle
314 * Identifiant de la boucle
315 * @param array $boucles
318 * Code PHP compilé de la boucle
320 function calculer_boucle($id_boucle, &$boucles) {
322 $boucle = &$boucles[$id_boucle];
323 instituer_boucle($boucle);
324 $boucles[$id_boucle] = pipeline('post_boucle', $boucles[$id_boucle]);
326 // en mode debug memoriser les premiers passages dans la boucle,
327 // mais pas tous, sinon ca pete.
328 if (_request('var_mode_affiche') != 'resultat')
331 $trace = $boucles[$id_boucle]->descr
['nom'] . $id_boucle;
332 $trace = "if (count(@\$GLOBALS['debug_objets']['resultat']['$trace'])<3)
333 \$GLOBALS['debug_objets']['resultat']['$trace'][] = \$t0;";
335 return ($boucles[$id_boucle]->type_requete
== TYPE_RECURSIF
)
336 ?
calculer_boucle_rec($id_boucle, $boucles, $trace)
337 : calculer_boucle_nonrec($id_boucle, $boucles, $trace);
342 * Compilation d'une boucle recursive.
345 * Il suffit (ET IL FAUT) sauvegarder les valeurs des arguments passes par
346 * reference, car par definition un tel passage ne les sauvegarde pas
348 * @param string $id_boucle
349 * Identifiant de la boucle
350 * @param array $boucles
352 * @param string $trace
353 * Code PHP (en mode debug uniquement) servant à conserver une
354 * trace des premières valeurs de la boucle afin de pouvoir
355 * les afficher dans le débugueur ultérieurement
357 * Code PHP compilé de la boucle récursive
359 function calculer_boucle_rec($id_boucle, &$boucles, $trace) {
360 $nom = $boucles[$id_boucle]->param
[0];
362 // Numrows[$nom] peut ne pas être encore defini
363 "\n\t\$save_numrows = (isset(\$Numrows['$nom']) ? \$Numrows['$nom'] : array());"
364 . "\n\t\$t0 = " . $boucles[$id_boucle]->return . ";"
365 . "\n\t\$Numrows['$nom'] = (\$save_numrows);"
367 . "\n\treturn \$t0;";
371 * Compilation d'une boucle non recursive.
373 * La constante donne le cadre systématique du code:
374 * %s1: initialisation des arguments de calculer_select
375 * %s2: appel de calculer_select en donnant un contexte pour les cas d'erreur
376 * %s3: initialisation du sous-tableau Numrows[id_boucle]
377 * %s4: sauvegarde de la langue et calcul des invariants de boucle sur elle
378 * %s5: boucle while sql_fetch ou str_repeat si corps monotone
379 * %s6: restauration de la langue
380 * %s7: liberation de la ressource, en tenant compte du serveur SQL
381 * %s8: code de trace eventuel avant le retour
383 define('CODE_CORPS_BOUCLE', '%s
384 if (defined("_BOUCLE_PROFILER")) $timer = time()+microtime();
387 $iter = IterFactory::create(
398 if (defined("_BOUCLE_PROFILER")
399 AND 1000*($timer = (time()+microtime())-$timer) > _BOUCLE_PROFILER)
400 spip_log(intval(1000*$timer)."ms %s","profiler"._LOG_AVERTISSEMENT);
405 * Compilation d'une boucle (non recursive).
407 * @param string $id_boucle
408 * Identifiant de la boucle
409 * @param array $boucles
411 * @param string $trace
412 * Code PHP (en mode debug uniquement) servant à conserver une
413 * trace des premières valeurs de la boucle afin de pouvoir
414 * les afficher dans le débugueur ultérieurement
416 * Code PHP compilé de la boucle récursive
418 function calculer_boucle_nonrec($id_boucle, &$boucles, $trace) {
420 $boucle = &$boucles[$id_boucle];
421 $return = $boucle->return;
422 $type_boucle = $boucle->type_requete
;
423 $primary = $boucle->primary
;
424 $constant = preg_match(CODE_MONOTONE
, str_replace("\\'",'', $return));
425 $flag_cpt = $boucle->mode_partie ||
$boucle->cptrows
;
428 // faudrait expanser le foreach a la compil, car y en a souvent qu'un
429 // et puis faire un [] plutot qu'un "','."
430 if ($boucle->doublons
)
431 $corps .= "\n\t\t\tforeach(" . $boucle->doublons
. ' as $k) $doublons[$k] .= "," . ' .
432 index_pile($id_boucle, $primary, $boucles)
435 // La boucle doit-elle selectionner la langue ?
436 // - par defaut, les boucles suivantes le font
437 // (sauf si forcer_lang==true ou si le titre contient <multi>).
438 // - a moins d'une demande explicite via {!lang_select}
439 if (!$constant && $boucle->lang_select
!= 'non' &&
440 (($boucle->lang_select
== 'oui') ||
441 in_array($type_boucle, array(
442 'articles', 'rubriques', 'hierarchie', 'breves'
445 // Memoriser la langue avant la boucle et la restituer apres
446 // afin que le corps de boucle affecte la globale directement
447 $init_lang = "lang_select(\$GLOBALS['spip_lang']);\n\t";
448 $fin_lang = "lang_select();\n\t";
451 "\n\t\tlang_select_public("
452 . index_pile($id_boucle, 'lang', $boucles)
453 . ", '".$boucle->lang_select
."'"
454 . (in_array($type_boucle, array(
455 'articles', 'rubriques', 'hierarchie', 'breves'
456 )) ?
', '.index_pile($id_boucle, 'titre', $boucles) : '')
462 // sortir les appels au traducteur (invariants de boucle)
463 if (strpos($return, '?php') === false
464 AND preg_match_all("/\W(_T[(]'[^']*'[)])/", $return, $r)) {
466 foreach($r[1] as $t) {
467 $init_lang .= "\n\t\$l$i = $t;";
468 $return = str_replace($t, "\$l$i", $return);
474 // gestion optimale des separateurs et des boucles constantes
475 if (count($boucle->separateur
))
476 $code_sep = ("'" . str_replace("'","\'",join('',$boucle->separateur
)) . "'");
479 ((!$boucle->separateur
) ?
480 (($constant && !$corps && !$flag_cpt) ?
$return :
481 (($return==="''") ?
'' :
482 ("\n\t\t" . '$t0 .= ' . $return . ";"))) :
484 ((strpos($return, '$t1.') === 0) ?
485 (".=" . substr($return,4)) :
488 '$t0 .= ((strlen($t1) && strlen($t0)) ? ' . $code_sep . " : '') . \$t1;"));
490 // Calculer les invalideurs si c'est une boucle non constante et si on
491 // souhaite invalider ces elements
492 if (!$constant AND $primary) {
493 include_spip('inc/invalideur');
494 if (function_exists($i = 'calcul_invalideurs'))
495 $corps = $i($corps, $primary, $boucles, $id_boucle);
498 // gerer le compteur de boucle
499 // avec ou sans son utilisation par les criteres {1/3} {1,4} {n-2,1}...
501 if ($boucle->partie
OR $boucle->cptrows
)
502 $corps = "\n\t\t\$Numrows['$id_boucle']['compteur_boucle']++;"
506 // si le corps est une constante, ne pas appeler le serveur N fois!
508 if (preg_match(CODE_MONOTONE
,str_replace("\\'",'',$corps), $r)) {
509 if (!isset($r[2]) OR (!$r[2])) {
510 if (!$boucle->numrows
)
511 return "\n\t\$t0 = '';";
515 $boucle->numrows
= true;
516 $corps = "\n\t\$t0 = str_repeat($corps, \$Numrows['$id_boucle']['total']);";
518 } else $corps = "while (\$Pile[\$SP]=\$iter->fetch()) {\n$corps\n }";
521 if (!$boucle->select
) {
522 if (!$boucle->numrows
OR $boucle->limit
OR $boucle->mode_partie
OR $boucle->group
)
524 else $count = 'count(*)';
525 $boucles[$id_boucle]->select
[]= $count;
529 $nums = "\n\t// COMPTEUR\n\t"
530 . "\$Numrows['$id_boucle']['compteur_boucle'] = 0;\n\t";
533 if ($boucle->numrows
OR $boucle->mode_partie
) {
534 $nums .= "\$Numrows['$id_boucle']['total'] = @intval(\$iter->count());"
535 . $boucle->mode_partie
539 // Ne calculer la requete que maintenant
540 // car ce qui precede appelle index_pile qui influe dessus
542 $init = (($init = $boucles[$id_boucle]->doublons
)
543 ?
("\n\t$init = array();") : '')
544 . calculer_requete_sql($boucles[$id_boucle]);
546 $contexte = memoriser_contexte_compil($boucle);
548 $a = sprintf(CODE_CORPS_BOUCLE
,
558 'BOUCLE'.$id_boucle .' @ '.($boucle->descr
['sourcefile'])
567 * Calcule le code PHP d'une boucle contenant les informations qui produiront une requête SQL
569 * Le code produit est un tableau associatif $command contenant les informations
570 * pour que la boucle produise ensuite sa requête, tel que $command['from'] = 'spip_articles';
572 * @param Boucle $boucle
575 * Code PHP compilé définissant les informations de requête
577 function calculer_requete_sql($boucle)
580 $init[] = calculer_dec('table', "'" . $boucle->id_table
."'");
581 $init[] = calculer_dec('id', "'" . $boucle->id_boucle
."'");
582 # En absence de champ c'est un decompte :
583 $init[] = calculer_dec('from', calculer_from($boucle));
584 $init[] = calculer_dec('type', calculer_from_type($boucle));
585 $init[] = calculer_dec('groupby', 'array(' . (($g=join("\",\n\t\t\"",$boucle->group
))?
'"'.$g.'"':'') . ")");
586 $init[] = calculer_dec('select', 'array("' . join("\",\n\t\t\"", $boucle->select
). "\")");
587 $init[] = calculer_dec('orderby', 'array(' . calculer_order($boucle) . ")");
588 $init[] = calculer_dec('where', calculer_dump_array($boucle->where
));
589 $init[] = calculer_dec('join', calculer_dump_join($boucle->join
));
590 $init[] = calculer_dec('limit',
591 (strpos($boucle->limit
, 'intval') === false ?
592 "'".$boucle->limit
."'"
595 $init[] = calculer_dec('having', calculer_dump_array($boucle->having
));
597 foreach ($init as $i){
599 $s .= "\n\t\t".end($i);
601 $d .= "\n\t".end($i);
604 return ($boucle->hierarchie ?
"\n\t$boucle->hierarchie" : '')
607 . "\n\t".'if (!isset($command[\'table\'])) {'
614 * Retourne une chaîne des informations du contexte de compilation
616 * Retourne la source, le nom, l'identifiant de boucle, la ligne, la langue
617 * de l'élément dans une chaîne.
619 * @see reconstruire_contexte_compil()
622 * Objet de l'AST dont on mémorise le contexte
624 * Informations du contexte séparés par des virgules,
625 * qui peut être utilisé pour la production d'un tableau array()
627 function memoriser_contexte_compil($p) {
628 return join(',', array(
629 _q($p->descr
['sourcefile']),
630 _q($p->descr
['nom']),
633 '$GLOBALS[\'spip_lang\']'));
637 * Reconstruit un contexte de compilation
639 * Pour un tableau d'information de contexte donné,
640 * retourne un objet Contexte (objet générique de l'AST)
641 * avec ces informations
643 * @see memoriser_contexte_compil()
645 * @param array $context_compil
646 * Tableau des informations du contexte
650 function reconstruire_contexte_compil($context_compil)
652 if (!is_array($context_compil)) return $context_compil;
654 $p->descr
= array('sourcefile' => $context_compil[0],
655 'nom' => $context_compil[1]);
656 $p->id_boucle
= $context_compil[2];
657 $p->ligne
= $context_compil[3];
658 $p->lang
= $context_compil[4];
662 // http://doc.spip.org/@calculer_dec
663 function calculer_dec($nom, $val)
665 $static = 'if (!isset($command[\''.$nom.'\'])) ';
666 // si une variable apparait dans le calcul de la clause
667 // il faut la re-evaluer a chaque passage
669 strpos($val, '$') !== false
671 OR strpos($val, 'sql_') !== false
673 $test = str_replace(array("array(",'\"',"\'"),array("","",""),$val) // supprimer les array( et les echappements de guillemets
674 AND strpos($test,"(")!==FALSE // si pas de parenthese ouvrante, pas de fonction, on peut sortir
675 AND $test = preg_replace(",'[^']*',UimsS","",$test) // supprimer les chaines qui peuvent contenir des fonctions SQL qui ne genent pas
676 AND preg_match(",\w+\s*\(,UimsS",$test,$regs) // tester la presence de fonctions restantes
681 return array($static,'$command[\''.$nom.'\'] = ' . $val . ';');
684 // http://doc.spip.org/@calculer_dump_array
685 function calculer_dump_array($a)
687 if (!is_array($a)) return $a ;
689 if ($a AND $a[0] == "'?'")
690 return ("(" . calculer_dump_array($a[1]) .
691 " ? " . calculer_dump_array($a[2]) .
692 " : " . calculer_dump_array($a[3]) .
695 foreach($a as $v) $res .= ", " . calculer_dump_array($v);
696 return "\n\t\t\tarray(" . substr($res,2) . ')';
700 // http://doc.spip.org/@calculer_dump_join
701 function calculer_dump_join($a)
704 foreach($a as $k => $v)
705 $res .= ", '$k' => array(".implode(',',$v).")";
706 return 'array(' . substr($res,2) . ')';
709 // http://doc.spip.org/@calculer_from
710 function calculer_from(&$boucle)
713 foreach($boucle->from
as $k => $v) $res .= ",'$k' => '$v'";
714 return 'array(' . substr($res,1) . ')';
717 // http://doc.spip.org/@calculer_from_type
718 function calculer_from_type(&$boucle)
721 foreach($boucle->from_type
as $k => $v) $res .= ",'$k' => '$v'";
722 return 'array(' . substr($res,1) . ')';
725 // http://doc.spip.org/@calculer_order
726 function calculer_order(&$boucle)
728 if (!$order = $boucle->order
729 AND !$order = $boucle->default_order
)
732 /*if (isset($boucle->modificateur['collate'])){
733 $col = "." . $boucle->modificateur['collate'];
734 foreach($order as $k=>$o)
735 if (strpos($order[$k],'COLLATE')===false)
738 return join(', ', $order);
741 // Production du code PHP a partir de la sequence livree par le phraseur
742 // $boucles est passe par reference pour affectation par index_pile.
743 // Retourne une expression PHP,
744 // (qui sera argument d'un Return ou la partie droite d'une affectation).
746 // http://doc.spip.org/@calculer_liste
747 function calculer_liste($tableau, $descr, &$boucles, $id_boucle='') {
748 if (!$tableau) return "''";
749 if (!isset($descr['niv'])) $descr['niv'] = 0;
750 $codes = compile_cas($tableau, $descr, $boucles, $id_boucle);
751 if ($codes === false) return false;
753 if (!$n) return "''";
754 $tab = str_repeat("\t", $descr['niv']);
755 if (_request('var_mode_affiche') != 'validation') {
760 foreach($codes as $code) {
761 if (!preg_match("/^'[^']*'$/", $code)
762 OR substr($res,-1,1)!=="'")
763 $res .= " .\n$tab$code";
765 $res = substr($res,0,-1) . substr($code,1);
768 return '(' . substr($res,2+
$descr['niv']) . ')';
771 $nom = $descr['nom'] . $id_boucle . ($descr['niv']?
$descr['niv']:'');
772 return "join('', array_map('array_shift', \$GLOBALS['debug_objets']['sequence']['$nom'] = array(" . join(" ,\n$tab", $codes) . ")))";
776 define('_REGEXP_COND_VIDE_NONVIDE',"/^[(](.*)[?]\s*''\s*:\s*('[^']+')\s*[)]$/");
777 define('_REGEXP_COND_NONVIDE_VIDE',"/^[(](.*)[?]\s*('[^']+')\s*:\s*''\s*[)]$/");
778 define('_REGEXP_CONCAT_NON_VIDE', "/^(.*)[.]\s*'[^']+'\s*$/");
780 // http://doc.spip.org/@compile_cas
781 function compile_cas($tableau, $descr, &$boucles, $id_boucle) {
784 // cas de la boucle recursive
785 if (is_array($id_boucle))
786 $id_boucle = $id_boucle[0];
787 $type = !$id_boucle ?
'' : $boucles[$id_boucle]->type_requete
;
788 $tab = str_repeat("\t", ++
$descr['niv']);
789 $mode = _request('var_mode_affiche');
791 // chaque commentaire introduit dans le code doit commencer
792 // par un caractere distinguant le cas, pour exploitation par debug.
793 foreach ($tableau as $p) {
798 $code = sandbox_composer_texte($p->texte
, $p);
799 $commentaire= strlen($p->texte
) . " signes";
807 foreach($p->traductions
as $k => $v) {
809 str_replace(array("\\","'"),array("\\\\","\\'"), $k) .
811 str_replace(array("\\","'"),array("\\\\","\\'"), $v) .
814 $code = "choisir_traduction(array(" .
826 $code = calculer_inclure($p, $boucles, $id_boucle);
827 if ($code === false) {
831 $commentaire = '<INCLURE ' . addslashes(str_replace("\n", ' ', $code)) . '>';
840 $nom = $p->id_boucle
;
842 $newdescr['id_mere'] = $nom;
844 $avant = calculer_liste($p->avant
,
845 $newdescr, $boucles, $id_boucle);
846 $apres = calculer_liste($p->apres
,
847 $newdescr, $boucles, $id_boucle);
849 $altern = calculer_liste($p->altern
,
850 $newdescr, $boucles, $id_boucle);
851 if (($avant === false) OR ($apres === false) OR ($altern === false)) {
856 str_replace("-","_", $nom) . $descr['nom'] .
857 '($Cache, $Pile, $doublons, $Numrows, $SP)';
858 $commentaire= "?$nom";
859 if (!$boucles[$nom]->milieu
860 AND $boucles[$nom]->type_requete
<> TYPE_RECURSIF
) {
861 if ($altern != "''") $code .= "\n. $altern";
862 if ($avant<>"''" OR $apres<>"''")
863 spip_log("boucle $nom toujours vide, code superflu dans $id");
864 $avant = $apres = $altern = "''";
865 } else if ($altern != "''") $altern = "($altern)";
872 foreach ($p->arg
as $k => $v) {
873 $_v = calculer_liste($v, $descr, $boucles, $id_boucle);
875 $l[] = _q($k) . ' => ' . $_v;
880 // Si le module n'est pas fourni, l'expliciter sauf si calculé
882 $m = $p->module
.':'.$p->nom_champ
;
883 } elseif ($p->nom_champ
) {
884 $m = MODULES_IDIOMES
.':'.$p->nom_champ
;
888 $code = (!$code ?
"'$m'" :
889 ($m ?
"'$m' . $code" :
890 ("(strpos(\$x=$code, ':') ? \$x : ('" . MODULES_IDIOMES
. ":' . \$x))")))
891 . (!$l ?
'' : (", array(" . implode(",\n", $l) . ")"));
894 $p->id_boucle
= $id_boucle;
895 $p->boucles
= &$boucles;
896 $code = compose_filtres($p, $code);
906 // cette structure pourrait etre completee des le phrase' (a faire)
907 $p->id_boucle
= $id_boucle;
908 $p->boucles
= &$boucles;
910 #$p->interdire_scripts = true;
911 $p->type_requete
= $type;
913 $code = calculer_champ($p);
914 $commentaire = '#' . $p->nom_champ
. $p->etoile
;
915 $avant = calculer_liste($p->avant
,
916 $descr, $boucles, $id_boucle);
917 $apres = calculer_liste($p->apres
,
918 $descr, $boucles, $id_boucle);
920 // Si la valeur est destinee a une comparaison a ''
921 // forcer la conversion en une chaine par strval
922 // si ca peut etre autre chose qu'une chaine
923 if (($avant != "''" OR $apres != "''")
925 # AND (strpos($code,'interdire_scripts') !== 0)
926 AND !preg_match(_REGEXP_COND_VIDE_NONVIDE
, $code)
927 AND !preg_match(_REGEXP_COND_NONVIDE_VIDE
, $code)
928 AND !preg_match(_REGEXP_CONCAT_NON_VIDE
, $code))
929 $code = "strval($code)";
933 // Erreur de construction de l'arbre de syntaxe abstraite
936 $err_e_c = _T('zbug_erreur_compilation');
937 erreur_squelette($err_e_c, $p);
941 $code = compile_retour($code, $avant, $apres, $altern, $tab, $descr['niv']);
942 $codes[]= (($mode == 'validation') ?
943 "array($code, '$commentaire', " . $p->ligne
. ")"
944 : (($mode == 'code') ?
945 "\n// $commentaire\n$code" :
950 return $err_e_c ?
false : $codes;
953 // production d'une expression conditionnelle ((v=EXP) ? (p . v .s) : a)
954 // mais si EXP est de la forme (t ? 'C' : '') on produit (t ? (p . C . s) : a)
955 // de meme si EXP est de la forme (t ? '' : 'C')
957 // http://doc.spip.org/@compile_retour
958 function compile_retour($code, $avant, $apres, $altern, $tab, $n)
960 if ($avant == "''") $avant = '';
961 if ($apres == "''") $apres = '';
962 if (!$avant AND !$apres AND ($altern==="''")) return $code;
964 if (preg_match(_REGEXP_CONCAT_NON_VIDE
, $code)) {
967 } elseif (preg_match(_REGEXP_COND_VIDE_NONVIDE
,$code, $r)) {
970 } else if (preg_match(_REGEXP_COND_NONVIDE_VIDE
,$code, $r)) {
975 $cond = "($t = $code)!==''";
978 $res = (!$avant ?
"" : "$avant . ") .
980 (!$apres ?
"" : " . $apres");
982 if ($res !== $t) $res = "($res)";
983 return !$cond ?
$res : "($cond ?\n\t$tab$res :\n\t$tab$altern)";
987 function compile_inclure_doublons($lexemes)
989 foreach($lexemes as $v)
990 if($v->type
=== 'include' AND $v->param
)
991 foreach($v->param
as $r)
992 if (trim($r[0]) === 'doublons')
997 // Prend en argument le texte d'un squelette, le nom de son fichier d'origine,
998 // sa grammaire et un nom. Retourne False en cas d'erreur,
999 // sinon retourne un tableau de fonctions PHP compilees a evaluer,
1000 // notamment une fonction portant ce nom et calculant une page.
1001 // Pour appeler la fonction produite, lui fournir 2 tableaux de 1 e'le'ment:
1002 // - 1er: element 'cache' => nom (du fichier ou` mettre la page)
1003 // - 2e: element 0 contenant un environnement ('id_article => $id_article, etc)
1004 // Elle retournera alors un tableau de 5 e'le'ments:
1005 // - 'texte' => page HTML, application du squelette a` l'environnement;
1006 // - 'squelette' => le nom du squelette
1007 // - 'process_ins' => 'html' ou 'php' selon la pre'sence de PHP dynamique
1008 // - 'invalideurs' => de'pendances de cette page, pour invalider son cache.
1009 // - 'entetes' => tableau des entetes http
1010 // En cas d'erreur, elle retournera un tableau des 2 premiers elements seulement
1012 // http://doc.spip.org/@public_compiler_dist
1013 function public_compiler_dist($squelette, $nom, $gram, $sourcefile, $connect=''){
1014 // Pre-traitement : reperer le charset du squelette, et le convertir
1015 // Bonus : supprime le BOM
1016 include_spip('inc/charsets');
1017 $squelette = transcoder_page($squelette);
1019 // rendre inertes les echappements de #[](){}<>
1021 while(false !== strpos($squelette, $inerte = '-INERTE'.$i)) $i++
;
1022 $squelette = preg_replace_callback(',\\\\([#[()\]{}<>]),',
1023 create_function('$a', "return '$inerte-'.ord(\$a[1]).'-';"), $squelette, -1, $esc);
1025 $descr = array('nom' => $nom,
1027 'sourcefile' => $sourcefile,
1028 'squelette' => $squelette);
1030 // Phraser le squelette, selon sa grammaire
1033 $f = charger_fonction('phraser_' . $gram, 'public');
1035 $squelette = $f($squelette, '', $boucles, $descr);
1036 $boucles = compiler_squelette($squelette, $boucles, $nom, $descr, $sourcefile, $connect);
1038 // restituer les echappements
1040 foreach($boucles as $i=>$boucle) {
1041 $boucles[$i]->return = preg_replace_callback(",$inerte-(\d+)-,", create_function('$a', 'return chr($a[1]);'),
1043 $boucles[$i]->descr
['squelette'] = preg_replace_callback(",$inerte-(\d+)-,", create_function('$a', 'return "\\\\".chr($a[1]);'),
1044 $boucle->descr
['squelette']);
1047 $debug = ($boucles AND defined('_VAR_MODE') AND _VAR_MODE
== 'debug');
1049 include_spip('public/decompiler');
1050 foreach($boucles as $id => $boucle) {
1052 $decomp = "\n/* BOUCLE " .
1053 $boucle->type_requete
.
1055 str_replace('*/', '* /', public_decompiler($boucle, $gram, 0, 'criteres')) .
1057 else $decomp = ("\n/*\n" .
1058 str_replace('*/', '* /', public_decompiler($squelette, $gram))
1060 $boucles[$id]->return = $decomp .$boucle->return;
1061 $GLOBALS['debug_objets']['code'][$nom.$id] = $boucle->return;
1068 // Point d'entree pour arbre de syntaxe abstraite fourni en premier argument
1069 // Autres specifications comme ci-dessus
1071 function compiler_squelette($squelette, $boucles, $nom, $descr, $sourcefile, $connect=''){
1072 static $trouver_table;
1073 spip_timer('calcul_skel');
1075 if (defined('_VAR_MODE') AND _VAR_MODE
== 'debug') {
1076 $GLOBALS['debug_objets']['squelette'][$nom] = $descr['squelette'];
1077 $GLOBALS['debug_objets']['sourcefile'][$nom] = $sourcefile;
1079 if (!isset($GLOBALS['debug_objets']['principal']))
1080 $GLOBALS['debug_objets']['principal'] = $nom;
1082 foreach ($boucles as $id => $boucle) {
1083 $GLOBALS['debug_objets']['boucle'][$nom.$id] = $boucle;
1085 $descr['documents'] = compile_inclure_doublons($squelette);
1087 // Demander la description des tables une fois pour toutes
1088 // et reperer si les doublons sont demandes
1089 // pour un inclure ou une boucle document
1090 // c'est utile a la fonction champs_traitements
1091 if (!$trouver_table)
1092 $trouver_table = charger_fonction('trouver_table', 'base');
1094 foreach($boucles as $id => $boucle) {
1095 if (!($type = $boucle->type_requete
)) continue;
1096 if (!$descr['documents'] AND (
1097 (($type == 'documents') AND $boucle->doublons
) OR
1098 compile_inclure_doublons($boucle->avant
) OR
1099 compile_inclure_doublons($boucle->apres
) OR
1100 compile_inclure_doublons($boucle->milieu
) OR
1101 compile_inclure_doublons($boucle->altern
)))
1102 $descr['documents'] = true;
1103 if ($type != TYPE_RECURSIF
) {
1104 if (!$boucles[$id]->sql_serveur
AND $connect)
1105 $boucles[$id]->sql_serveur
= $connect;
1107 // chercher dans les iterateurs du repertoire iterateur/
1108 if ($g = charger_fonction(
1109 preg_replace('/\W/', '_', $boucle->type_requete
), 'iterateur', true)) {
1110 $boucles[$id] = $g($boucle);
1112 // sinon, en cas de requeteur d'un type predefini,
1113 // utiliser les informations donnees par le requeteur
1114 // cas "php:xx" et "data:xx".
1115 } else if ($boucle->sql_serveur
AND $requeteur = charger_fonction($boucle->sql_serveur
, 'requeteur', true)) {
1116 $requeteur($boucles, $boucle, $id);
1118 // utiliser la description des champs transmis
1120 $show = $trouver_table($type, $boucles[$id]->sql_serveur
);
1121 // si la table n'existe pas avec le connecteur par defaut,
1122 // c'est peut etre une table qui necessite son connecteur dedie fourni
1123 // permet une ecriture allegee (GEO) -> (geo:GEO)
1125 AND $show=$trouver_table($type, strtolower($type))) {
1126 $boucles[$id]->sql_serveur
= strtolower($type);
1129 $boucles[$id]->show
= $show;
1130 // recopie les infos les plus importantes
1131 $boucles[$id]->primary
= $show['key']["PRIMARY KEY"];
1132 $boucles[$id]->id_table
= $x = preg_replace(",^spip_,","",$show['id_table']);
1133 $boucles[$id]->from
[$x] = $nom_table = $show['table'];
1134 $boucles[$id]->iterateur
= 'SQL';
1136 $boucles[$id]->descr
= &$descr;
1137 if ((!$boucles[$id]->jointures
)
1138 AND is_array($show['tables_jointures'])
1139 AND count($x = $show['tables_jointures']))
1140 $boucles[$id]->jointures
= $x;
1141 if ($boucles[$id]->jointures_explicites
){
1142 $jointures = preg_split("/\s+/",$boucles[$id]->jointures_explicites
);
1143 while ($j=array_pop($jointures))
1144 array_unshift($boucles[$id]->jointures
,$j);
1147 // Pas une erreur si la table est optionnelle
1148 if ($boucles[$id]->table_optionnelle
)
1149 $boucles[$id]->type_requete
= '';
1151 $boucles[$id]->type_requete
= false;
1152 $boucle = $boucles[$id];
1153 $x = (!$boucle->sql_serveur ?
'' :
1154 ($boucle->sql_serveur
. ":")) .
1156 $msg = array('zbug_table_inconnue',
1157 array('table' => $x));
1158 erreur_squelette($msg, $boucle);
1165 // Commencer par reperer les boucles appelees explicitement
1166 // car elles indexent les arguments de maniere derogatoire
1167 foreach($boucles as $id => $boucle) {
1168 if ($boucle->type_requete
== TYPE_RECURSIF
AND $boucle->param
) {
1169 $boucles[$id]->descr
= &$descr;
1170 $rec = &$boucles[$boucle->param
[0]];
1172 $msg = array('zbug_boucle_recursive_undef',
1173 array('nom' => $boucle->param
[0]));
1174 erreur_squelette($msg, $boucle);
1175 $boucles[$id]->type_requete
= false;
1177 $rec->externe
= $id;
1178 $descr['id_mere'] = $id;
1179 $boucles[$id]->return =
1180 calculer_liste(array($rec),
1187 foreach($boucles as $id => $boucle) {
1188 $id = strval($id); // attention au type dans index_pile
1189 $type = $boucle->type_requete
;
1190 if ($type AND $type != TYPE_RECURSIF
) {
1192 if ($boucle->param
) {
1193 // retourne un tableau en cas d'erreur
1194 $res = calculer_criteres($id, $boucles);
1196 $descr['id_mere'] = $id;
1197 $boucles[$id]->return =
1198 calculer_liste($boucle->milieu
,
1202 // Si les criteres se sont mal compiles
1203 // ne pas tenter d'assembler le code final
1204 // (mais compiler le corps pour detection d'erreurs)
1205 if (is_array($res)) {
1206 $boucles[$id]->type_requete
= false;
1211 // idem pour la racine
1212 $descr['id_mere'] = '';
1213 $corps = calculer_liste($squelette, $descr, $boucles);
1217 // Calcul du corps de toutes les fonctions PHP,
1218 // en particulier les requetes SQL et TOTAL_BOUCLE
1219 // de'terminables seulement maintenant
1221 foreach($boucles as $id => $boucle) {
1222 $boucle = $boucles[$id] = pipeline('pre_boucle', $boucle);
1223 if ($boucle->return === false) {$corps = false; continue;}
1224 // appeler la fonction de definition de la boucle
1226 if ($req = $boucle->type_requete
) {
1227 // boucle personnalisée ?
1228 $table = strtoupper($boucle->type_requete
);
1229 $serveur = strtolower($boucle->sql_serveur
);
1231 // fonction de boucle avec serveur & table
1233 ((!function_exists($f = "boucle_".$serveur."_".$table))
1234 AND (!function_exists($f = $f."_dist"))
1237 // fonction de boucle avec table
1238 AND (!function_exists($f = "boucle_".$table))
1239 AND (!function_exists($f = $f."_dist"))
1241 // fonction de boucle standard
1242 if (!function_exists($f = 'boucle_DEFAUT')) {
1243 $f = 'boucle_DEFAUT_dist';
1247 $req = "\n\n\tstatic \$command = array();\n\t" .
1248 "static \$connect;\n\t" .
1249 "\$command['connect'] = \$connect = " .
1250 _q($boucle->sql_serveur
) .
1253 } else $req = ("\n\treturn '';");
1255 $boucles[$id]->return =
1256 "\n\nfunction BOUCLE" . strtr($id,"-","_") . $nom .
1257 '(&$Cache, &$Pile, &$doublons, &$Numrows, $SP) {' .
1262 // Au final, si le corps ou un critere au moins s'est mal compile
1263 // retourner False, sinon inserer leur decompilation
1264 if (is_bool($corps)) return false;
1266 $principal = "\nfunction " . $nom . '($Cache, $Pile, $doublons=array(), $Numrows=array(), $SP=0) {
1268 // reporter de maniere securisee les doublons inclus
1270 if (isset($Pile[0]["doublons"]) AND is_array($Pile[0]["doublons"]))
1271 $doublons = nettoyer_env_doublons($Pile[0]["doublons"]);
1276 // ATTENTION, le calcul de l'expression $corps affectera $Cache
1277 // c'est pourquoi on l'affecte a la variable auxiliaire $page.
1278 // avant de referencer $Cache
1281 return analyse_resultat_skel(".var_export($nom,true)
1282 .", \$Cache, \$page, ".var_export($sourcefile,true).");
1285 $secondes = spip_timer('calcul_skel');
1286 spip_log("COMPIL ($secondes) [$sourcefile] $nom.php");
1287 // $connect n'est pas sûr : on nettoie
1288 $connect = preg_replace(',[^\w],', '', $connect);
1290 // Assimiler la fct principale a une boucle anonyme, pour retourner un resultat simple
1292 $code->descr
= $descr;
1295 // Fonction principale du squelette ' .
1297 ($connect ?
" pour $connect" : '') .
1298 (!CODE_COMMENTE ?
'' : "\n// Temps de compilation total: $secondes") .
1302 $boucles[''] = $code;
1308 * Requeteur pour les boucles (php:nom_iterateur)
1310 * Analyse si le nom d'iterateur correspond bien a une classe PHP existante
1311 * et dans ce cas charge la boucle avec cet iterateur.
1312 * Affichera une erreur dans le cas contraire.
1314 * @param $boucles Liste des boucles
1315 * @param $boucle La boucle parcourue
1316 * @param $id L'identifiant de la boucle parcourue
1319 function requeteur_php_dist(&$boucles, &$boucle, &$id) {
1320 if (class_exists($boucle->type_requete
)) {
1321 $g = charger_fonction('php', 'iterateur');
1322 $boucles[$id] = $g($boucle, $boucle->type_requete
);
1324 $x = $boucle->type_requete
;
1325 $boucle->type_requete
= false;
1326 $msg = array('zbug_iterateur_inconnu',
1327 array('iterateur' => $x));
1328 erreur_squelette($msg, $boucle);
1334 * Requeteur pour les boucles (data:type de donnee)
1335 * note: (DATA) tout court ne passe pas par ici.
1337 * Analyse si le type de donnee peut etre traite
1338 * et dans ce cas charge la boucle avec cet iterateur.
1339 * Affichera une erreur dans le cas contraire.
1341 * @param $boucles Liste des boucles
1342 * @param $boucle La boucle parcourue
1343 * @param $id L'identifiant de la boucle parcourue
1346 function requeteur_data_dist(&$boucles, &$boucle, &$id) {
1347 include_spip('iterateur/data');
1348 if ($h = charger_fonction($boucle->type_requete
. '_to_array' , 'inc', true)) {
1349 $g = charger_fonction('data', 'iterateur');
1350 $boucles[$id] = $g($boucle);
1351 // from[0] stocke le type de data (rss, yql, ...)
1352 $boucles[$id]->from
[] = $boucle->type_requete
;
1355 $x = $boucle->type_requete
;
1356 $boucle->type_requete
= false;
1357 $msg = array('zbug_requeteur_inconnu',
1359 'requeteur' => 'data',
1362 erreur_squelette($msg, $boucle);