spip_nursit/ecrire/public/criteres.php

2807 lines
95 KiB
PHP
Raw Permalink Normal View History

2023-06-01 15:30:12 +00:00
<?php
/***************************************************************************\
* SPIP, Systeme de publication pour l'internet *
* *
* Copyright (c) 2001-2019 *
* 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. *
\***************************************************************************/
/**
* Définition des {criteres} d'une boucle
*
* @package SPIP\Core\Compilateur\Criteres
**/
if (!defined('_ECRIRE_INC_VERSION')) {
return;
}
/**
* Une Regexp repérant une chaine produite par le compilateur,
* souvent utilisée pour faire de la concaténation lors de la compilation
* plutôt qu'à l'exécution, i.e. pour remplacer 'x'.'y' par 'xy'
**/
define('_CODE_QUOTE', ",^(\n//[^\n]*\n)? *'(.*)' *$,");
/**
* Compile le critère {racine}
*
* Ce critère sélectionne les éléments à la racine d'une hiérarchie,
* c'est à dire ayant id_parent=0
*
* @link http://www.spip.net/@racine
*
* @param string $idb Identifiant de la boucle
* @param array $boucles AST du squelette
* @param Critere $crit Paramètres du critère dans cette boucle
* @return void
**/
function critere_racine_dist($idb, &$boucles, $crit) {
$not = $crit->not;
$boucle = &$boucles[$idb];
$id_parent = isset($GLOBALS['exceptions_des_tables'][$boucle->id_table]['id_parent']) ?
$GLOBALS['exceptions_des_tables'][$boucle->id_table]['id_parent'] :
'id_parent';
$c = array("'='", "'$boucle->id_table." . "$id_parent'", 0);
$boucle->where[] = ($crit->not ? array("'NOT'", $c) : $c);
}
/**
* Compile le critère {exclus}
*
* Exclut du résultat lélément dans lequel on se trouve déjà
*
* @link http://www.spip.net/@exclus
*
* @param string $idb Identifiant de la boucle
* @param array $boucles AST du squelette
* @param Critere $crit Paramètres du critère dans cette boucle
* @return void
**/
function critere_exclus_dist($idb, &$boucles, $crit) {
$not = $crit->not;
$boucle = &$boucles[$idb];
$id = $boucle->primary;
if ($not or !$id) {
return (array('zbug_critere_inconnu', array('critere' => $not . $crit->op)));
}
$arg = kwote(calculer_argument_precedent($idb, $id, $boucles));
$boucle->where[] = array("'!='", "'$boucle->id_table." . "$id'", $arg);
}
/**
* Compile le critère {doublons} ou {unique}
*
* Ce critères enlève de la boucle les éléments déjà sauvegardés
* dans un précédent critère {doublon} sur une boucle de même table.
*
* Il est possible de spécifier un nom au doublon tel que {doublons sommaire}
*
* @link http://www.spip.net/@doublons
*
* @param string $idb Identifiant de la boucle
* @param array $boucles AST du squelette
* @param Critere $crit Paramètres du critère dans cette boucle
* @return void
**/
function critere_doublons_dist($idb, &$boucles, $crit) {
$boucle = &$boucles[$idb];
$primary = $boucle->primary;
// la table nécessite une clé primaire, non composée
if (!$primary or strpos($primary, ',')) {
return (array('zbug_doublon_sur_table_sans_cle_primaire'));
}
$not = ($crit->not ? '' : 'NOT');
// le doublon s'applique sur un type de boucle (article)
$nom = "'" . $boucle->type_requete . "'";
// compléter le nom avec un nom précisé {doublons nom}
// on obtient $nom = "'article' . 'nom'"
if (isset($crit->param[0])) {
$nom .= "." . calculer_liste($crit->param[0], array(), $boucles, $boucles[$idb]->id_parent);
}
// code qui déclarera l'index du stockage de nos doublons (pour éviter une notice PHP)
$init_comment = "\n\n\t// Initialise le(s) critère(s) doublons\n";
$init_code = "\tif (!isset(\$doublons[\$d = $nom])) { \$doublons[\$d] = ''; }\n";
// on crée un sql_in avec la clé primaire de la table
// et la collection des doublons déjà emmagasinés dans le tableau
// $doublons et son index, ici $nom
// debut du code "sql_in('articles.id_article', "
$debut_in = "sql_in('" . $boucle->id_table . '.' . $primary . "', ";
// lecture des données du doublon "$doublons[$doublon_index[] = "
// Attention : boucle->doublons désigne une variable qu'on affecte
$debut_doub = '$doublons[' . (!$not ? '' : ($boucle->doublons . "[]= "));
// le debut complet du code des doublons
$debut_doub = $debut_in . $debut_doub;
// nom du doublon "('article' . 'nom')]"
$fin_doub = "($nom)]";
// si on trouve un autre critère doublon,
// on fusionne pour avoir un seul IN, et on s'en va !
foreach ($boucle->where as $k => $w) {
if (strpos($w[0], $debut_doub) === 0) {
// fusionner le sql_in (du where)
$boucle->where[$k][0] = $debut_doub . $fin_doub . ' . ' . substr($w[0], strlen($debut_in));
// fusionner l'initialisation (du hash) pour faire plus joli
$x = strpos($boucle->hash, $init_comment);
$len = strlen($init_comment);
$boucle->hash =
substr($boucle->hash, 0, $x + $len) . $init_code . substr($boucle->hash, $x + $len);
return;
}
}
// mettre l'ensemble dans un tableau pour que ce ne soit pas vu comme une constante
$boucle->where[] = array($debut_doub . $fin_doub . ", '" . $not . "')");
// déclarer le doublon s'il n'existe pas encore
$boucle->hash .= $init_comment . $init_code;
# la ligne suivante avait l'intention d'eviter une collecte deja faite
# mais elle fait planter une boucle a 2 critere doublons:
# {!doublons A}{doublons B}
# (de http://article.gmane.org/gmane.comp.web.spip.devel/31034)
# if ($crit->not) $boucle->doublons = "";
}
/**
* Compile le critère {lang_select}
*
* Permet de restreindre ou non une boucle en affichant uniquement
* les éléments dans la langue en cours. Certaines boucles
* tel que articles et rubriques restreignent par défaut sur la langue
* en cours.
*
* Sans définir de valeur au critère, celui-ci utilise 'oui' comme
* valeur par défaut.
*
* @param string $idb Identifiant de la boucle
* @param array $boucles AST du squelette
* @param Critere $crit Paramètres du critère dans cette boucle
* @return void
**/
function critere_lang_select_dist($idb, &$boucles, $crit) {
if (!isset($crit->param[1][0]) or !($param = $crit->param[1][0]->texte)) {
$param = 'oui';
}
if ($crit->not) {
$param = ($param == 'oui') ? 'non' : 'oui';
}
$boucle = &$boucles[$idb];
$boucle->lang_select = $param;
}
/**
* Compile le critère {debut_xxx}
*
* Limite le nombre d'éléments affichés.
*
* Ce critère permet de faire commencer la limitation des résultats
* par une variable passée dans lURL et commençant par 'debut_' tel que
* {debut_page,10}. Le second paramètre est le nombre de résultats à
* afficher.
*
* Note : il est plus simple d'utiliser le critère pagination.
*
* @param string $idb Identifiant de la boucle
* @param array $boucles AST du squelette
* @param Critere $crit Paramètres du critère dans cette boucle
* @return void
**/
function critere_debut_dist($idb, &$boucles, $crit) {
list($un, $deux) = $crit->param;
$un = $un[0]->texte;
$deux = $deux[0]->texte;
if ($deux) {
$boucles[$idb]->limit = 'intval($Pile[0]["debut' .
$un .
'"]) . ",' .
$deux .
'"';
} else {
calculer_critere_DEFAUT_dist($idb, $boucles, $crit);
}
}
/**
* Compile le critère `pagination` qui demande à paginer une boucle.
*
* Demande à paginer la boucle pour n'afficher qu'une partie des résultats,
* et gère l'affichage de la partie de page demandée par debut_xx dans
* dans l'environnement du squelette.
*
* Le premier paramètre indique le nombre d'éléments par page, le second,
* rarement utilisé permet de définir le nom de la variable désignant la
* page demandée (`debut_xx`), qui par défaut utilise l'identifiant de la boucle.
*
* @critere
* @see balise_PAGINATION_dist()
* @link http://www.spip.net/3367 Le système de pagination
* @link http://www.spip.net/4867 Le critère pagination
* @example
* ```
* {pagination}
* {pagination 20}
* {pagination #ENV{pages,5}} etc
* {pagination 20 #ENV{truc,chose}} pour utiliser la variable debut_#ENV{truc,chose}
* ```
*
* @param string $idb Identifiant de la boucle
* @param array $boucles AST du squelette
* @param Critere $crit Paramètres du critère dans cette boucle
* @return void
**/
function critere_pagination_dist($idb, &$boucles, $crit) {
$boucle = &$boucles[$idb];
// definition de la taille de la page
$pas = !isset($crit->param[0][0]) ? "''"
: calculer_liste(array($crit->param[0][0]), array(), $boucles, $boucle->id_parent);
if (!preg_match(_CODE_QUOTE, $pas, $r)) {
$pas = "((\$a = intval($pas)) ? \$a : 10)";
} else {
$r = intval($r[2]);
$pas = strval($r ? $r : 10);
}
// Calcul du nommage de la pagination si il existe.
// La nouvelle syntaxe {pagination 20, nom} est prise en compte et privilégiée mais on reste
// compatible avec l'ancienne car certains cas fonctionnent correctement
$type = "'$idb'";
// Calcul d'un nommage spécifique de la pagination si précisé.
// Syntaxe {pagination 20, nom}
if (isset($crit->param[0][1])) {
$type = calculer_liste(array($crit->param[0][1]), array(), $boucles, $boucle->id_parent);
} // Ancienne syntaxe {pagination 20 nom} pour compatibilité
elseif (isset($crit->param[1][0])) {
$type = calculer_liste(array($crit->param[1][0]), array(), $boucles, $boucle->id_parent);
}
$debut = ($type[0] !== "'") ? "'debut'.$type" : ("'debut" . substr($type, 1));
$boucle->modificateur['debut_nom'] = $type;
$partie =
// tester si le numero de page demande est de la forme '@yyy'
'isset($Pile[0][' . $debut . ']) ? $Pile[0][' . $debut . '] : _request(' . $debut . ");\n"
. "\tif(substr(\$debut_boucle,0,1)=='@'){\n"
. "\t\t" . '$debut_boucle = $Pile[0][' . $debut . '] = quete_debut_pagination(\'' . $boucle->primary . '\',$Pile[0][\'@' . $boucle->primary . '\'] = substr($debut_boucle,1),' . $pas . ',$iter);' . "\n"
. "\t\t" . '$iter->seek(0);' . "\n"
. "\t}\n"
. "\t" . '$debut_boucle = intval($debut_boucle)';
$boucle->hash .= '
$command[\'pagination\'] = array((isset($Pile[0][' . $debut . ']) ? $Pile[0][' . $debut . '] : null), ' . $pas . ');';
$boucle->total_parties = $pas;
calculer_parties($boucles, $idb, $partie, 'p+');
// ajouter la cle primaire dans le select pour pouvoir gerer la pagination referencee par @id
// sauf si pas de primaire, ou si primaire composee
// dans ce cas, on ne sait pas gerer une pagination indirecte
$t = $boucle->id_table . '.' . $boucle->primary;
if ($boucle->primary
and !preg_match('/[,\s]/', $boucle->primary)
and !in_array($t, $boucle->select)
) {
$boucle->select[] = $t;
}
}
/**
* Compile le critère `recherche` qui permet de sélectionner des résultats
* d'une recherche.
*
* Le texte cherché est pris dans le premier paramètre `{recherche xx}`
* ou à défaut dans la clé `recherche` de l'environnement du squelette.
*
* @critere
* @link http://www.spip.net/3878
* @see inc_prepare_recherche_dist()
*
* @param string $idb Identifiant de la boucle
* @param array $boucles AST du squelette
* @param Critere $crit Paramètres du critère dans cette boucle
* @return void
**/
function critere_recherche_dist($idb, &$boucles, $crit) {
$boucle = &$boucles[$idb];
if (!$boucle->primary or strpos($boucle->primary, ',')) {
erreur_squelette(_T('zbug_critere_sur_table_sans_cle_primaire', array('critere' => 'recherche')), $boucle);
return;
}
if (isset($crit->param[0])) {
$quoi = calculer_liste($crit->param[0], array(), $boucles, $boucles[$idb]->id_parent);
} else {
$quoi = '(isset($Pile[0]["recherche"])?$Pile[0]["recherche"]:(isset($GLOBALS["recherche"])?$GLOBALS["recherche"]:""))';
}
$_modificateur = var_export($boucle->modificateur, true);
$boucle->hash .= '
// RECHERCHE'
. ($crit->cond ? '
if (!strlen(' . $quoi . ')){
list($rech_select, $rech_where) = array("0 as points","");
} else' : '') . '
{
$prepare_recherche = charger_fonction(\'prepare_recherche\', \'inc\');
list($rech_select, $rech_where) = $prepare_recherche(' . $quoi . ', "' . $boucle->id_table . '", "' . $crit->cond . '","' . $boucle->sql_serveur . '",' . $_modificateur . ',"' . $boucle->primary . '");
}
';
$t = $boucle->id_table . '.' . $boucle->primary;
if (!in_array($t, $boucles[$idb]->select)) {
$boucle->select[] = $t;
} # pour postgres, neuneu ici
// jointure uniquement sur le serveur principal
// (on ne peut joindre une table d'un serveur distant avec la table des resultats du serveur principal)
if (!$boucle->sql_serveur) {
$boucle->join['resultats'] = array("'" . $boucle->id_table . "'", "'id'", "'" . $boucle->primary . "'");
$boucle->from['resultats'] = 'spip_resultats';
}
$boucle->select[] = '$rech_select';
//$boucle->where[]= "\$rech_where?'resultats.id=".$boucle->id_table.".".$boucle->primary."':''";
// et la recherche trouve
$boucle->where[] = '$rech_where?$rech_where:\'\'';
}
/**
* Compile le critère `traduction`
*
* Sélectionne toutes les traductions de l'élément courant (la boucle englobante)
* en différentes langues (y compris l'élément englobant)
*
* Équivalent à `(id_trad>0 AND id_trad=id_trad(precedent)) OR id_xx=id_xx(precedent)`
*
* @param string $idb Identifiant de la boucle
* @param array $boucles AST du squelette
* @param Critere $crit Paramètres du critère dans cette boucle
* @return void
**/
function critere_traduction_dist($idb, &$boucles, $crit) {
$boucle = &$boucles[$idb];
$prim = $boucle->primary;
$table = $boucle->id_table;
$arg = kwote(calculer_argument_precedent($idb, 'id_trad', $boucles));
$dprim = kwote(calculer_argument_precedent($idb, $prim, $boucles));
$boucle->where[] =
array(
"'OR'",
array(
"'AND'",
array("'='", "'$table.id_trad'", 0),
array("'='", "'$table.$prim'", $dprim)
),
array(
"'AND'",
array("'>'", "'$table.id_trad'", 0),
array("'='", "'$table.id_trad'", $arg)
)
);
}
/**
* Compile le critère {origine_traduction}
*
* Sélectionne les éléments qui servent de base à des versions traduites
* (par exemple les articles "originaux" sur une boucle articles)
*
* Équivalent à (id_trad>0 AND id_xx=id_trad) OR (id_trad=0)
*
* @param string $idb Identifiant de la boucle
* @param array $boucles AST du squelette
* @param Critere $crit Paramètres du critère dans cette boucle
* @return void
**/
function critere_origine_traduction_dist($idb, &$boucles, $crit) {
$boucle = &$boucles[$idb];
$prim = $boucle->primary;
$table = $boucle->id_table;
$c =
array(
"'OR'",
array("'='", "'$table." . "id_trad'", "'$table.$prim'"),
array("'='", "'$table.id_trad'", "'0'")
);
$boucle->where[] = ($crit->not ? array("'NOT'", $c) : $c);
}
/**
* Compile le critère {meme_parent}
*
* Sélectionne les éléments ayant le même parent que la boucle parente,
* c'est à dire les frères et sœurs.
*
* @param string $idb Identifiant de la boucle
* @param array $boucles AST du squelette
* @param Critere $crit Paramètres du critère dans cette boucle
* @return void
**/
function critere_meme_parent_dist($idb, &$boucles, $crit) {
$boucle = &$boucles[$idb];
$arg = kwote(calculer_argument_precedent($idb, 'id_parent', $boucles));
$id_parent = isset($GLOBALS['exceptions_des_tables'][$boucle->id_table]['id_parent']) ?
$GLOBALS['exceptions_des_tables'][$boucle->id_table]['id_parent'] :
'id_parent';
$mparent = $boucle->id_table . '.' . $id_parent;
if ($boucle->type_requete == 'rubriques' or isset($GLOBALS['exceptions_des_tables'][$boucle->id_table]['id_parent'])) {
$boucle->where[] = array("'='", "'$mparent'", $arg);
} // le cas FORUMS est gere dans le plugin forum, dans la fonction critere_FORUMS_meme_parent_dist()
else {
return (array('zbug_critere_inconnu', array('critere' => $crit->op . ' ' . $boucle->type_requete)));
}
}
/**
* Compile le critère `branche` qui sélectionne dans une boucle les
* éléments appartenant à une branche d'une rubrique.
*
* Cherche l'identifiant de la rubrique en premier paramètre du critère `{branche XX}`
* s'il est renseigné, sinon, sans paramètre (`{branche}` tout court) dans les
* boucles parentes. On calcule avec lui la liste des identifiants
* de rubrique de toute la branche.
*
* La boucle qui possède ce critère cherche une liaison possible avec
* la colonne `id_rubrique`, et tentera de trouver une jointure avec une autre
* table si c'est nécessaire pour l'obtenir.
*
* Ce critère peut être rendu optionnel avec `{branche ?}` en remarquant
* cependant que le test s'effectue sur la présence d'un champ 'id_rubrique'
* sinon d'une valeur 'id_rubrique' dans l'environnement (et non 'branche'
* donc).
*
* @link http://www.spip.net/@branche
*
* @param string $idb Identifiant de la boucle
* @param array $boucles AST du squelette
* @param Critere $crit Paramètres du critère dans cette boucle
* @return void
**/
function critere_branche_dist($idb, &$boucles, $crit) {
$not = $crit->not;
$boucle = &$boucles[$idb];
// prendre en priorite un identifiant en parametre {branche XX}
if (isset($crit->param[0])) {
$arg = calculer_liste($crit->param[0], array(), $boucles, $boucles[$idb]->id_parent);
// sinon on le prend chez une boucle parente
} else {
$arg = kwote(calculer_argument_precedent($idb, 'id_rubrique', $boucles), $boucle->sql_serveur, 'int NOT NULL');
}
//Trouver une jointure
$champ = "id_rubrique";
$desc = $boucle->show;
//Seulement si necessaire
if (!array_key_exists($champ, $desc['field'])) {
$cle = trouver_jointure_champ($champ, $boucle);
$trouver_table = charger_fonction("trouver_table", "base");
$desc = $trouver_table($boucle->from[$cle]);
if (count(trouver_champs_decomposes($champ, $desc)) > 1) {
$decompose = decompose_champ_id_objet($champ);
$champ = array_shift($decompose);
$boucle->where[] = array("'='", _q($cle . "." . reset($decompose)), '"' . sql_quote(end($decompose)) . '"');
}
} else {
$cle = $boucle->id_table;
}
$c = "sql_in('$cle" . ".$champ', calcul_branche_in($arg)"
. ($not ? ", 'NOT'" : '') . ")";
$boucle->where[] = !$crit->cond ? $c :
("($arg ? $c : " . ($not ? "'0=1'" : "'1=1'") . ')');
}
/**
* Compile le critère `logo` qui liste les objets qui ont un logo
*
* @uses lister_objets_avec_logos()
* Pour obtenir les éléments qui ont un logo
*
* @param string $idb Identifiant de la boucle
* @param array $boucles AST du squelette
* @param Critere $crit Paramètres du critère dans cette boucle
* @return void
**/
function critere_logo_dist($idb, &$boucles, $crit) {
$not = $crit->not;
$boucle = &$boucles[$idb];
$c = "sql_in('" .
$boucle->id_table . '.' . $boucle->primary
. "', lister_objets_avec_logos('" . $boucle->primary . "'), '')";
if ($not) {
$boucle->where[] = array("'NOT'", $c);
} else {
$boucle->where[] = $c;
}
}
/**
* Compile le critère `fusion` qui regroupe les éléments selon une colonne.
*
* C'est la commande SQL «GROUP BY»
*
* @critere
* @link http://www.spip.net/5166
* @example
* ```
* <BOUCLE_a(articles){fusion lang}>
* ```
*
* @param string $idb Identifiant de la boucle
* @param array $boucles AST du squelette
* @param Critere $crit Paramètres du critère dans cette boucle
* @return void
**/
function critere_fusion_dist($idb, &$boucles, $crit) {
if ($t = isset($crit->param[0])) {
$t = $crit->param[0];
if ($t[0]->type == 'texte') {
$t = $t[0]->texte;
if (preg_match("/^(.*)\.(.*)$/", $t, $r)) {
$t = table_objet_sql($r[1]);
$t = array_search($t, $boucles[$idb]->from);
if ($t) {
$t .= '.' . $r[2];
}
}
} else {
$t = '".'
. calculer_critere_arg_dynamique($idb, $boucles, $t)
. '."';
}
}
if ($t) {
$boucles[$idb]->group[] = $t;
if (!in_array($t, $boucles[$idb]->select)) {
$boucles[$idb]->select[] = $t;
}
} else {
return (array('zbug_critere_inconnu', array('critere' => $crit->op . ' ?')));
}
}
/**
* Compile le critère `{collecte}` qui permet de spécifier l'interclassement
* à utiliser pour les tris de la boucle.
*
* Cela permet avec le critère `{par}` de trier un texte selon
* l'interclassement indiqué. L'instruction s'appliquera sur les critères `{par}`
* qui succèdent ce critère, ainsi qu'au critère `{par}` précédent
* si aucun interclassement ne lui est déjà appliqué.
*
* Techniquement, c'est la commande SQL "COLLATE" qui utilisée.
* (elle peut être appliquée sur les order by, group by, where, like ...)
*
* @example
* - `{par titre}{collecte utf8_spanish_ci}` ou `{collecte utf8_spanish_ci}{par titre}`
* - `{par titre}{par surtitre}{collecte utf8_spanish_ci}` :
* Seul 'surtitre' (`par` précédent) utilisera l'interclassement
* - `{collecte utf8_spanish_ci}{par titre}{par surtitre}` :
* 'titre' et 'surtitre' utiliseront l'interclassement (tous les `par` suivants)
*
* @note
* Piège sur une éventuelle écriture peu probable :
* `{par a}{collecte c1}{par b}{collecte c2}` : le tri `{par b}`
* utiliserait l'interclassement c1 (et non c2 qui ne s'applique pas
* au `par` précédent s'il a déjà un interclassement demandé).
*
* @critere
* @link http://www.spip.net/4028
* @see critere_par_dist() Le critère `{par}`
*
* @param string $idb Identifiant de la boucle
* @param array $boucles AST du squelette
* @param Critere $crit Paramètres du critère dans cette boucle
*/
function critere_collecte_dist($idb, &$boucles, $crit) {
if (isset($crit->param[0])) {
$_coll = calculer_liste($crit->param[0], array(), $boucles, $boucles[$idb]->id_parent);
$boucle = $boucles[$idb];
$boucle->modificateur['collate'] = "($_coll ?' COLLATE '.$_coll:'')";
$n = count($boucle->order);
if ($n && (strpos($boucle->order[$n - 1], 'COLLATE') === false)) {
// l'instruction COLLATE doit être placée avant ASC ou DESC
// notamment lors de l'utilisation `{!par xxx}{collate yyy}`
if (
(false !== $i = strpos($boucle->order[$n - 1], 'ASC'))
OR (false !== $i = strpos($boucle->order[$n - 1], 'DESC'))
) {
$boucle->order[$n - 1] = substr_replace($boucle->order[$n - 1], "' . " . $boucle->modificateur['collate'] . " . ' ", $i, 0);
} else {
$boucle->order[$n - 1] .= " . " . $boucle->modificateur['collate'];
}
}
} else {
return (array('zbug_critere_inconnu', array('critere' => $crit->op . " " . count($boucles[$idb]->order))));
}
}
// http://code.spip.net/@calculer_critere_arg_dynamique
function calculer_critere_arg_dynamique($idb, &$boucles, $crit, $suffix = '') {
$boucle = $boucles[$idb];
$alt = "('" . $boucle->id_table . '.\' . $x' . $suffix . ')';
$var = '$champs_' . $idb;
$desc = (strpos($boucle->in, "static $var =") !== false);
if (!$desc) {
$desc = $boucle->show['field'];
$desc = implode(',', array_map('_q', array_keys($desc)));
$boucles[$idb]->in .= "\n\tstatic $var = array(" . $desc . ");";
}
if ($desc) {
$alt = "(in_array(\$x, $var) ? $alt :(\$x$suffix))";
}
$arg = calculer_liste($crit, array(), $boucles, $boucle->id_parent);
return "((\$x = preg_replace(\"/\\W/\",'', $arg)) ? $alt : '')";
}
/**
* Compile le critère `{par}` qui permet d'ordonner les résultats d'une boucle
*
* Demande à trier la boucle selon certains champs (en SQL, la commande `ORDER BY`).
* Si plusieurs tris sont demandés (plusieurs fois le critère `{par x}{par y}` dans une boucle ou plusieurs champs
* séparés par des virgules dans le critère `{par x, y, z}`), ils seront appliqués dans l'ordre.
*
* Quelques particularités :
* - `{par hasard}` : trie par hasard
* - `{par num titre}` : trie par numéro de titre
* - `{par multi titre}` : trie par la langue extraite d'une balise polyglotte `<multi>` sur le champ titre
* - `{!par date}` : trie par date inverse en utilisant le champ date principal déclaré pour la table (si c'est un objet éditorial).
* - `{!par points}` : trie par pertinence de résultat de recherche (avec le critère `{recherche}`)
* - `{par FUNCTION_SQL(n)}` : trie en utilisant une fonction SQL (peut dépendre du moteur SQL utilisé).
* Exemple : `{par SUBSTRING_INDEX(titre, ".", -1)}` (tri ~ alphabétique en ignorant les numéros de titres
* (exemple erroné car faux dès qu'un titre possède un point.)).
* - `{par table.champ}` : trie en effectuant une jointure sur la table indiquée.
* - `{par #BALISE}` : trie sur la valeur retournée par la balise (doit être un champ de la table, ou 'hasard').
*
* @example
* - `{par titre}`
* - `{!par date}`
* - `{par num titre, multi titre, hasard}`
*
* @critere
* @link http://www.spip.net/5531
* @see critere_tri_dist() Le critère `{tri ...}`
* @see critere_inverse_dist() Le critère `{inverse}`
*
* @uses critere_parinverse()
*
* @param string $idb Identifiant de la boucle
* @param array $boucles AST du squelette
* @param Critere $crit Paramètres du critère dans cette boucle
*/
function critere_par_dist($idb, &$boucles, $crit) {
return critere_parinverse($idb, $boucles, $crit);
}
/**
* Calculs pour le critère `{par}` ou `{inverse}` pour ordonner les résultats d'une boucle
*
* Les expressions intermédiaires `{par expr champ}` sont calculées dans des fonctions
* `calculer_critere_par_expression_{expr}()` notamment `{par num champ}` ou `{par multi champ}`.
*
* @see critere_par_dist() Le critère `{par}` pour des exemples
*
* @uses calculer_critere_arg_dynamique() pour le calcul de `{par #ENV{tri}}`
* @uses calculer_critere_par_hasard() pour le calcul de `{par hasard}`
* @uses calculer_critere_par_champ()
* @see calculer_critere_par_expression_num() pour le calcul de `{par num x}`
* @see calculer_critere_par_expression_multi() pour le calcul de `{par multi x}`
*
* @param string $idb Identifiant de la boucle
* @param array $boucles AST du squelette
* @param Critere $crit Paramètres du critère dans cette boucle
*/
function critere_parinverse($idb, &$boucles, $crit) {
$boucle = &$boucles[$idb];
$sens = $collecte = '';
if ($crit->not) {
$sens = " . ' DESC'";
}
if (isset($boucle->modificateur['collate'])) {
$collecte = ' . ' . $boucle->modificateur['collate'];
}
// Pour chaque paramètre du critère
foreach ($crit->param as $tri) {
$order = $fct = '';
// tris specifiés dynamiquement {par #ENV{tri}}
if ($tri[0]->type != 'texte') {
// calculer le order dynamique qui verifie les champs
$order = calculer_critere_arg_dynamique($idb, $boucles, $tri, $sens);
// ajouter 'hasard' comme possibilité de tri dynamique
calculer_critere_par_hasard($idb, $boucles, $crit);
}
// tris textuels {par titre}
else {
$par = array_shift($tri);
$par = $par->texte;
// tris de la forme {par expression champ} tel que {par num titre} ou {par multi titre}
if (preg_match(",^(\w+)[\s]+(.*)$,", $par, $m)) {
$expression = trim($m[1]);
$champ = trim($m[2]);
if (function_exists($f = 'calculer_critere_par_expression_' . $expression)) {
$order = $f($idb, $boucles, $crit, $tri, $champ);
} else {
return array('zbug_critere_inconnu', array('critere' => $crit->op . " $par"));
}
// tris de la forme {par champ} ou {par FONCTION(champ)}
} elseif (preg_match(",^" . CHAMP_SQL_PLUS_FONC . '$,is', $par, $match)) {
// {par FONCTION(champ)}
if (count($match) > 2) {
$par = substr($match[2], 1, -1);
$fct = $match[1];
}
// quelques cas spécifiques {par hasard}, {par date}
if ($par == 'hasard') {
$order = calculer_critere_par_hasard($idb, $boucles, $crit);
} elseif ($par == 'date' and !empty($boucle->show['date'])) {
$order = "'" . $boucle->id_table . "." . $boucle->show['date'] . "'";
} else {
// cas général {par champ}, {par table.champ}, ...
$order = calculer_critere_par_champ($idb, $boucles, $crit, $par);
}
}
// on ne sait pas traiter…
else {
return array('zbug_critere_inconnu', array('critere' => $crit->op . " $par"));
}
// En cas d'erreur de squelette retournée par une fonction
if (is_array($order)) {
return $order;
}
}
if (preg_match('/^\'([^"]*)\'$/', $order, $m)) {
$t = $m[1];
if (strpos($t, '.') and !in_array($t, $boucle->select)) {
$boucle->select[] = $t;
}
} else {
$sens = '';
}
if ($fct) {
if (preg_match("/^\s*'(.*)'\s*$/", $order, $r)) {
$order = "'$fct(" . $r[1] . ")'";
} else {
$order = "'$fct(' . $order . ')'";
}
}
$t = $order . $collecte . $sens;
if (preg_match("/^(.*)'\s*\.\s*'([^']*')$/", $t, $r)) {
$t = $r[1] . $r[2];
}
$boucle->order[] = $t;
}
}
/**
* Calculs pour le critère `{par hasard}`
*
* Ajoute le générateur d'aléatoire au SELECT de la boucle.
*
* @param string $idb Identifiant de la boucle
* @param array $boucles AST du squelette
* @param Critere $crit Paramètres du critère dans cette boucle
* @return string Clause pour le Order by
*/
function calculer_critere_par_hasard($idb, &$boucles, $crit) {
$boucle = &$boucles[$idb];
// Si ce n'est fait, ajouter un champ 'hasard' dans le select
$parha = "rand() AS hasard";
if (!in_array($parha, $boucle->select)) {
$boucle->select[] = $parha;
}
return "'hasard'";
}
/**
* Calculs pour le critère `{par num champ}` qui extrait le numéro préfixant un texte
*
* Tri par numéro de texte (tel que "10. titre"). Le numéro calculé est ajouté au SELECT
* de la boucle. L'écriture `{par num #ENV{tri}}` est aussi prise en compte.
*
* @note
* Les textes sans numéro valent 0 et sont donc placés avant les titres ayant des numéros.
* Utiliser `{par sinum champ, num champ}` pour avoir le comportement inverse.
*
* @see calculer_critere_par_expression_sinum() pour le critère `{par sinum champ}`
* @uses calculer_critere_par_champ()
*
* @param string $idb Identifiant de la boucle
* @param array $boucles AST du squelette
* @param Critere $crit Paramètres du critère dans cette boucle
* @param array $tri Paramètre en cours du critère
* @param string $champ Texte suivant l'expression ('titre' dans {par num titre})
* @return string Clause pour le Order by
*/
function calculer_critere_par_expression_num($idb, &$boucles, $crit, $tri, $champ) {
$_champ = calculer_critere_par_champ($idb, $boucles, $crit, $champ, true);
if (is_array($_champ)) {
return array('zbug_critere_inconnu', array('critere' => $crit->op . " num $champ"));
}
$boucle = &$boucles[$idb];
$texte = '0+' . $_champ;
$suite = calculer_liste($tri, array(), $boucles, $boucle->id_parent);
if ($suite !== "''") {
$texte = "\" . ((\$x = $suite) ? ('$texte' . \$x) : '0')" . " . \"";
}
$as = 'num' . ($boucle->order ? count($boucle->order) : "");
$boucle->select[] = $texte . " AS $as";
$order = "'$as'";
return $order;
}
/**
* Calculs pour le critère `{par sinum champ}` qui ordonne les champs avec numéros en premier
*
* Ajoute au SELECT la valeur 'sinum' qui vaut 0 si le champ a un numéro, 1 s'il n'en a pas.
* Ainsi `{par sinum titre, num titre, titre}` mettra les éléments sans numéro en fin de liste,
* contrairement à `{par num titre, titre}` seulement.
*
* @see calculer_critere_par_expression_num() pour le critère `{par num champ}`
* @uses calculer_critere_par_champ()
*
* @param string $idb Identifiant de la boucle
* @param array $boucles AST du squelette
* @param Critere $crit Paramètres du critère dans cette boucle
* @param array $tri Paramètre en cours du critère
* @param string $champ Texte suivant l'expression ('titre' dans {par sinum titre})
* @return string Clause pour le Order by
*/
function calculer_critere_par_expression_sinum($idb, &$boucles, $crit, $tri, $champ) {
$_champ = calculer_critere_par_champ($idb, $boucles, $crit, $champ, true);
if (is_array($_champ)) {
return array('zbug_critere_inconnu', array('critere' => $crit->op . " sinum $champ"));
}
$boucle = &$boucles[$idb];
$texte = '0+' . $_champ;
$suite = calculer_liste($tri, array(), $boucles, $boucle->id_parent);
if ($suite !== "''") {
$texte = "\" . ((\$x = $suite) ? ('$texte' . \$x) : '0')" . " . \"";
}
$as = 'sinum' . ($boucle->order ? count($boucle->order) : "");
$boucle->select[] = 'CASE (' . $texte . ') WHEN 0 THEN 1 ELSE 0 END AS ' . $as;
$order = "'$as'";
return $order;
}
/**
* Calculs pour le critère `{par multi champ}` qui extrait la langue en cours dans les textes
* ayant des balises `<multi>` (polyglottes)
*
* Ajoute le calcul du texte multi extrait dans le SELECT de la boucle.
* Il ne peut y avoir qu'un seul critère de tri `multi` par boucle.
*
* @uses calculer_critere_par_champ()
* @param string $idb Identifiant de la boucle
* @param array $boucles AST du squelette
* @param Critere $crit Paramètres du critère dans cette boucle
* @param array $tri Paramètre en cours du critère
* @param string $champ Texte suivant l'expression ('titre' dans {par multi titre})
* @return string Clause pour le Order by
*/
function calculer_critere_par_expression_multi($idb, &$boucles, $crit, $tri, $champ) {
$_champ = calculer_critere_par_champ($idb, $boucles, $crit, $champ, true);
if (is_array($_champ)) {
return array('zbug_critere_inconnu', array('critere' => $crit->op . " multi $champ"));
}
$boucle = &$boucles[$idb];
$boucle->select[] = "\".sql_multi('" . $_champ . "', \$GLOBALS['spip_lang']).\"";
$order = "'multi'";
return $order;
}
/**
* Retourne le champ de tri demandé en ajoutant éventuellement les jointures nécessaires à la boucle.
*
* - si le champ existe dans la table, on l'utilise
* - si c'est une exception de jointure, on l'utilise (et crée la jointure au besoin)
* - si c'est un champ dont la jointure est déjà présente on la réutilise
* - si c'est un champ dont la jointure n'est pas présente, on la crée.
*
* @param string $idb Identifiant de la boucle
* @param array $boucles AST du squelette
* @param Critere $crit Paramètres du critère dans cette boucle
* @param string $par Nom du tri à analyser ('champ' ou 'table.champ')
* @param bool $raw Retourne le champ pour le compilateur ("'alias.champ'") ou brut ('alias.champ')
* @return array|string
*/
function calculer_critere_par_champ($idb, &$boucles, $crit, $par, $raw = false) {
$boucle = &$boucles[$idb];
// le champ existe dans la table, pas de souci (le plus commun)
if (isset($desc['field'][$par])) {
$par = $boucle->id_table . "." . $par;
}
// le champ est peut être une jointure
else {
$table = $table_alias = false; // toutes les tables de jointure possibles
$champ = $par;
// le champ demandé est une exception de jointure {par titre_mot}
if (isset($GLOBALS['exceptions_des_jointures'][$par])) {
list($table, $champ) = $GLOBALS['exceptions_des_jointures'][$par];
} // la table de jointure est explicitement indiquée {par truc.muche}
elseif (preg_match("/^([^,]*)\.(.*)$/", $par, $r)) {
list(, $table, $champ) = $r;
$table_alias = $table; // c'est peut-être un alias de table {par L1.titre}
$table = table_objet_sql($table);
}
// Si on connait la table d'arrivée, on la demande donc explicitement
// Sinon on cherche le champ dans les tables possibles de jointures
// Si la table est déjà dans le from, on la réutilise.
if ($infos = chercher_champ_dans_tables($champ, $boucle->from, $boucle->sql_serveur, $table)) {
$par = $infos['alias'] . "." . $champ;
} elseif (
$boucle->jointures_explicites
and $alias = trouver_jointure_champ($champ, $boucle, explode(' ', $boucle->jointures_explicites), false, $table)
) {
$par = $alias . "." . $champ;
} elseif ($alias = trouver_jointure_champ($champ, $boucle, $boucle->jointures, false, $table)) {
$par = $alias . "." . $champ;
// en spécifiant directement l'alias {par L2.titre} (situation hasardeuse tout de même)
} elseif (
$table_alias
and isset($boucle->from[$table_alias])
and $infos = chercher_champ_dans_tables($champ, $boucle->from, $boucle->sql_serveur, $boucle->from[$table_alias])
) {
$par = $infos['alias'] . "." . $champ;
} elseif ($table) {
// On avait table + champ, mais on ne les a pas trouvés
return array('zbug_critere_inconnu', array('critere' => $crit->op . " $par"));
} else {
// Sinon tant pis, ca doit etre un champ synthetise (cf points)
}
}
return $raw ? $par : "'$par'";
}
/**
* Retourne un champ de tri en créant une jointure
* si la table n'est pas présente dans le from de la boucle.
*
* @deprecated
* @param string $table Table du champ désiré
* @param string $champ Champ désiré
* @param Boucle $boucle Boucle en cours de compilation
* @return string Champ pour le compilateur si trouvé, tel que "'alias.champ'", sinon vide.
*/
function critere_par_joint($table, $champ, &$boucle) {
$t = array_search($table, $boucle->from);
if (!$t) {
$t = trouver_jointure_champ($champ, $boucle);
}
return !$t ? '' : ("'" . $t . '.' . $champ . "'");
}
/**
* Compile le critère `{inverse}` qui inverse l'ordre utilisé par le précédent critère `{par}`
*
* Accèpte un paramètre pour déterminer le sens : `{inverse #X}` utilisera un tri croissant (ASC)
* si la valeur retournée par `#X` est considérée vrai (`true`),
* le sens contraire (DESC) sinon.
*
* @example
* - `{par date}{inverse}`, équivalent à `{!par date}`
* - `{par date}{inverse #ENV{sens}}` utilise la valeur d'environnement sens pour déterminer le sens.
*
* @critere
* @see critere_par_dist() Le critère `{par}`
* @link http://www.spip.net/5530
* @uses critere_parinverse()
*
* @param string $idb Identifiant de la boucle
* @param array $boucles AST du squelette
* @param Critere $crit Paramètres du critère dans cette boucle
*/
function critere_inverse_dist($idb, &$boucles, $crit) {
$boucle = &$boucles[$idb];
// Classement par ordre inverse
if ($crit->not) {
critere_parinverse($idb, $boucles, $crit);
} else {
$order = "' DESC'";
// Classement par ordre inverse fonction eventuelle de #ENV{...}
if (isset($crit->param[0])) {
$critere = calculer_liste($crit->param[0], array(), $boucles, $boucles[$idb]->id_parent);
$order = "(($critere)?' DESC':'')";
}
$n = count($boucle->order);
if (!$n) {
if (isset($boucle->default_order[0])) {
$boucle->default_order[0] .= ' . " DESC"';
} else {
$boucle->default_order[] = ' DESC';
}
} else {
$t = $boucle->order[$n - 1] . " . $order";
if (preg_match("/^(.*)'\s*\.\s*'([^']*')$/", $t, $r)) {
$t = $r[1] . $r[2];
}
$boucle->order[$n - 1] = $t;
}
}
}
// http://code.spip.net/@critere_agenda_dist
function critere_agenda_dist($idb, &$boucles, $crit) {
$params = $crit->param;
if (count($params) < 1) {
return array('zbug_critere_inconnu', array('critere' => $crit->op . " ?"));
}
$boucle = &$boucles[$idb];
$parent = $boucle->id_parent;
$fields = $boucle->show['field'];
$date = array_shift($params);
$type = array_shift($params);
// la valeur $type doit etre connue a la compilation
// donc etre forcement reduite a un litteral unique dans le source
$type = is_object($type[0]) ? $type[0]->texte : null;
// La valeur date doit designer un champ de la table SQL.
// Si c'est un litteral unique dans le source, verifier a la compil,
// sinon synthetiser le test de verif pour execution ulterieure
// On prendra arbitrairement le premier champ si test negatif.
if ((count($date) == 1) and ($date[0]->type == 'texte')) {
$date = $date[0]->texte;
if (!isset($fields[$date])) {
return array('zbug_critere_inconnu', array('critere' => $crit->op . " " . $date));
}
} else {
$a = calculer_liste($date, array(), $boucles, $parent);
$noms = array_keys($fields);
$defaut = $noms[0];
$noms = join(" ", $noms);
# bien laisser 2 espaces avant $nom pour que strpos<>0
$cond = "(\$a=strval($a))AND\nstrpos(\" $noms \",\" \$a \")";
$date = "'.(($cond)\n?\$a:\"$defaut\").'";
}
$annee = $params ? array_shift($params) : "";
$annee = "\n" . 'sprintf("%04d", ($x = ' .
calculer_liste($annee, array(), $boucles, $parent) .
') ? $x : date("Y"))';
$mois = $params ? array_shift($params) : "";
$mois = "\n" . 'sprintf("%02d", ($x = ' .
calculer_liste($mois, array(), $boucles, $parent) .
') ? $x : date("m"))';
$jour = $params ? array_shift($params) : "";
$jour = "\n" . 'sprintf("%02d", ($x = ' .
calculer_liste($jour, array(), $boucles, $parent) .
') ? $x : date("d"))';
$annee2 = $params ? array_shift($params) : "";
$annee2 = "\n" . 'sprintf("%04d", ($x = ' .
calculer_liste($annee2, array(), $boucles, $parent) .
') ? $x : date("Y"))';
$mois2 = $params ? array_shift($params) : "";
$mois2 = "\n" . 'sprintf("%02d", ($x = ' .
calculer_liste($mois2, array(), $boucles, $parent) .
') ? $x : date("m"))';
$jour2 = $params ? array_shift($params) : "";
$jour2 = "\n" . 'sprintf("%02d", ($x = ' .
calculer_liste($jour2, array(), $boucles, $parent) .
') ? $x : date("d"))';
$date = $boucle->id_table . ".$date";
$quote_end = ",'" . $boucle->sql_serveur . "','text'";
if ($type == 'jour') {
$boucle->where[] = array(
"'='",
"'DATE_FORMAT($date, \'%Y%m%d\')'",
("sql_quote($annee . $mois . $jour$quote_end)")
);
} elseif ($type == 'mois') {
$boucle->where[] = array(
"'='",
"'DATE_FORMAT($date, \'%Y%m\')'",
("sql_quote($annee . $mois$quote_end)")
);
} elseif ($type == 'semaine') {
$boucle->where[] = array(
"'AND'",
array(
"'>='",
"'DATE_FORMAT($date, \'%Y%m%d\')'",
("date_debut_semaine($annee, $mois, $jour)")
),
array(
"'<='",
"'DATE_FORMAT($date, \'%Y%m%d\')'",
("date_fin_semaine($annee, $mois, $jour)")
)
);
} elseif (count($crit->param) > 2) {
$boucle->where[] = array(
"'AND'",
array(
"'>='",
"'DATE_FORMAT($date, \'%Y%m%d\')'",
("sql_quote($annee . $mois . $jour$quote_end)")
),
array("'<='", "'DATE_FORMAT($date, \'%Y%m%d\')'", ("sql_quote($annee2 . $mois2 . $jour2$quote_end)"))
);
}
// sinon on prend tout
}
/**
* Compile les critères {i,j} et {i/j}
*
* Le critère {i,j} limite l'affiche de la boucle en commançant l'itération
* au i-ème élément, et pour j nombre d'éléments.
* Le critère {n-i,j} limite en commençant au n moins i-ème élément de boucle
* Le critère {i,n-j} limite en terminant au n moins j-ème élément de boucle.
*
* Le critère {i/j} affiche une part d'éléments de la boucle.
* Commence à i*n/j élément et boucle n/j éléments. {2/4} affiche le second
* quart des éléments d'une boucle.
*
* Traduit si possible (absence de n dans {i,j}) la demande en une
* expression LIMIT du gestionnaire SQL
*
* @param string $idb Identifiant de la boucle
* @param array $boucles AST du squelette
* @param Critere $crit Paramètres du critère dans cette boucle
* @return void
**/
function calculer_critere_parties($idb, &$boucles, $crit) {
$boucle = &$boucles[$idb];
$a1 = $crit->param[0];
$a2 = $crit->param[1];
$op = $crit->op;
list($a11, $a12) = calculer_critere_parties_aux($idb, $boucles, $a1);
list($a21, $a22) = calculer_critere_parties_aux($idb, $boucles, $a2);
if (($op == ',') && (is_numeric($a11) && (is_numeric($a21)))) {
$boucle->limit = $a11 . ',' . $a21;
} else {
// 3 dans {1/3}, {2,3} ou {1,n-3}
$boucle->total_parties = ($a21 != 'n') ? $a21 : $a22;
// 2 dans {2/3}, {2,5}, {n-2,1}
$partie = ($a11 != 'n') ? $a11 : $a12;
$mode = (($op == '/') ? '/' :
(($a11 == 'n') ? '-' : '+') . (($a21 == 'n') ? '-' : '+'));
// cas simple {0,#ENV{truc}} compilons le en LIMIT :
if ($a11 !== 'n' and $a21 !== 'n' and $mode == "++" and $op == ',') {
$boucle->limit =
(is_numeric($a11) ? "'$a11'" : $a11)
. ".','."
. (is_numeric($a21) ? "'$a21'" : $a21);
} else {
calculer_parties($boucles, $idb, $partie, $mode);
}
}
}
/**
* Compile certains critères {i,j} et {i/j}
*
* Calcule une expression déterminant $debut_boucle et $fin_boucle (le
* début et la fin des éléments de la boucle qui doivent être affichés)
* et les déclare dans la propriété «mode_partie» de la boucle, qui se
* charge également de déplacer le pointeur de boucle sur le premier
* élément à afficher.
*
* Place dans la propriété partie un test vérifiant que l'élément de
* boucle en cours de lecture appartient bien à la plage autorisée.
* Trop tôt, passe à l'élément suivant, trop tard, sort de l'itération de boucle.
*
* @param array $boucles AST du squelette
* @param string $id_boucle Identifiant de la boucle
* @param string $debut Valeur ou code pour trouver le début (i dans {i,j})
* @param string $mode
* Mode (++, p+, +- ...) : 2 signes début & fin
* - le signe - indique
* -- qu'il faut soustraire debut du total {n-3,x}. 3 étant $debut
* -- qu'il faut raccourcir la fin {x,n-3} de 3 elements. 3 étant $total_parties
* - le signe p indique une pagination
* @return void
**/
function calculer_parties(&$boucles, $id_boucle, $debut, $mode) {
$total_parties = $boucles[$id_boucle]->total_parties;
preg_match(",([+-/p])([+-/])?,", $mode, $regs);
list(, $op1, $op2) = array_pad($regs, 3, null);
$nombre_boucle = "\$Numrows['$id_boucle']['total']";
// {1/3}
if ($op1 == '/') {
$pmoins1 = is_numeric($debut) ? ($debut - 1) : "($debut-1)";
$totpos = is_numeric($total_parties) ? ($total_parties) :
"($total_parties ? $total_parties : 1)";
$fin = "ceil(($nombre_boucle * $debut )/$totpos) - 1";
$debut = !$pmoins1 ? 0 : "ceil(($nombre_boucle * $pmoins1)/$totpos);";
} else {
// cas {n-1,x}
if ($op1 == '-') {
$debut = "$nombre_boucle - $debut;";
}
// cas {x,n-1}
if ($op2 == '-') {
$fin = '$debut_boucle + ' . $nombre_boucle . ' - '
. (is_numeric($total_parties) ? ($total_parties + 1) :
($total_parties . ' - 1'));
} else {
// {x,1} ou {pagination}
$fin = '$debut_boucle'
. (is_numeric($total_parties) ?
(($total_parties == 1) ? "" : (' + ' . ($total_parties - 1))) :
('+' . $total_parties . ' - 1'));
}
// {pagination}, gerer le debut_xx=-1 pour tout voir
if ($op1 == 'p') {
$debut .= ";\n \$debut_boucle = ((\$tout=(\$debut_boucle == -1))?0:(\$debut_boucle))";
$debut .= ";\n \$debut_boucle = max(0,min(\$debut_boucle,floor(($nombre_boucle-1)/($total_parties))*($total_parties)))";
$fin = "(\$tout ? $nombre_boucle : $fin)";
}
}
// 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
// Utiliser min pour rabattre $fin_boucle sur total_boucle.
$boucles[$id_boucle]->mode_partie = "\n\t"
. '$debut_boucle = ' . $debut . ";\n "
. "\$debut_boucle = intval(\$debut_boucle);\n "
. '$fin_boucle = min(' . $fin . ", \$Numrows['$id_boucle']['total'] - 1);\n "
. '$Numrows[\'' . $id_boucle . "']['grand_total'] = \$Numrows['$id_boucle']['total'];\n "
. '$Numrows[\'' . $id_boucle . '\']["total"] = max(0,$fin_boucle - $debut_boucle + 1);'
. "\n\tif (\$debut_boucle>0"
. " AND \$debut_boucle < \$Numrows['$id_boucle']['grand_total']"
. " AND \$iter->seek(\$debut_boucle,'continue'))"
. "\n\t\t\$Numrows['$id_boucle']['compteur_boucle'] = \$debut_boucle;\n\t";
$boucles[$id_boucle]->partie = "
if (\$Numrows['$id_boucle']['compteur_boucle'] <= \$debut_boucle) continue;
if (\$Numrows['$id_boucle']['compteur_boucle']-1 > \$fin_boucle) break;";
}
/**
* Analyse un des éléments des critères {a,b} ou {a/b}
*
* Pour l'élément demandé (a ou b) retrouve la valeur de l'élément,
* et de combien il est soustrait si c'est le cas comme dans {a-3,b}
*
* @param string $idb Identifiant de la boucle
* @param array $boucles AST du squelette
* @param array $param Paramètre à analyser (soit a, soit b dans {a,b} ou {a/b})
* @return array Valeur de l'élément (peut être une expression PHP), Nombre soustrait
**/
function calculer_critere_parties_aux($idb, &$boucles, $param) {
if ($param[0]->type != 'texte') {
$a1 = calculer_liste(array($param[0]), array('id_mere' => $idb), $boucles, $boucles[$idb]->id_parent);
if (isset($param[1]->texte)) {
preg_match(',^ *(-([0-9]+))? *$,', $param[1]->texte, $m);
return array("intval($a1)", ((isset($m[2]) and $m[2]) ? $m[2] : 0));
} else {
return array("intval($a1)", 0);
}
} else {
preg_match(',^ *(([0-9]+)|n) *(- *([0-9]+)? *)?$,', $param[0]->texte, $m);
$a1 = $m[1];
if (empty($m[3])) {
return array($a1, 0);
} elseif (!empty($m[4])) {
return array($a1, $m[4]);
} else {
return array($a1, calculer_liste(array($param[1]), array(), $boucles, $boucles[$idb]->id_parent));
}
}
}
/**
* Compile les critères d'une boucle
*
* Cette fonction d'aiguillage cherche des fonctions spécifiques déclarées
* pour chaque critère demandé, dans l'ordre ci-dessous :
*
* - critere_{serveur}_{table}_{critere}, sinon avec _dist
* - critere_{serveur}_{critere}, sinon avec _dist
* - critere_{table}_{critere}, sinon avec _dist
* - critere_{critere}, sinon avec _dist
* - calculer_critere_defaut, sinon avec _dist
*
* Émet une erreur de squelette si un critère retourne une erreur.
*
* @param string $idb
* Identifiant de la boucle
* @param array $boucles
* AST du squelette
* @return string|array
* string : Chaine vide sans erreur
* array : Erreur sur un des critères
**/
function calculer_criteres($idb, &$boucles) {
$msg = '';
$boucle = $boucles[$idb];
$table = strtoupper($boucle->type_requete);
$serveur = strtolower($boucle->sql_serveur);
$defaut = charger_fonction('DEFAUT', 'calculer_critere');
// s'il y avait une erreur de syntaxe, propager cette info
if (!is_array($boucle->criteres)) {
return array();
}
foreach ($boucle->criteres as $crit) {
$critere = $crit->op;
// critere personnalise ?
if (
(!$serveur or
((!function_exists($f = "critere_" . $serveur . "_" . $table . "_" . $critere))
and (!function_exists($f = $f . "_dist"))
and (!function_exists($f = "critere_" . $serveur . "_" . $critere))
and (!function_exists($f = $f . "_dist"))
)
)
and (!function_exists($f = "critere_" . $table . "_" . $critere))
and (!function_exists($f = $f . "_dist"))
and (!function_exists($f = "critere_" . $critere))
and (!function_exists($f = $f . "_dist"))
) {
// fonction critere standard
$f = $defaut;
}
// compile le critere
$res = $f($idb, $boucles, $crit);
// Gestion centralisee des erreurs pour pouvoir propager
if (is_array($res)) {
$msg = $res;
erreur_squelette($msg, $boucle);
}
}
return $msg;
}
/**
* Désemberlificote les guillements et échappe (ou fera échapper) le contenu...
*
* Madeleine de Proust, revision MIT-1958 sqq, revision CERN-1989
* hum, c'est kwoi cette fonxion ? on va dire qu'elle desemberlificote les guillemets...
*
* http://code.spip.net/@kwote
*
* @param string $lisp Code compilé
* @param string $serveur Connecteur de bdd utilisé
* @param string $type Type d'échappement (char, int...)
* @return string Code compilé rééchappé
*/
function kwote($lisp, $serveur = '', $type = '') {
if (preg_match(_CODE_QUOTE, $lisp, $r)) {
return $r[1] . "\"" . sql_quote(str_replace(array("\\'", "\\\\"), array("'", "\\"), $r[2]), $serveur, $type) . "\"";
} else {
return "sql_quote($lisp, '$serveur', '" . str_replace("'", "\\'", $type) . "')";
}
}
/**
* Compile un critère possédant l'opérateur IN : {xx IN yy}
*
* Permet de restreindre un champ sur une liste de valeurs tel que
* {id_article IN 3,4} {id_article IN #LISTE{3,4}}
*
* Si on a une liste de valeurs dans #ENV{x}, utiliser la double etoile
* pour faire par exemple {id_article IN #ENV**{liste_articles}}
*
* @param string $idb Identifiant de la boucle
* @param array $boucles AST du squelette
* @param Critere $crit Paramètres du critère dans cette boucle
* @return void
**/
function critere_IN_dist($idb, &$boucles, $crit) {
$r = calculer_critere_infixe($idb, $boucles, $crit);
if (!$r) {
return (array('zbug_critere_inconnu', array('critere' => $crit->op . " ?")));
}
list($arg, $op, $val, $col, $where_complement) = $r;
$in = critere_IN_cas($idb, $boucles, $crit->not ? 'NOT' : ($crit->exclus ? 'exclus' : ''), $arg, $op, $val, $col);
// inserer la condition; exemple: {id_mot ?IN (66, 62, 64)}
$where = $in;
if ($crit->cond) {
$pred = calculer_argument_precedent($idb, $col, $boucles);
$where = array("'?'", $pred, $where, "''");
if ($where_complement) // condition annexe du type "AND (objet='article')"
{
$where_complement = array("'?'", $pred, $where_complement, "''");
}
}
if ($crit->exclus) {
if (!preg_match(",^L[0-9]+[.],", $arg)) {
$where = array("'NOT'", $where);
} else
// un not sur un critere de jointure se traduit comme un NOT IN avec une sous requete
// c'est une sous requete identique a la requete principale sous la forme (SELF,$select,$where) avec $select et $where qui surchargent
{
$where = array(
"'NOT'",
array(
"'IN'",
"'" . $boucles[$idb]->id_table . "." . $boucles[$idb]->primary . "'",
array("'SELF'", "'" . $boucles[$idb]->id_table . "." . $boucles[$idb]->primary . "'", $where)
)
);
}
}
$boucles[$idb]->where[] = $where;
if ($where_complement) // condition annexe du type "AND (objet='article')"
{
$boucles[$idb]->where[] = $where_complement;
}
}
// http://code.spip.net/@critere_IN_cas
function critere_IN_cas($idb, &$boucles, $crit2, $arg, $op, $val, $col) {
static $num = array();
$descr = $boucles[$idb]->descr;
$cpt = &$num[$descr['nom']][$descr['gram']][$idb];
$var = '$in' . $cpt++;
$x = "\n\t$var = array();";
foreach ($val as $k => $v) {
if (preg_match(",^(\n//.*\n)?'(.*)'$,", $v, $r)) {
// optimiser le traitement des constantes
if (is_numeric($r[2])) {
$x .= "\n\t$var" . "[]= $r[2];";
} else {
$x .= "\n\t$var" . "[]= " . sql_quote($r[2]) . ";";
}
} else {
// Pour permettre de passer des tableaux de valeurs
// on repere l'utilisation brute de #ENV**{X},
// c'est-a-dire sa traduction en ($PILE[0][X]).
// et on deballe mais en rajoutant l'anti XSS
$x .= "\n\tif (!(is_array(\$a = ($v))))\n\t\t$var" . "[]= \$a;\n\telse $var = array_merge($var, \$a);";
}
}
$boucles[$idb]->in .= $x;
// inserer le tri par defaut selon les ordres du IN ...
// avec une ecriture de type FIELD qui degrade les performances (du meme ordre qu'un regexp)
// et que l'on limite donc strictement aux cas necessaires :
// si ce n'est pas un !IN, et si il n'y a pas d'autre order dans la boucle
if (!$crit2) {
$boucles[$idb]->default_order[] = "((!sql_quote($var) OR sql_quote($var)===\"''\") ? 0 : ('FIELD($arg,' . sql_quote($var) . ')'))";
}
return "sql_in('$arg',sql_quote($var)" . ($crit2 == 'NOT' ? ",'NOT'" : "") . ")";
}
/**
* Compile le critère {where}
*
* Ajoute une contrainte sql WHERE, tout simplement pour faire le pont
* entre php et squelettes, en utilisant la syntaxe attendue par
* la propriété $where d'une Boucle.
*
* @param string $idb Identifiant de la boucle
* @param array $boucles AST du squelette
* @param Critere $crit Paramètres du critère dans cette boucle
* @return void
*/
function critere_where_dist($idb, &$boucles, $crit) {
$boucle = &$boucles[$idb];
if (isset($crit->param[0])) {
$_where = calculer_liste($crit->param[0], array(), $boucles, $boucle->id_parent);
} else {
$_where = 'spip_sanitize_from_request(@$Pile[0]["where"],"where","vide")';
}
if ($crit->cond) {
$_where = "((\$zzw = $_where) ? \$zzw : '')";
}
if ($crit->not) {
$_where = "array('NOT',$_where)";
}
$boucle->where[] = $_where;
}
/**
* Compile le critère `{tri}` permettant le tri dynamique d'un champ
*
* Le critère `{tri}` gère un champ de tri qui peut être modifié dynamiquement par la balise `#TRI`.
* Il s'utilise donc conjointement avec la balise `#TRI` dans la même boucle
* pour génerér les liens qui permettent de changer le critère de tri et le sens du tri
*
* @syntaxe `{tri [champ_par_defaut][,sens_par_defaut][,nom_variable]}`
*
* - champ_par_defaut : un champ de la table sql
* - sens_par_defaut : -1 ou inverse pour décroissant, 1 ou direct pour croissant
* peut être un tableau pour préciser des sens par défaut associés à chaque champ
* exemple : `array('titre' => 1, 'date' => -1)` pour trier par défaut
* les titre croissants et les dates décroissantes
* dans ce cas, quand un champ est utilisé pour le tri et n'est pas présent dans le tableau
* c'est la première valeur qui est utilisée
* - nom_variable : nom de la variable utilisée (par defaut `tri_{nomboucle}`)
*
* {tri titre}
* {tri titre,inverse}
* {tri titre,-1}
* {tri titre,-1,truc}
*
* Exemple d'utilisation :
*
* <B_articles>
* <p>#TRI{titre,'Trier par titre'} | #TRI{date,'Trier par date'}</p>
* <ul>
* <BOUCLE_articles(ARTICLES){tri titre}>
* <li>#TITRE - [(#DATE|affdate_jourcourt)]</li>
* </BOUCLE_articles>
* </ul>
* </B_articles>
*
* @note
* Contraitement à `{par ...}`, `{tri}` ne peut prendre qu'un seul champ,
* mais il peut être complété avec `{par ...}` pour indiquer des criteres secondaires
*
* Exemble :
* `{tri num titre}{par titre}` permet de faire un tri sur le rang (modifiable dynamiquement)
* avec un second critère sur le titre en cas d'égalité des rangs
*
* @link http://www.spip.net/5429
* @see critere_par_dist() Le critère `{par ...}`
* @see balise_TRI_dist() La balise `#TRI`
*
* @param string $idb Identifiant de la boucle
* @param array $boucles AST du squelette
* @param Critere $crit Paramètres du critère dans cette boucle
* @return void
*/
function critere_tri_dist($idb, &$boucles, $crit) {
$boucle = &$boucles[$idb];
// definition du champ par defaut
$_champ_defaut = !isset($crit->param[0][0]) ? "''"
: calculer_liste(array($crit->param[0][0]), array(), $boucles, $boucle->id_parent);
$_sens_defaut = !isset($crit->param[1][0]) ? "1"
: calculer_liste(array($crit->param[1][0]), array(), $boucles, $boucle->id_parent);
$_variable = !isset($crit->param[2][0]) ? "'$idb'"
: calculer_liste(array($crit->param[2][0]), array(), $boucles, $boucle->id_parent);
$_tri = "((\$t=(isset(\$Pile[0]['tri'.$_variable]))?\$Pile[0]['tri'.$_variable]:((strncmp($_variable,'session',7)==0 AND session_get('tri'.$_variable))?session_get('tri'.$_variable):$_champ_defaut))?tri_protege_champ(\$t):'')";
$_sens_defaut = "(is_array(\$s=$_sens_defaut)?(isset(\$s[\$st=$_tri])?\$s[\$st]:reset(\$s)):\$s)";
$_sens = "((intval(\$t=(isset(\$Pile[0]['sens'.$_variable]))?\$Pile[0]['sens'.$_variable]:((strncmp($_variable,'session',7)==0 AND session_get('sens'.$_variable))?session_get('sens'.$_variable):$_sens_defaut))==-1 OR \$t=='inverse')?-1:1)";
$boucle->modificateur['tri_champ'] = $_tri;
$boucle->modificateur['tri_sens'] = $_sens;
$boucle->modificateur['tri_nom'] = $_variable;
// faut il inserer un test sur l'existence de $tri parmi les champs de la table ?
// evite des erreurs sql, mais peut empecher des tri sur jointure ...
$boucle->hash .= "
\$senstri = '';
\$tri = $_tri;
if (\$tri){
\$senstri = $_sens;
\$senstri = (\$senstri<0)?' DESC':'';
};
";
$boucle->select[] = "\".tri_champ_select(\$tri).\"";
$boucle->order[] = "tri_champ_order(\$tri,\$command['from']).\$senstri";
}
# Criteres de comparaison
/**
* Compile un critère non déclaré explicitement
*
* Compile les critères non déclarés, ainsi que les parties de boucles
* avec les critères {0,1} ou {1/2}
*
* @param string $idb Identifiant de la boucle
* @param array $boucles AST du squelette
* @param Critere $crit Paramètres du critère dans cette boucle
* @return void
**/
function calculer_critere_DEFAUT_dist($idb, &$boucles, $crit) {
// double cas particulier {0,1} et {1/2} repere a l'analyse lexicale
if (($crit->op == ",") or ($crit->op == '/')) {
return calculer_critere_parties($idb, $boucles, $crit);
}
$r = calculer_critere_infixe($idb, $boucles, $crit);
if (!$r) {
# // on produit une erreur seulement si le critere n'a pas de '?'
# if (!$crit->cond) {
return (array('zbug_critere_inconnu', array('critere' => $crit->op)));
# }
} else {
calculer_critere_DEFAUT_args($idb, $boucles, $crit, $r);
}
}
/**
* Compile un critère non déclaré explicitement, dont on reçoit une analyse
*
* Ajoute en fonction des arguments trouvés par calculer_critere_infixe()
* les conditions WHERE à appliquer sur la boucle.
*
* @see calculer_critere_infixe()
*
* @param string $idb Identifiant de la boucle
* @param array $boucles AST du squelette
* @param Critere $crit Paramètres du critère dans cette boucle
* @param array $args Description du critère
* Cf. retour de calculer_critere_infixe()
* @return void
**/
function calculer_critere_DEFAUT_args($idb, &$boucles, $crit, $args) {
list($arg, $op, $val, $col, $where_complement) = $args;
$where = array("'$op'", "'$arg'", $val[0]);
// inserer la negation (cf !...)
if ($crit->not) {
$where = array("'NOT'", $where);
}
if ($crit->exclus) {
if (!preg_match(",^L[0-9]+[.],", $arg)) {
$where = array("'NOT'", $where);
} else
// un not sur un critere de jointure se traduit comme un NOT IN avec une sous requete
// c'est une sous requete identique a la requete principale sous la forme (SELF,$select,$where) avec $select et $where qui surchargent
{
$where = array(
"'NOT'",
array(
"'IN'",
"'" . $boucles[$idb]->id_table . "." . $boucles[$idb]->primary . "'",
array("'SELF'", "'" . $boucles[$idb]->id_table . "." . $boucles[$idb]->primary . "'", $where)
)
);
}
}
// inserer la condition (cf {lang?})
// traiter a part la date, elle est mise d'office par SPIP,
if ($crit->cond) {
$pred = calculer_argument_precedent($idb, $col, $boucles);
if ($col == "date" or $col == "date_redac") {
if ($pred == "\$Pile[0]['" . $col . "']") {
$pred = "(\$Pile[0]['{$col}_default']?'':$pred)";
}
}
if ($op == '=' and !$crit->not) {
$where = array(
"'?'",
"(is_array($pred))",
critere_IN_cas($idb, $boucles, 'COND', $arg, $op, array($pred), $col),
$where
);
}
$where = array("'?'", "!(is_array($pred)?count($pred):strlen($pred))", "''", $where);
if ($where_complement) // condition annexe du type "AND (objet='article')"
{
$where_complement = array("'?'", "!(is_array($pred)?count($pred):strlen($pred))", "''", $where_complement);
}
}
$boucles[$idb]->where[] = $where;
if ($where_complement) // condition annexe du type "AND (objet='article')"
{
$boucles[$idb]->where[] = $where_complement;
}
}
/**
* Décrit un critère non déclaré explicitement
*
* Décrit un critère non déclaré comme {id_article} {id_article>3} en
* retournant un tableau de l'analyse si la colonne (ou l'alias) existe vraiment.
*
* Ajoute au passage pour chaque colonne utilisée (alias et colonne véritable)
* un modificateur['criteres'][colonne].
*
* S'occupe de rechercher des exceptions, tel que
* - les id_parent, id_enfant, id_secteur,
* - des colonnes avec des exceptions déclarées,
* - des critères de date (jour_relatif, ...),
* - des critères sur tables jointes explicites (mots.titre),
* - des critères sur tables de jointure non explicite (id_mot sur une boucle articles...)
*
*
* @param string $idb Identifiant de la boucle
* @param array $boucles AST du squelette
* @param Critere $crit Paramètres du critère dans cette boucle
* @return array|string
* Liste si on trouve le champ :
* - string $arg
* Opérande avant l'opérateur : souvent la colonne d'application du critère, parfois un calcul
* plus complexe dans le cas des dates.
* - string $op
* L'opérateur utilisé, tel que '='
* - string[] $val
* Liste de codes PHP obtenant les valeurs des comparaisons (ex: id_article sur la boucle parente)
* Souvent (toujours ?) un tableau d'un seul élément.
* - $col_alias
* - $where_complement
*
* Chaîne vide si on ne trouve pas le champ...
**/
function calculer_critere_infixe($idb, &$boucles, $crit) {
$boucle = &$boucles[$idb];
$type = $boucle->type_requete;
$table = $boucle->id_table;
$desc = $boucle->show;
$col_vraie = null;
list($fct, $col, $op, $val, $args_sql) =
calculer_critere_infixe_ops($idb, $boucles, $crit);
$col_alias = $col;
$where_complement = false;
// Cas particulier : id_enfant => utiliser la colonne id_objet
if ($col == 'id_enfant') {
$col = $boucle->primary;
}
// Cas particulier : id_parent => verifier les exceptions de tables
if ((in_array($col, array('id_parent', 'id_secteur')) and isset($GLOBALS['exceptions_des_tables'][$table][$col]))
or (isset($GLOBALS['exceptions_des_tables'][$table][$col]) and is_string($GLOBALS['exceptions_des_tables'][$table][$col]))
) {
$col = $GLOBALS['exceptions_des_tables'][$table][$col];
} // et possibilite de gerer un critere secteur sur des tables de plugins (ie forums)
else {
if (($col == 'id_secteur') and ($critere_secteur = charger_fonction("critere_secteur_$type", "public", true))) {
$table = $critere_secteur($idb, $boucles, $val, $crit);
}
// cas id_article=xx qui se mappe en id_objet=xx AND objet=article
// sauf si exception declaree : sauter cette etape
else {
if (
!isset($GLOBALS['exceptions_des_jointures'][table_objet_sql($table)][$col])
and !isset($GLOBALS['exceptions_des_jointures'][$col])
and count(trouver_champs_decomposes($col, $desc)) > 1
) {
$e = decompose_champ_id_objet($col);
$col = array_shift($e);
$where_complement = primary_doublee($e, $table);
} // Cas particulier : expressions de date
else {
if ($c = calculer_critere_infixe_date($idb, $boucles, $col)) {
list($col, $col_vraie) = $c;
$table = '';
} // table explicitée {mots.titre}
else {
if (preg_match('/^(.*)\.(.*)$/', $col, $r)) {
list(, $table, $col) = $r;
$col_alias = $col;
$trouver_table = charger_fonction('trouver_table', 'base');
if ($desc = $trouver_table($table, $boucle->sql_serveur)
and isset($desc['field'][$col])
and $cle = array_search($desc['table'], $boucle->from)
) {
$table = $cle;
} else {
$table = trouver_jointure_champ($col, $boucle, array($table), ($crit->cond or $op != '='));
}
#$table = calculer_critere_externe_init($boucle, array($table), $col, $desc, ($crit->cond OR $op!='='), true);
if (!$table) {
return '';
}
}
// si le champ n'est pas trouvé dans la table,
// on cherche si une jointure peut l'obtenir
elseif (@!array_key_exists($col, $desc['field'])) {
// Champ joker * des iterateurs DATA qui accepte tout
if (@array_key_exists('*', $desc['field'])) {
$desc['field'][$col_vraie ? $col_vraie : $col] = ''; // on veut pas de cast INT par defaut car le type peut etre n'importe quoi dans les boucles DATA
}
else {
$r = calculer_critere_infixe_externe($boucle, $crit, $op, $desc, $col, $col_alias, $table);
if (!$r) {
return '';
}
list($col, $col_alias, $table, $where_complement, $desc) = $r;
}
}
}
}
}
}
$col_vraie = ($col_vraie ? $col_vraie : $col);
// Dans tous les cas,
// virer les guillemets eventuels autour d'un int (qui sont refuses par certains SQL)
// et passer dans sql_quote avec le type si connu
// et int sinon si la valeur est numerique
// sinon introduire le vrai type du champ si connu dans le sql_quote (ou int NOT NULL sinon)
// Ne pas utiliser intval, PHP tronquant les Bigint de SQL
if ($op == '=' or in_array($op, $GLOBALS['table_criteres_infixes'])) {
$type_cast_quote = (isset($desc['field'][$col_vraie]) ? $desc['field'][$col_vraie] : 'int NOT NULL');
// defaire le quote des int et les passer dans sql_quote avec le bon type de champ si on le connait, int sinon
// prendre en compte le debug ou la valeur arrive avec un commentaire PHP en debut
if (preg_match(",^\\A(\s*//.*?$\s*)?\"'(-?\d+)'\"\\z,ms", $val[0], $r)) {
$val[0] = $r[1] . '"' . sql_quote($r[2], $boucle->sql_serveur, $type_cast_quote) . '"';
}
// sinon expliciter les
// sql_quote(truc) en sql_quote(truc,'',type)
// sql_quote(truc,serveur) en sql_quote(truc,serveur,type)
// sql_quote(truc,serveur,'') en sql_quote(truc,serveur,type)
// sans toucher aux
// sql_quote(truc,'','varchar(10) DEFAULT \'oui\' COLLATE NOCASE')
// sql_quote(truc,'','varchar')
elseif (preg_match('/\Asql_quote[(](.*?)(,[^)]*?)?(,[^)]*(?:\(\d+\)[^)]*)?)?[)]\s*\z/ms', $val[0], $r)
// si pas deja un type
and (!isset($r[3]) or !$r[3] or !trim($r[3],", '"))
) {
$r = $r[1]
. ((isset($r[2]) and $r[2]) ? $r[2] : ",''")
. ",'" . addslashes($type_cast_quote) . "'";
$val[0] = "sql_quote($r)";
}
elseif(strpos($val[0], '@@defaultcast@@') !== false
and preg_match("/'@@defaultcast@@'\s*\)\s*\z/ms", $val[0], $r)) {
$val[0] = substr($val[0], 0, -strlen($r[0])) . "'" . addslashes($type_cast_quote) . "')";
}
}
if(strpos($val[0], '@@defaultcast@@') !== false
and preg_match("/'@@defaultcast@@'\s*\)\s*\z/ms", $val[0], $r)) {
$val[0] = substr($val[0], 0, -strlen($r[0])) . "'char')";
}
// Indicateur pour permettre aux fonctionx boucle_X de modifier
// leurs requetes par defaut, notamment le champ statut
// Ne pas confondre champs de la table principale et des jointures
if ($table === $boucle->id_table) {
$boucles[$idb]->modificateur['criteres'][$col_vraie] = true;
if ($col_alias != $col_vraie) {
$boucles[$idb]->modificateur['criteres'][$col_alias] = true;
}
}
// ajout pour le cas special d'une condition sur le champ statut:
// il faut alors interdire a la fonction de boucle
// de mettre ses propres criteres de statut
// http://www.spip.net/@statut (a documenter)
// garde pour compatibilite avec code des plugins anterieurs, mais redondant avec la ligne precedente
if ($col == 'statut') {
$boucles[$idb]->statut = true;
}
// inserer le nom de la table SQL devant le nom du champ
if ($table) {
if ($col[0] == "`") {
$arg = "$table." . substr($col, 1, -1);
} else {
$arg = "$table.$col";
}
} else {
$arg = $col;
}
// inserer la fonction SQL
if ($fct) {
$arg = "$fct($arg$args_sql)";
}
return array($arg, $op, $val, $col_alias, $where_complement);
}
/**
* Décrit un critère non déclaré explicitement, sur un champ externe à la table
*
* Décrit un critère non déclaré comme {id_article} {id_article>3} qui correspond
* à un champ non présent dans la table, et donc à retrouver par jointure si possible.
*
* @param Boucle $boucle Description de la boucle
* @param Critere $crit Paramètres du critère dans cette boucle
* @param string $op L'opérateur utilisé, tel que '='
* @param array $desc Description de la table
* @param string $col Nom de la colonne à trouver (la véritable)
* @param string $col_alias Alias de la colonne éventuel utilisé dans le critère ex: id_enfant
* @param string $table Nom de la table SQL de la boucle
* @return array|string
* Liste si jointure possible :
* - string $col
* - string $col_alias
* - string $table
* - array $where
* - array $desc
*
* Chaîne vide si on ne trouve pas le champ par jointure...
**/
function calculer_critere_infixe_externe($boucle, $crit, $op, $desc, $col, $col_alias, $table) {
$where = '';
$calculer_critere_externe = 'calculer_critere_externe_init';
// gestion par les plugins des jointures tordues
// pas automatiques mais necessaires
$table_sql = table_objet_sql($table);
if (isset($GLOBALS['exceptions_des_jointures'][$table_sql])
and is_array($GLOBALS['exceptions_des_jointures'][$table_sql])
and
(
isset($GLOBALS['exceptions_des_jointures'][$table_sql][$col])
or
isset($GLOBALS['exceptions_des_jointures'][$table_sql][''])
)
) {
$t = $GLOBALS['exceptions_des_jointures'][$table_sql];
$index = isset($t[$col])
? $t[$col] : (isset($t['']) ? $t[''] : array());
if (count($index) == 3) {
list($t, $col, $calculer_critere_externe) = $index;
} elseif (count($index) == 2) {
list($t, $col) = $t[$col];
} elseif (count($index) == 1) {
list($calculer_critere_externe) = $index;
$t = $table;
} else {
$t = '';
} // jointure non declaree. La trouver.
} elseif (isset($GLOBALS['exceptions_des_jointures'][$col])) {
list($t, $col) = $GLOBALS['exceptions_des_jointures'][$col];
} else {
$t = '';
} // jointure non declaree. La trouver.
// ici on construit le from pour fournir $col en piochant dans les jointures
// si des jointures explicites sont fournies, on cherche d'abord dans celles ci
// permet de forcer une table de lien quand il y a ambiguite
// <BOUCLE_(DOCUMENTS documents_liens){id_mot}>
// alors que <BOUCLE_(DOCUMENTS){id_mot}> produit la meme chose que <BOUCLE_(DOCUMENTS mots_liens){id_mot}>
$table = "";
if ($boucle->jointures_explicites) {
$jointures_explicites = explode(' ', $boucle->jointures_explicites);
$table = $calculer_critere_externe($boucle, $jointures_explicites, $col, $desc, ($crit->cond or $op != '='), $t);
}
// et sinon on cherche parmi toutes les jointures declarees
if (!$table) {
$table = $calculer_critere_externe($boucle, $boucle->jointures, $col, $desc, ($crit->cond or $op != '='), $t);
}
if (!$table) {
return '';
}
// il ne reste plus qu'a trouver le champ dans les from
list($nom, $desc, $cle) = trouver_champ_exterieur($col, $boucle->from, $boucle);
if (count($cle) > 1 or reset($cle) !== $col) {
$col_alias = $col; // id_article devient juste le nom d'origine
if (count($cle) > 1 and reset($cle) == 'id_objet') {
$e = decompose_champ_id_objet($col);
$col = array_shift($e);
$where = primary_doublee($e, $table);
} else {
$col = reset($cle);
}
}
return array($col, $col_alias, $table, $where, $desc);
}
/**
* Calcule une condition WHERE entre un nom du champ et une valeur
*
* Ne pas appliquer sql_quote lors de la compilation,
* car on ne connait pas le serveur SQL
*
* @todo Ce nom de fonction n'est pas très clair ?
*
* @param array $decompose Liste nom du champ, code PHP pour obtenir la valeur
* @param string $table Nom de la table
* @return string[]
* Liste de 3 éléments pour une description where du compilateur :
* - operateur (=),
* - table.champ,
* - valeur
**/
function primary_doublee($decompose, $table) {
$e1 = reset($decompose);
$e2 = "sql_quote('" . end($decompose) . "')";
return array("'='", "'$table." . $e1 . "'", $e2);
}
/**
* Champ hors table, ça ne peut être qu'une jointure.
*
* On cherche la table du champ et on regarde si elle est déjà jointe
* Si oui et qu'on y cherche un champ nouveau, pas de jointure supplementaire
* Exemple: criteres {titre_mot=...}{type_mot=...}
* Dans les 2 autres cas ==> jointure
* (Exemple: criteres {type_mot=...}{type_mot=...} donne 2 jointures
* pour selectioner ce qui a exactement ces 2 mots-cles.
*
* @param Boucle $boucle
* Description de la boucle
* @param array $joints
* Liste de jointures possibles (ex: $boucle->jointures ou $boucle->jointures_explicites)
* @param string $col
* Colonne cible de la jointure
* @param array $desc
* Description de la table
* @param bool $cond
* Flag pour savoir si le critère est conditionnel ou non
* @param bool|string $checkarrivee
* string : nom de la table jointe on veut trouver le champ.
* n'a normalement pas d'appel sans $checkarrivee.
* @return string
* Alias de la table de jointure (Lx)
* Vide sinon.
*/
function calculer_critere_externe_init(&$boucle, $joints, $col, $desc, $cond, $checkarrivee = false) {
// si on demande un truc du genre spip_mots
// avec aussi spip_mots_liens dans les jointures dispo
// et qu'on est la
// il faut privilegier la jointure directe en 2 etapes spip_mots_liens, spip_mots
if ($checkarrivee
and is_string($checkarrivee)
and $a = table_objet($checkarrivee)
and in_array($a . '_liens', $joints)
) {
if ($res = calculer_lien_externe_init($boucle, $joints, $col, $desc, $cond, $checkarrivee)) {
return $res;
}
}
foreach ($joints as $joint) {
if ($arrivee = trouver_champ_exterieur($col, array($joint), $boucle, $checkarrivee)) {
// alias de table dans le from
$t = array_search($arrivee[0], $boucle->from);
// recuperer la cle id_xx eventuellement decomposee en (id_objet,objet)
$cols = $arrivee[2];
// mais on ignore la 3eme cle si presente qui correspond alors au point de depart
if (count($cols) > 2) {
array_pop($cols);
}
if ($t) {
// la table est déjà dans le FROM, on vérifie si le champ est utilisé.
$joindre = false;
foreach ($cols as $col) {
$c = '/\b' . $t . ".$col" . '\b/';
if (trouver_champ($c, $boucle->where)) {
$joindre = true;
} else {
// mais ca peut etre dans le FIELD pour le Having
$c = "/FIELD.$t" . ".$col,/";
if (trouver_champ($c, $boucle->select)) {
$joindre = true;
}
}
}
if (!$joindre) {
return $t;
}
}
array_pop($arrivee);
if ($res = calculer_jointure($boucle, array($boucle->id_table, $desc), $arrivee, $cols, $cond, 1)) {
return $res;
}
}
}
return '';
}
/**
* Générer directement une jointure via une table de lien spip_xxx_liens
* pour un critère {id_xxx}
*
* @todo $checkarrivee doit être obligatoire ici ?
*
* @param Boucle $boucle
* Description de la boucle
* @param array $joints
* Liste de jointures possibles (ex: $boucle->jointures ou $boucle->jointures_explicites)
* @param string $col
* Colonne cible de la jointure
* @param array $desc
* Description de la table
* @param bool $cond
* Flag pour savoir si le critère est conditionnel ou non
* @param bool|string $checkarrivee
* string : nom de la table jointe on veut trouver le champ.
* n'a normalement pas d'appel sans $checkarrivee.
* @return string
* Alias de la table de jointure (Lx)
*/
function calculer_lien_externe_init(&$boucle, $joints, $col, $desc, $cond, $checkarrivee = false) {
$primary_arrivee = id_table_objet($checkarrivee);
// [FIXME] $checkarrivee peut-il arriver avec false ????
$intermediaire = trouver_champ_exterieur($primary_arrivee, $joints, $boucle, $checkarrivee . "_liens");
$arrivee = trouver_champ_exterieur($col, $joints, $boucle, $checkarrivee);
if (!$intermediaire or !$arrivee) {
return '';
}
array_pop($intermediaire); // enlever la cle en 3eme argument
array_pop($arrivee); // enlever la cle en 3eme argument
$res = fabrique_jointures($boucle,
array(
array(
$boucle->id_table,
$intermediaire,
array(id_table_objet($desc['table_objet']), 'id_objet', 'objet', $desc['type'])
),
array(reset($intermediaire), $arrivee, $primary_arrivee)
), $cond, $desc, $boucle->id_table, array($col));
return $res;
}
/**
* Recherche la présence d'un champ dans une valeur de tableau
*
* @param string $champ
* Expression régulière pour trouver un champ donné.
* Exemple : /\barticles.titre\b/
* @param array $where
* Tableau de valeurs dans lesquels chercher le champ.
* @return bool
* true si le champ est trouvé quelque part dans $where
* false sinon.
**/
function trouver_champ($champ, $where) {
if (!is_array($where)) {
return preg_match($champ, $where);
} else {
foreach ($where as $clause) {
if (trouver_champ($champ, $clause)) {
return true;
}
}
return false;
}
}
/**
* Détermine l'operateur et les opérandes d'un critère non déclaré
*
* Lorsque l'opérateur n'est pas explicite comme sur {id_article>0} c'est
* l'opérateur '=' qui est utilisé.
*
* Traite les cas particuliers id_parent, id_enfant, date, lang
*
* @param string $idb Identifiant de la boucle
* @param array $boucles AST du squelette
* @param Critere $crit Paramètres du critère dans cette boucle
* @return array
* Liste :
* - string $fct Nom d'une fonction SQL sur le champ ou vide (ex: SUM)
* - string $col Nom de la colonne SQL utilisée
* - string $op Opérateur
* - string[] $val
* Liste de codes PHP obtenant les valeurs des comparaisons (ex: id_article sur la boucle parente)
* Souvent un tableau d'un seul élément.
* - string $args_sql Suite des arguments du critère. ?
**/
function calculer_critere_infixe_ops($idb, &$boucles, $crit) {
// cas d'une valeur comparee a elle-meme ou son referent
if (count($crit->param) == 0) {
$op = '=';
$col = $val = $crit->op;
if (preg_match('/^(.*)\.(.*)$/', $col, $r)) {
$val = $r[2];
}
// Cas special {lang} : aller chercher $GLOBALS['spip_lang']
if ($val == 'lang') {
$val = array(kwote('$GLOBALS[\'spip_lang\']'));
} else {
$defaut = null;
if ($val == 'id_parent') {
// Si id_parent, comparer l'id_parent avec l'id_objet
// de la boucle superieure.... faudrait verifier qu'il existe
// pour eviter l'erreur SQL
$val = $boucles[$idb]->primary;
// mais si pas de boucle superieure, prendre id_parent dans l'env
$defaut = "@\$Pile[0]['id_parent']";
} elseif ($val == 'id_enfant') {
// Si id_enfant, comparer l'id_objet avec l'id_parent
// de la boucle superieure
$val = 'id_parent';
} elseif ($crit->cond and ($col == "date" or $col == "date_redac")) {
// un critere conditionnel sur date est traite a part
// car la date est mise d'office par SPIP,
$defaut = "(\$Pile[0]['{$col}_default']?'':\$Pile[0]['" . $col . "'])";
}
$val = calculer_argument_precedent($idb, $val, $boucles, $defaut);
$val = array(kwote($val));
}
} else {
// comparaison explicite
// le phraseur impose que le premier param soit du texte
$params = $crit->param;
$op = $crit->op;
if ($op == '==') {
$op = 'REGEXP';
}
$col = array_shift($params);
$col = $col[0]->texte;
$val = array();
$desc = array('id_mere' => $idb);
$parent = $boucles[$idb]->id_parent;
// Dans le cas {x=='#DATE'} etc, defaire le travail du phraseur,
// celui ne sachant pas ce qu'est un critere infixe
// et a fortiori son 2e operande qu'entoure " ou '
if (count($params) == 1
and count($params[0]) == 3
and $params[0][0]->type == 'texte'
and $params[0][2]->type == 'texte'
and ($p = $params[0][0]->texte) == $params[0][2]->texte
and (($p == "'") or ($p == '"'))
and $params[0][1]->type == 'champ'
) {
$val[] = "$p\\$p#" . $params[0][1]->nom_champ . "\\$p$p";
} else {
foreach ((($op != 'IN') ? $params : calculer_vieux_in($params)) as $p) {
$a = calculer_liste($p, $desc, $boucles, $parent);
if (strcasecmp($op, 'IN') == 0) {
$val[] = $a;
} else {
$val[] = kwote($a, $boucles[$idb]->sql_serveur, '@@defaultcast@@');
} // toujours quoter en char ici
}
}
}
$fct = $args_sql = '';
// fonction SQL ?
// chercher FONCTION(champ) tel que CONCAT(titre,descriptif)
if (preg_match('/^(.*)' . SQL_ARGS . '$/', $col, $m)) {
$fct = $m[1];
preg_match('/^\(([^,]*)(.*)\)$/', $m[2], $a);
$col = $a[1];
if (preg_match('/^(\S*)(\s+AS\s+.*)$/i', $col, $m)) {
$col = $m[1];
$args_sql = $m[2];
}
$args_sql .= $a[2];
}
return array($fct, $col, $op, $val, $args_sql);
}
// compatibilite ancienne version
// http://code.spip.net/@calculer_vieux_in
function calculer_vieux_in($params) {
$deb = $params[0][0];
$k = count($params) - 1;
$last = $params[$k];
$j = count($last) - 1;
$last = $last[$j];
$n = isset($last->texte) ? strlen($last->texte) : 0;
if (!((isset($deb->texte[0]) and $deb->texte[0] == '(')
&& (isset($last->texte[$n - 1]) and $last->texte[$n - 1] == ')'))
) {
return $params;
}
$params[0][0]->texte = substr($deb->texte, 1);
// attention, on peut avoir k=0,j=0 ==> recalculer
$last = $params[$k][$j];
$n = strlen($last->texte);
$params[$k][$j]->texte = substr($last->texte, 0, $n - 1);
$newp = array();
foreach ($params as $v) {
if ($v[0]->type != 'texte') {
$newp[] = $v;
} else {
foreach (explode(',', $v[0]->texte) as $x) {
$t = new Texte;
$t->texte = $x;
$newp[] = array($t);
}
}
}
return $newp;
}
/**
* Calcule les cas particuliers de critères de date
*
* Lorsque la colonne correspond à un critère de date, tel que
* jour, jour_relatif, jour_x, age, age_relatif, age_x...
*
* @param string $idb Identifiant de la boucle
* @param array $boucles AST du squelette
* @param string $col Nom du champ demandé
* @return string|array
* chaine vide si ne correspond pas à une date,
* sinon liste
* - expression SQL de calcul de la date,
* - nom de la colonne de date (si le calcul n'est pas relatif)
**/
function calculer_critere_infixe_date($idb, &$boucles, $col) {
if (!preg_match(",^((age|jour|mois|annee)_relatif|date|mois|annee|jour|heure|age)(_[a-z_]+)?$,", $col, $regs)) {
return '';
}
$boucle = $boucles[$idb];
$table = $boucle->show;
// si c'est une colonne de la table, ne rien faire
if (isset($table['field'][$col])) {
return '';
}
if (!$table['date'] && !isset($GLOBALS['table_date'][$table['id_table']])) {
return '';
}
$pred = $date_orig = isset($GLOBALS['table_date'][$table['id_table']]) ? $GLOBALS['table_date'][$table['id_table']] : $table['date'];
$col = $regs[1];
if (isset($regs[3]) and $suite = $regs[3]) {
# Recherche de l'existence du champ date_xxxx,
# si oui choisir ce champ, sinon choisir xxxx
if (isset($table['field']["date$suite"])) {
$date_orig = 'date' . $suite;
} else {
$date_orig = substr($suite, 1);
}
$pred = $date_orig;
} else {
if (isset($regs[2]) and $rel = $regs[2]) {
$pred = 'date';
}
}
$date_compare = "\"' . normaliser_date(" .
calculer_argument_precedent($idb, $pred, $boucles) .
") . '\"";
$col_vraie = $date_orig;
$date_orig = $boucle->id_table . '.' . $date_orig;
switch ($col) {
case 'date':
$col = $date_orig;
break;
case 'jour':
$col = "DAYOFMONTH($date_orig)";
break;
case 'mois':
$col = "MONTH($date_orig)";
break;
case 'annee':
$col = "YEAR($date_orig)";
break;
case 'heure':
$col = "DATE_FORMAT($date_orig, \\'%H:%i\\')";
break;
case 'age':
$col = calculer_param_date("NOW()", $date_orig);
$col_vraie = "";// comparer a un int (par defaut)
break;
case 'age_relatif':
$col = calculer_param_date($date_compare, $date_orig);
$col_vraie = "";// comparer a un int (par defaut)
break;
case 'jour_relatif':
$col = "(TO_DAYS(" . $date_compare . ")-TO_DAYS(" . $date_orig . "))";
$col_vraie = "";// comparer a un int (par defaut)
break;
case 'mois_relatif':
$col = "MONTH(" . $date_compare . ")-MONTH(" .
$date_orig . ")+12*(YEAR(" . $date_compare .
")-YEAR(" . $date_orig . "))";
$col_vraie = "";// comparer a un int (par defaut)
break;
case 'annee_relatif':
$col = "YEAR(" . $date_compare . ")-YEAR(" .
$date_orig . ")";
$col_vraie = "";// comparer a un int (par defaut)
break;
}
return array($col, $col_vraie);
}
/**
* Calcule l'expression SQL permettant de trouver un nombre de jours écoulés.
*
* Le calcul SQL retournera un nombre de jours écoulés entre la date comparée
* et la colonne SQL indiquée
*
* @param string $date_compare
* Code PHP permettant d'obtenir le timestamp référent.
* C'est à partir de lui que l'on compte les jours
* @param string $date_orig
* Nom de la colonne SQL qui possède la date
* @return string
* Expression SQL calculant le nombre de jours écoulé entre une valeur
* de colonne SQL et une date.
**/
function calculer_param_date($date_compare, $date_orig) {
if (preg_match(",'\" *\.(.*)\. *\"',", $date_compare, $r)) {
$init = "'\" . (\$x = $r[1]) . \"'";
$date_compare = '\'$x\'';
} else {
$init = $date_compare;
}
return
// optimisation : mais prevoir le support SQLite avant
"TIMESTAMPDIFF(HOUR,$date_orig,$init)/24";
}
/**
* Compile le critère {source} d'une boucle DATA
*
* Permet de déclarer le mode d'obtention des données dans une boucle
* DATA (premier argument) et les données (la suite).
*
* @example
* (DATA){source mode, "xxxxxx", arg, arg, arg}
* (DATA){source tableau, #LISTE{un,deux,trois}}
*
* @param string $idb Identifiant de la boucle
* @param array $boucles AST du squelette
* @param Critere $crit Paramètres du critère dans cette boucle
*/
function critere_DATA_source_dist($idb, &$boucles, $crit) {
$boucle = &$boucles[$idb];
$args = array();
foreach ($crit->param as &$param) {
array_push($args,
calculer_liste($param, array(), $boucles, $boucles[$idb]->id_parent));
}
$boucle->hash .= '
$command[\'sourcemode\'] = ' . array_shift($args) . ";\n";
$boucle->hash .= '
$command[\'source\'] = array(' . join(', ', $args) . ");\n";
}
/**
* Compile le critère {datasource} d'une boucle DATA
*
* Permet de déclarer le mode d'obtention des données dans une boucle DATA
*
* @deprecated Utiliser directement le critère {source}
*
* @param string $idb Identifiant de la boucle
* @param array $boucles AST du squelette
* @param Critere $crit Paramètres du critère dans cette boucle
*/
function critere_DATA_datasource_dist($idb, &$boucles, $crit) {
$boucle = &$boucles[$idb];
$boucle->hash .= '
$command[\'source\'] = array(' . calculer_liste($crit->param[0], array(), $boucles, $boucles[$idb]->id_parent) . ');
$command[\'sourcemode\'] = ' . calculer_liste($crit->param[1], array(), $boucles, $boucles[$idb]->id_parent) . ';';
}
/**
* Compile le critère {datacache} d'une boucle DATA
*
* Permet de transmettre une durée de cache (time to live) utilisée
* pour certaines sources d'obtention des données (par exemple RSS),
* indiquant alors au bout de combien de temps la donnée est à réobtenir.
*
* La durée par défaut est 1 journée.
*
* @param string $idb Identifiant de la boucle
* @param array $boucles AST du squelette
* @param Critere $crit Paramètres du critère dans cette boucle
*/
function critere_DATA_datacache_dist($idb, &$boucles, $crit) {
$boucle = &$boucles[$idb];
$boucle->hash .= '
$command[\'datacache\'] = ' . calculer_liste($crit->param[0], array(), $boucles, $boucles[$idb]->id_parent) . ';';
}
/**
* Compile le critère {args} d'une boucle PHP
*
* Permet de passer des arguments à un iterateur non-spip
* (PHP:xxxIterator){args argument1, argument2, argument3}
*
* @param string $idb Identifiant de la boucle
* @param array $boucles AST du squelette
* @param Critere $crit Paramètres du critère dans cette boucle
*/
function critere_php_args_dist($idb, &$boucles, $crit) {
$boucle = &$boucles[$idb];
$boucle->hash .= '$command[\'args\']=array();';
foreach ($crit->param as $param) {
$boucle->hash .= '
$command[\'args\'][] = ' . calculer_liste($param, array(), $boucles, $boucles[$idb]->id_parent) . ';';
}
}
/**
* Compile le critère {liste} d'une boucle DATA
*
* Passe une liste de données à l'itérateur DATA
*
* @example
* (DATA){liste X1, X2, X3}
* équivalent à (DATA){source tableau,#LISTE{X1, X2, X3}}
*
* @param string $idb Identifiant de la boucle
* @param array $boucles AST du squelette
* @param Critere $crit Paramètres du critère dans cette boucle
*/
function critere_DATA_liste_dist($idb, &$boucles, $crit) {
$boucle = &$boucles[$idb];
$boucle->hash .= "\n\t" . '$command[\'liste\'] = array();' . "\n";
foreach ($crit->param as $param) {
$boucle->hash .= "\t" . '$command[\'liste\'][] = ' . calculer_liste($param, array(), $boucles,
$boucles[$idb]->id_parent) . ";\n";
}
}
/**
* Compile le critère {enum} d'une boucle DATA
*
* Passe les valeurs de début et de fin d'une énumération, qui seront
* vues comme une liste d'autant d'éléments à parcourir pour aller du
* début à la fin.
*
* Cela utilisera la fonction range() de PHP.
*
* @example
* (DATA){enum Xdebut, Xfin}
* (DATA){enum a,z}
* (DATA){enum z,a}
* (DATA){enum 1.0,9.2}
*
* @link http://php.net/manual/fr/function.range.php
*
* @param string $idb Identifiant de la boucle
* @param array $boucles AST du squelette
* @param Critere $crit Paramètres du critère dans cette boucle
*/
function critere_DATA_enum_dist($idb, &$boucles, $crit) {
$boucle = &$boucles[$idb];
$boucle->hash .= "\n\t" . '$command[\'enum\'] = array();' . "\n";
foreach ($crit->param as $param) {
$boucle->hash .= "\t" . '$command[\'enum\'][] = ' . calculer_liste($param, array(), $boucles,
$boucles[$idb]->id_parent) . ";\n";
}
}
/**
* Compile le critère {datapath} d'une boucle DATA
*
* Extrait un chemin d'un tableau de données
*
* (DATA){datapath query.results}
*
* @param string $idb Identifiant de la boucle
* @param array $boucles AST du squelette
* @param Critere $crit Paramètres du critère dans cette boucle
*/
function critere_DATA_datapath_dist($idb, &$boucles, $crit) {
$boucle = &$boucles[$idb];
foreach ($crit->param as $param) {
$boucle->hash .= '
$command[\'datapath\'][] = ' . calculer_liste($param, array(), $boucles, $boucles[$idb]->id_parent) . ';';
}
}
/**
* Compile le critère {si}
*
* Le critère {si condition} est applicable à toutes les boucles et conditionne
* l'exécution de la boucle au résultat de la condition. La partie alternative
* de la boucle est alors affichée si une condition n'est pas remplie (comme
* lorsque la boucle ne ramène pas de résultat).
* La différence étant que si la boucle devait réaliser une requête SQL
* (par exemple une boucle ARTICLES), celle ci n'est pas réalisée si la
* condition n'est pas remplie.
*
* Les valeurs de la condition sont forcément extérieures à cette boucle
* (sinon il faudrait l'exécuter pour connaître le résultat, qui doit tester
* si on exécute la boucle !)
*
* Si plusieurs critères {si} sont présents, ils sont cumulés :
* si une seule des conditions n'est pas vérifiée, la boucle n'est pas exécutée.
*
* @example
* {si #ENV{exec}|=={article}}
* {si (#_contenu:GRAND_TOTAL|>{10})}
* {si #AUTORISER{voir,articles}}
*
* @param string $idb Identifiant de la boucle
* @param array $boucles AST du squelette
* @param Critere $crit Paramètres du critère dans cette boucle
*/
function critere_si_dist($idb, &$boucles, $crit) {
$boucle = &$boucles[$idb];
// il faut initialiser 1 fois le tableau a chaque appel de la boucle
// (par exemple lorsque notre boucle est appelee dans une autre boucle)
// mais ne pas l'initialiser n fois si il y a n criteres {si } dans la boucle !
$boucle->hash .= "\n\tif (!isset(\$si_init)) { \$command['si'] = array(); \$si_init = true; }\n";
if ($crit->param) {
foreach ($crit->param as $param) {
$boucle->hash .= "\t\$command['si'][] = "
. calculer_liste($param, array(), $boucles, $boucles[$idb]->id_parent) . ";\n";
}
// interdire {si 0} aussi !
} else {
$boucle->hash .= '$command[\'si\'][] = 0;';
}
}
/**
* Compile le critère {tableau} d'une boucle POUR
*
* {tableau #XX} pour compatibilite ascendante boucle POUR
* ... préférer la notation (DATA){source tableau,#XX}
*
* @deprecated Utiliser une boucle (DATA){source tableau,#XX}
*
* @param string $idb Identifiant de la boucle
* @param array $boucles AST du squelette
* @param Critere $crit Paramètres du critère dans cette boucle
*/
function critere_POUR_tableau_dist($idb, &$boucles, $crit) {
$boucle = &$boucles[$idb];
$boucle->hash .= '
$command[\'source\'] = array(' . calculer_liste($crit->param[0], array(), $boucles, $boucles[$idb]->id_parent) . ');
$command[\'sourcemode\'] = \'table\';';
}
/**
* Compile le critère {noeud}
*
* Trouver tous les objets qui ont des enfants (les noeuds de l'arbre)
* {noeud}
* {!noeud} retourne les feuilles
*
* @global array $exceptions_des_tables
*
* @param string $idb Identifiant de la boucle
* @param array $boucles AST du squelette
* @param Critere $crit Paramètres du critère dans cette boucle
*/
function critere_noeud_dist($idb, &$boucles, $crit) {
$not = $crit->not;
$boucle = &$boucles[$idb];
$primary = $boucle->primary;
if (!$primary or strpos($primary, ',')) {
erreur_squelette(_T('zbug_doublon_sur_table_sans_cle_primaire'), $boucle);
return;
}
$table = $boucle->type_requete;
$table_sql = table_objet_sql(objet_type($table));
$id_parent = isset($GLOBALS['exceptions_des_tables'][$boucle->id_table]['id_parent']) ?
$GLOBALS['exceptions_des_tables'][$boucle->id_table]['id_parent'] :
'id_parent';
$in = "IN";
$where = array("'IN'", "'$boucle->id_table." . "$primary'", "'('.sql_get_select('$id_parent', '$table_sql').')'");
if ($not) {
$where = array("'NOT'", $where);
}
$boucle->where[] = $where;
}
/**
* Compile le critère {feuille}
*
* Trouver tous les objets qui n'ont pas d'enfants (les feuilles de l'arbre)
* {feuille}
* {!feuille} retourne les noeuds
*
* @global array $exceptions_des_tables
* @param string $idb Identifiant de la boucle
* @param array $boucles AST du squelette
* @param Critere $crit Paramètres du critère dans cette boucle
*/
function critere_feuille_dist($idb, &$boucles, $crit) {
$not = $crit->not;
$crit->not = $not ? false : true;
critere_noeud_dist($idb, $boucles, $crit);
$crit->not = $not;
}