New file |
0,0 → 1,692 |
<?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; |
|
} |
|
?> |