Dépôt officiel du core SPIP * Copie possible par svn sur svn://trac.rezo.net/spip * Les svn:externals sont présent dans https://git.spip.net/SPIP/[nom du plugin dist]
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2936 lines
100 KiB

<?php
18 years ago
/***************************************************************************\
* SPIP, Systeme de publication pour l'internet *
* *
* Copyright (c) 2001-2019 *
18 years ago
* 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. *
\***************************************************************************/
11 years ago
/**
* Définition des {criteres} d'une boucle
*
* @package SPIP\Core\Compilateur\Criteres
**/
if (!defined('_ECRIRE_INC_VERSION')) {
return;
}
11 years ago
/**
* 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)? *'(.*)' *$,");
11 years ago
/**
* 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) {
18 years ago
$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);
}
11 years ago
/**
* Compile le critère {exclus}
*
* Exclut du résultat l’élément dans lequel on se trouve déjà
*
11 years ago
* @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) {
18 years ago
$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);
}
11 years ago
/**
* 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.
11 years ago
*
* Il est possible de spécifier un nom au doublon tel que {doublons sommaire}
*
11 years ago
* @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];
18 years ago
$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 l’URL 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
10 years ago
* @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) {
18 years ago
$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
10 years ago
*
* @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) {
$boucle = &$boucles[$idb];
$not = ($crit->not ? 'NOT' : '');
$serveur = $boucle->sql_serveur;
$c = "sql_in('" .
$boucle->id_table . '.' . $boucle->primary
. "', lister_objets_avec_logos('" . $boucle->primary . "'), '$not', '$serveur')";
$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 . ' ?')));
}