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 foisif (defined("_INC_COMPILO")) return;define("_INC_COMPILO", "1");// reperer un code ne calculant rien, meme avec commentairedefine('CODE_MONOTONE', "^(\n//[^\n]*\n)?\(?'([^'])*'\)?$");// Definition de la structure $p, et fonctions de recherche et de reservation// dans l'arborescence des bouclesinclude_local("inc-compilo-index.php3"); # index ? structure ? pile ?// definition des bouclesinclude_local("inc-boucles.php3");// definition des criteresinclude_local("inc-criteres.php3");// definition des balisesinclude_local("inc-balises.php3");// definition de l'APIinclude_local("inc-compilo-api.php3");# definition des tablesinclude_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 0strpos($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_boucleAND \$Numrows['$id_boucle']['compteur_boucle']-1 <= \$fin_boucle) {";// Calculer les invalideurs si c'est une boucle non constanteif ($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 MySQLindex_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 expliciteif (!$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 partiesif ($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 = '// RESULTATSwhile ($Pile[$SP] = @spip_abstract_fetch($result,"' .$boucle->sql_serveur .'")) {' ."\n$corps\n }\n" .$fin ;}//// Requete//if (!$order = $boucle->orderAND !$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))) .'"), # SELECTarray("' .join('","', array_unique($boucle->from)) .'"), # FROMarray(' .(!$boucle->where ? '' : ( '"' . join('","', $boucle->where) . '"')) ."), # WHERE'".addslashes($boucle->group)."', # GROUParray(" .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 recursiveif (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 seulcase '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;// inclurecase 'include':$code = calculer_inclure($p, $descr, $boucles, $id_boucle);$commentaire = '!' . $p->texte;$avant='';$apres='';$altern = "''";break;// bouclecase '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'));} // switchif ($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));} // foreachreturn $codes;}// affichage du code produitfunction code_boucle(&$boucles, $id, $nom){$boucle = &$boucles[$id];// Indiquer la boucle en commentaire$pretty = '';if ($boucle->type_requete != 'boucle'){// Resynthetiser les criteresforeach ($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 seulementfunction calculer_squelette($squelette, $nom, $gram, $sourcefile) {# 3 variables qui sont en fait des constantes après chargementglobal $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 derogatoireforeach($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 spipif (!function_exists($f)) $f = $f.'_dist';// laquelle a une definition par defautif (!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;}?>