Blame | Last modification | View Log | RSS feed
<?php
/***************************************************************************\
* SPIP, Systeme de publication pour l'internet *
* *
* Copyright (c) 2001-2005 *
* Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James *
* *
* Ce programme est un logiciel libre distribue sous licence GNU/GPL. *
* Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne. *
\***************************************************************************/
//
// Fichier principal du compilateur de squelettes
//
// Ce fichier ne sera execute qu'une fois
if (defined("_INC_COMPILO")) return;
define("_INC_COMPILO", "1");
// reperer un code ne calculant rien, meme avec commentaire
define('CODE_MONOTONE', "^(\n//[^\n]*\n)?\(?'([^'])*'\)?$");
// Definition de la structure $p, et fonctions de recherche et de reservation
// dans l'arborescence des boucles
include_local("inc-compilo-index.php3"); # index ? structure ? pile ?
// definition des boucles
include_local("inc-boucles.php3");
// definition des criteres
include_local("inc-criteres.php3");
// definition des balises
include_local("inc-balises.php3");
// definition de l'API
include_local("inc-compilo-api.php3");
# definition des tables
include_ecrire('inc_serialbase.php3');
// outils pour debugguer le compilateur
#include_local("inc-compilo-debug.php3"); # desactive
//
// Calculer un <INCLURE()>
//
function calculer_inclure($struct, $descr, &$boucles, $id_boucle) {
$fichier = $struct->texte;
if (!($path = find_in_path($fichier)))
{
spip_log("ERREUR: <INCLURE($fichier)> impossible");
erreur_squelette(_T('zbug_info_erreur_squelette'),
"<INCLURE($fichier)> - "
._T('fichier_introuvable', array('fichier' => $fichier)));
return "'<!-- Erreur INCLURE(".texte_script($fichier).") -->'";
}
$l = array();
foreach($struct->param as $val) {
$var = array_shift($val);
$l[] = "\'$var\' => \'' . addslashes(" .
($val ? calculer_liste($val[0], $descr, $boucles, $id_boucle) :
(($var =='lang') ?
'$GLOBALS["spip_lang"]' :
index_pile($id_boucle, $var, $boucles)))
. ") . '\\'";
}
return "\n'<".
"?php\n\t\$contexte_inclus = array(" .
join(",\n\t",$l) .
");" .
"\n\tinclude(\\'$path\\');" .
"\n?'." . "'>'";
}
//
// calculer_boucle() produit le corps PHP d'une boucle Spip
// Sauf pour les recursives, ce corps est un Select SQL + While PHP
// remplissant une variable $t0 retournee en valeur
//
function calculer_boucle($id_boucle, &$boucles) {
$boucle = &$boucles[$id_boucle];
$return = $boucle->return;
$type_boucle = $boucle->type_requete;
if ($type_boucle == 'boucle') {
$corps = "\n \$t0 = " . $return . ";";
$init = "";
} else {
$primary = $boucle->primary;
$constant = ereg(CODE_MONOTONE,$return);
// Cas {1/3} {1,4} {n-2,1}...
$flag_cpt = $boucle->mode_partie || // pas '$compteur' a cause du cas 0
strpos($return,'compteur_boucle');
//
// Creer le debut du corps de la boucle :
//
$corps = '';
if ($flag_cpt)
$corps = "\n \$Numrows['$id_boucle']['compteur_boucle']++;";
if ($boucle->mode_partie)
$corps .= "
if (\$Numrows['$id_boucle']['compteur_boucle']-1 >= \$debut_boucle
AND \$Numrows['$id_boucle']['compteur_boucle']-1 <= \$fin_boucle) {";
// Calculer les invalideurs si c'est une boucle non constante
if ($primary && !$constant)
$corps .= "\n\t\t\$Cache['$primary'][" .
(($primary != 'id_forum') ?
index_pile($id_boucle, $primary, $boucles) :
("calcul_index_forum(" .
// Retournera 4 [$SP] mais force la demande du champ a MySQL
index_pile($id_boucle, 'id_article', $boucles) . ',' .
index_pile($id_boucle, 'id_breve', $boucles) . ',' .
index_pile($id_boucle, 'id_rubrique', $boucles) .',' .
index_pile($id_boucle, 'id_syndic', $boucles) .
")")) .
"] = 1; // invalideurs\n";
if ($boucle->doublons)
$corps .= " \$doublons[".$boucle->doublons."] .= ','. " .
index_pile($id_boucle, $primary, $boucles)
. "; // doublons\n";
if (count($boucle->separateur))
$code_sep = ("'" . ereg_replace("'","\'",join('',$boucle->separateur)) . "'");
$init = '';
$fin = '';
// La boucle doit-elle selectionner la langue ?
// -. par defaut, les boucles suivantes le font
// "peut-etre", c'est-a-dire si forcer_lang == false.
// - . a moins d'une demande explicite
if (!$constant && $boucle->lang_select != 'non' &&
(($boucle->lang_select == 'oui') ||
(
$type_boucle == 'articles'
OR $type_boucle == 'rubriques'
OR $type_boucle == 'hierarchie'
OR $type_boucle == 'breves'
)))
{
$corps .=
(($boucle->lang_select != 'oui') ?
"\t\tif (!\$GLOBALS['forcer_lang'])\n\t " : '')
. "\t\t\$GLOBALS['spip_lang'] = (\$x = "
. index_pile($id_boucle, 'lang', $boucles)
. ') ? $x : $old_lang;';
// Memoriser la langue avant la boucle pour la restituer apres
$init .= "\n \$old_lang = \$GLOBALS['spip_lang'];";
$fin .= "\n \$GLOBALS['spip_lang'] = \$old_lang;";
}
// gestion optimale des separateurs et des boucles constantes
$corps .=
((!$boucle->separateur) ?
(($constant && !$corps) ? $return :
("\n\t\t" . '$t0 .= ' . $return . ";")) :
("\n\t\t\$t1 " .
((strpos($return, '$t1.') === 0) ?
(".=" . substr($return,4)) :
('= ' . $return)) .
";\n\t\t" .
'$t0 .= (($t1 && $t0) ? ' . $code_sep . " : '') . \$t1;"));
// Fin de parties
if ($boucle->mode_partie)
$corps .= "\n }\n";
// Gestion de la hierarchie (voir inc-boucles.php3)
if ($boucle->hierarchie)
$init .= "\n ".$boucle->hierarchie;
// si le corps est une constante, ne pas appeler le serveur N fois!
if (ereg(CODE_MONOTONE,$corps, $r)) {
if (!$r[2]) {
if (!$boucle->numrows)
return 'return "";';
else
$corps = "";
} else {
$boucle->numrows = true;
$corps = "\n ".'for($x=$Numrows["'.$id_boucle.'"]["total"];$x>0;$x--)
$t0 .= ' . $corps .';';
}
} else {
$corps = '
// RESULTATS
while ($Pile[$SP] = @spip_abstract_fetch($result,"' .
$boucle->sql_serveur .
'")) {' .
"\n$corps\n }\n" .
$fin ;
}
//
// Requete
//
if (!$order = $boucle->order
AND !$order = $boucle->default_order)
$order = array();
$init .= $boucle->hash .
"\n\n // REQUETE
\$result = spip_abstract_select(\n\t\tarray(\"".
# En absence de champ c'est un decompte :
# prendre la primary pour avoir qqch
# (COUNT incompatible avec le cas general
($boucle->select ?
join("\",\n\t\t\"", $boucle->select) :
($boucle->id_table . "." .
(($p = strpos($primary, ',')) ?
substr($primary, 0, $p) : $primary))) .
'"), # SELECT
array("' .
join('","', array_unique($boucle->from)) .
'"), # FROM
array(' .
(!$boucle->where ? '' : ( '"' . join('",
"', $boucle->where) . '"')) .
"), # WHERE
'".addslashes($boucle->group)."', # GROUP
array(" .
join(', ', $order) .
"), # ORDER
" . (strpos($boucle->limit, 'intval') === false ?
"'".$boucle->limit."'" :
$boucle->limit). ", # LIMIT
'".$boucle->sous_requete."', # sous
'" . (!$boucle->having ? "" : "(COUNT(*)> $boucle->having)")."', # HAVING
'".$boucle->id_table."', # table
'".$boucle->id_boucle."', # boucle
'".$boucle->sql_serveur."'); # serveur";
$init .= "\n ".'$t0 = "";
$SP++;';
if ($flag_cpt)
$init .= "\n \$Numrows['$id_boucle']['compteur_boucle'] = 0;";
if ($boucle->mode_partie)
$init .= calculer_parties($boucles, $id_boucle);
else if ($boucle->numrows)
$init .= "\n \$Numrows['" .
$id_boucle .
"']['total'] = @spip_abstract_count(\$result,'" .
$boucle->sql_serveur .
"');";
//
// Conclusion et retour
//
$corps .= "\n @spip_abstract_free(\$result,'" .
$boucle->sql_serveur . "');";
}
return $init . $corps .
## inserer le code d'envoi au debusqueur du resultat de la fonction
(($GLOBALS['var_mode_affiche'] != 'resultat') ? "" : "
boucle_debug_resultat('$id_boucle', 'resultat', \$t0);") .
"\n return \$t0;";
}
//
// fonction traitant les criteres {1,n} (analyses dans inc-criteres)
//
## a deplacer dans inc-criteres ??
function calculer_parties($boucles, $id_boucle) {
$boucle = &$boucles[$id_boucle];
$partie = $boucle->partie;
$mode_partie = $boucle->mode_partie;
$total_parties = $boucle->total_parties;
// Notes :
// $debut_boucle et $fin_boucle sont les indices SQL du premier
// et du dernier demandes dans la boucle : 0 pour le premier,
// n-1 pour le dernier ; donc total_boucle = 1 + debut - fin
// nombre total avant partition
$retour = "\n\n // Partition\n " .
'$nombre_boucle = @spip_abstract_count($result,"' .
$boucle->sql_serveur .
'");';
ereg("([+-/])([+-/])?", $mode_partie, $regs);
list(,$op1,$op2) = $regs;
// {1/3}
if ($op1 == '/') {
$pmoins1 = is_numeric($partie) ? ($partie-1) : "($partie-1)";
$totpos = is_numeric($total_parties) ? ($total_parties) :
"($total_parties ? $total_parties : 1)";
$retour .= "\n "
.'$debut_boucle = ceil(($nombre_boucle * '
. $pmoins1 . ')/' . $totpos . ");";
$fin = 'ceil (($nombre_boucle * '
. $partie . ')/' . $totpos . ") - 1";
}
// {1,x}
elseif ($op1 == '+') {
$retour .= "\n "
. '$debut_boucle = ' . $partie . ';';
}
// {n-1,x}
elseif ($op1 == '-') {
$retour .= "\n "
. '$debut_boucle = $nombre_boucle - ' . $partie . ';';
}
// {x,1}
if ($op2 == '+') {
$fin = '$debut_boucle'
. (is_numeric($total_parties) ?
(($total_parties==1) ? "" :(' + ' . ($total_parties-1))):
('+' . $total_parties . ' - 1'));
}
// {x,n-1}
elseif ($op2 == '-') {
$fin = '$debut_boucle + $nombre_boucle - '
. (is_numeric($total_parties) ? ($total_parties+1) :
($total_parties . ' - 1'));
}
// Rabattre $fin_boucle sur le maximum
$retour .= "\n "
.'$fin_boucle = min(' . $fin . ', $nombre_boucle - 1);';
// calcul du total boucle final
$retour .= "\n "
.'$Numrows[\''.$id_boucle.'\']["total"] = max(0,$fin_boucle - $debut_boucle + 1);';
return $retour;
}
// Production du code PHP a partir de la sequence livree par le phraseur
// $boucles est passe par reference pour affectation par index_pile.
// Retourne une expression PHP,
// (qui sera argument d'un Return ou la partie droite d'une affectation).
function calculer_liste($tableau, $descr, &$boucles, $id_boucle='') {
if (!$tableau) return "''";
$codes = compile_cas($tableau, $descr, $boucles, $id_boucle);
$n = count($codes);
if (!$n) return "''";
if ($GLOBALS['var_mode_affiche'] != 'validation')
return
(($n==1) ? $codes[0] :
"(" . join (" .\n$tab", $codes) . ")");
else return "debug_sequence('$id_boucle', '" .
($descr['nom']) .
"', " .
intval($descr['niv']) .
", array(" .
join(" ,\n$tab", $codes) . "))";
}
function compile_cas($tableau, $descr, &$boucles, $id_boucle='') {
$codes = array();
// cas de la boucle recursive
if (is_array($id_boucle))
$id_boucle = $id_boucle[0];
$type = $boucles[$id_boucle]->type_requete;
$descr['niv']++;
for ($i=0; $i<=$descr['niv']; $i++) $tab .= "\t";
// chaque commentaire introduit dans le code doit commencer
// par un caractere distinguant le cas, pour exploitation par debug.
foreach ($tableau as $p) {
switch($p->type) {
// texte seul
case 'texte':
$code = "'".ereg_replace("([\\\\'])", "\\\\1", $p->texte)."'";
$commentaire= strlen($p->texte) . " signes";
$avant='';
$apres='';
$altern = "''";
break;
case 'polyglotte':
$code = "";
foreach($p->traductions as $k => $v) {
$code .= ",'" .
ereg_replace("([\\\\'])", "\\\\1", $k) .
"' => '" .
ereg_replace("([\\\\'])", "\\\\1", $v) .
"'";
}
$code = "multi_trad(array(" .
substr($code,1) .
"))";
$commentaire= '&';
$avant='';
$apres='';
$altern = "''";
break;
// inclure
case 'include':
$code = calculer_inclure($p, $descr, $boucles, $id_boucle);
$commentaire = '!' . $p->texte;
$avant='';
$apres='';
$altern = "''";
break;
// boucle
case 'boucle':
$nom = $p->id_boucle;
$newdescr = $descr;
$newdescr['id_mere'] = $nom;
$newdescr['niv']++;
$code = 'BOUCLE' .
ereg_replace("-","_", $nom) . $descr['nom'] .
'($Cache, $Pile, $doublons, $Numrows, $SP)';
$commentaire= "?$nom";
$avant = calculer_liste($p->avant,
$newdescr, $boucles, $id_boucle);
$apres = calculer_liste($p->apres,
$newdescr, $boucles, $id_boucle);
$newdescr['niv']--;
$altern = calculer_liste($p->altern,
$newdescr, $boucles, $id_boucle);
break;
case 'idiome':
$p->code = "_T('" . $p->module . ":" .$p->nom_champ . "')";
$p->id_boucle = $id_boucle;
$p->boucles = &$boucles;
$p->statut = 'php'; // ne pas manger les espaces avec trim()
$commentaire = ":";
$code = applique_filtres($p);
$avant='';
$apres='';
$altern = "''";
break;
case 'champ';
// cette structure pourrait etre completee des le phrase' (a faire)
$p->id_boucle = $id_boucle;
$p->boucles = &$boucles;
$p->descr = $descr;
$p->statut = 'html';
$p->type_requete = $type;
$code = calculer_champ($p);
$commentaire = '#' . $p->nom_champ . $p->etoile;
$avant = calculer_liste($p->avant,
$descr, $boucles, $id_boucle);
$apres = calculer_liste($p->apres,
$descr, $boucles, $id_boucle);
$altern = "''";
break;
default:
erreur_squelette(_T('zbug_info_erreur_squelette'));
} // switch
if ($avant == "''") $avant = '';
if ($apres == "''") $apres = '';
if ($avant||$apres||($altern!="''"))
{
$t = '$t' . $descr['niv'];
$res = (!$avant ? "" : "$avant . ") .
$t .
(!$apres ? "" : " . $apres");
$code = "(($t = $code) ?\n\t$tab($res) :\n\t$tab($altern))";
}
if ($code != "''")
$codes[]= (($GLOBALS['var_mode_affiche'] == 'validation') ?
"array(" . $p->ligne . ", '$commentaire', $code)"
: (($GLOBALS['var_mode_affiche'] == 'code') ?
"\n// $commentaire\n$code" :
$code));
} // foreach
return $codes;
}
// affichage du code produit
function code_boucle(&$boucles, $id, $nom)
{
$boucle = &$boucles[$id];
// Indiquer la boucle en commentaire
$pretty = '';
if ($boucle->type_requete != 'boucle')
{
// Resynthetiser les criteres
foreach ($boucle->param as $param) {
$s = "";
$sep = "";
foreach ($param as $t) {
if (is_array($t)) { // toujours vrai normalement
$s .= $sep;
$c = $t[0];
if ($c->apres)
$s .= ($c->apres . $c->texte . $c->apres);
else {
// faudrait decompiler aussi les balises...
foreach ($t as $c)
$s .= ($c->type == 'texte') ? $c->texte : '#...';
}
$sep = ", ";
}
}
$pretty .= ' {' . $s . '}';
}
}
$pretty = "BOUCLE$id(".strtoupper($boucle->type_requete) . ")" .
ereg_replace("[\r\n]", " ", $pretty);
return $pretty;
}
// Prend en argument le source d'un squelette, sa grammaire et un nom.
// Retourne une fonction PHP/SQL portant ce nom et calculant une page HTML.
// Pour appeler la fonction produite, lui fournir 2 tableaux de 1 e'le'ment:
// - 1er: element 'cache' => nom (du fichier ou` mettre la page)
// - 2e: element 0 contenant un environnement ('id_article => $id_article, etc)
// Elle retourne alors un tableau de 4 e'le'ments:
// - 'texte' => page HTML, application du squelette a` l'environnement;
// - 'squelette' => le nom du squelette
// - 'process_ins' => 'html' ou 'php' selon la pre'sence de PHP dynamique
// - 'invalideurs' => de'pendances de cette page, pour invalider son cache.
// (voir son utilisation, optionnelle, dans invalideur.php)
// En cas d'erreur, elle retourne un tableau des 2 premiers elements seulement
function calculer_squelette($squelette, $nom, $gram, $sourcefile) {
# 3 variables qui sont en fait des constantes après chargement
global $table_primary, $table_des_tables, $tables_des_serveurs_sql;
// Phraser le squelette, selon sa grammaire
// pour le moment: "html" seul connu (HTML+balises BOUCLE)
$boucles = array();
spip_timer('calcul_skel');
include_local("inc-$gram-squel.php3");
$racine = phraser($squelette, '',$boucles, $nom);
// tableau des informations sur le squelette
$descr = array('nom' => $nom, 'documents' => false, 'sourcefile' => $sourcefile);
// une boucle documents est conditionnee par tout le reste!
foreach($boucles as $idb => $boucle) {
if (($boucle->type_requete == 'documents') && $boucle->doublons)
{ $descr['documents'] = true; break; }
}
// Commencer par reperer les boucles appelees explicitement
// car elles indexent les arguments de maniere derogatoire
foreach($boucles as $id => $boucle) {
if ($boucle->type_requete == 'boucle') {
$rec = &$boucles[$boucle->param[0]];
if (!$rec) {
return array(_T('zbug_info_erreur_squelette'),
($boucle->param[0]
. ' '. _T('zbug_boucle_recursive_undef')));
} else {
$rec->externe = $id;
$descr['id_mere'] = $id;
$boucles[$id]->return =
calculer_liste(array($rec),
$descr,
$boucles,
$boucle->param);
}
}
}
foreach($boucles as $id => $boucle) {
$type = $boucle->type_requete;
if ($type != 'boucle') {
$boucles[$id]->id_table = $table_des_tables[$type];
if ($boucles[$id]->id_table) {
$boucles[$id]->primary = $table_primary[$type];
} else {
// table non Spip.
$boucles[$id]->id_table = $type;
$serveur = $boucle->sql_serveur;
$x = &$tables_des_serveurs_sql[$serveur ? $serveur : 'localhost'][$type]['key'];
$boucles[$id]->primary = ($x["PRIMARY KEY"] ? $x["PRIMARY KEY"] : $x["KEY"]);
}
if ($boucle->param) {
$res = calculer_criteres($id, $boucles);
if (is_array($res)) return $res; # erreur
}
$descr['id_mere'] = $id;
$boucles[$id]->return =
calculer_liste($boucle->milieu,
$descr,
$boucles,
$id);
}
}
// idem pour la racine
$descr['id_mere'] = '';
$corps = calculer_liste($racine, $descr, $boucles);
// Calcul du corps de toutes les fonctions PHP,
// en particulier les requetes SQL et TOTAL_BOUCLE
// de'terminables seulement maintenant
// Les 4 premiers parame`tres sont passe's par re'fe'rence
// (les 1er et 3e pour modif, les 2 et 4 pour gain de place)
foreach($boucles as $id => $boucle) {
// appeler la fonction de definition de la boucle
$f = 'boucle_'.strtoupper($boucle->type_requete);
// si pas de definition perso, definition spip
if (!function_exists($f)) $f = $f.'_dist';
// laquelle a une definition par defaut
if (!function_exists($f)) $f = 'boucle_DEFAUT';
$boucles[$id]->return =
"function BOUCLE" . ereg_replace("-","_",$id) . $nom .
'(&$Cache, &$Pile, &$doublons, &$Numrows, $SP) {' .
$f($id, $boucles) .
"\n}\n\n";
if ($GLOBALS['var_mode'] == 'debug')
boucle_debug_compile ($id, $nom, $boucles[$id]->return);
}
$code = "";
foreach($boucles as $id => $boucle) {
$code .= "\n//\n// <BOUCLE " .
# code_boucle($boucles, $id, $nom). # pas au point
$boucle->type_requete .
">\n//\n" .
$boucle->return;
}
$secondes = spip_timer('calcul_skel');
spip_log("calcul skel $sourcefile ($secondes)");
$squelette_compile = "<"."?php
/*
* Squelette : $sourcefile
* Date : ".http_gmoddate(@filemtime($sourcefile))." GMT
* Compile : ".http_gmoddate(time())." GMT ($secondes)
* " . (!$boucles ? "Pas de boucle" :
("Boucles : " . join (', ', array_keys($boucles)))) ."
*/ " .
$code . "
//
// Fonction principale du squelette $sourcefile
//
function $nom (\$Cache, \$Pile, \$doublons=array(), \$Numrows='', \$SP=0) {
\$t0 = $corps;
return array(
'texte' => \$t0,
'squelette' => '$nom',
'process_ins' => ((strpos(\$t0,'<'.'?')=== false) ? 'html' : 'php'),
'invalideurs' => \$Cache
);
}
?".">";
if ($GLOBALS['var_mode'] == 'debug')
squelette_debug_compile($nom, $sourcefile, $squelette_compile, $squelette);
return $squelette_compile;
}
?>