3 /***************************************************************************\
4 * SPIP, Systeme de publication pour l'internet *
6 * Copyright (c) 2001-2019 *
7 * Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James *
9 * Ce programme est un logiciel libre distribue sous licence GNU/GPL. *
10 * Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne. *
11 \***************************************************************************/
14 * Définition des {criteres} d'une boucle
16 * @package SPIP\Core\Compilateur\Criteres
19 if (!defined('_ECRIRE_INC_VERSION')) {
24 * Une Regexp repérant une chaine produite par le compilateur,
25 * souvent utilisée pour faire de la concaténation lors de la compilation
26 * plutôt qu'à l'exécution, i.e. pour remplacer 'x'.'y' par 'xy'
28 define('_CODE_QUOTE', ",^(\n//[^\n]*\n)? *'(.*)' *$,");
32 * Compile le critère {racine}
34 * Ce critère sélectionne les éléments à la racine d'une hiérarchie,
35 * c'est à dire ayant id_parent=0
37 * @link http://www.spip.net/@racine
39 * @param string $idb Identifiant de la boucle
40 * @param array $boucles AST du squelette
41 * @param Critere $crit Paramètres du critère dans cette boucle
44 function critere_racine_dist($idb, &$boucles, $crit) {
47 $boucle = &$boucles[$idb];
48 $id_parent = isset($GLOBALS['exceptions_des_tables'][$boucle->id_table
]['id_parent']) ?
49 $GLOBALS['exceptions_des_tables'][$boucle->id_table
]['id_parent'] :
52 $c = array("'='", "'$boucle->id_table." . "$id_parent'", 0);
53 $boucle->where
[] = ($crit->not ?
array("'NOT'", $c) : $c);
58 * Compile le critère {exclus}
60 * Exclut du résultat l’élément dans lequel on se trouve déjà
62 * @link http://www.spip.net/@exclus
64 * @param string $idb Identifiant de la boucle
65 * @param array $boucles AST du squelette
66 * @param Critere $crit Paramètres du critère dans cette boucle
69 function critere_exclus_dist($idb, &$boucles, $crit) {
71 $boucle = &$boucles[$idb];
72 $id = $boucle->primary
;
75 return (array('zbug_critere_inconnu', array('critere' => $not . $crit->op
)));
77 $arg = kwote(calculer_argument_precedent($idb, $id, $boucles));
78 $boucle->where
[] = array("'!='", "'$boucle->id_table." . "$id'", $arg);
83 * Compile le critère {doublons} ou {unique}
85 * Ce critères enlève de la boucle les éléments déjà sauvegardés
86 * dans un précédent critère {doublon} sur une boucle de même table.
88 * Il est possible de spécifier un nom au doublon tel que {doublons sommaire}
90 * @link http://www.spip.net/@doublons
92 * @param string $idb Identifiant de la boucle
93 * @param array $boucles AST du squelette
94 * @param Critere $crit Paramètres du critère dans cette boucle
97 function critere_doublons_dist($idb, &$boucles, $crit) {
98 $boucle = &$boucles[$idb];
99 $primary = $boucle->primary
;
101 // la table nécessite une clé primaire, non composée
102 if (!$primary or strpos($primary, ',')) {
103 return (array('zbug_doublon_sur_table_sans_cle_primaire'));
106 $not = ($crit->not ?
'' : 'NOT');
108 // le doublon s'applique sur un type de boucle (article)
109 $nom = "'" . $boucle->type_requete
. "'";
111 // compléter le nom avec un nom précisé {doublons nom}
112 // on obtient $nom = "'article' . 'nom'"
113 if (isset($crit->param
[0])) {
114 $nom .= "." . calculer_liste($crit->param
[0], array(), $boucles, $boucles[$idb]->id_parent
);
117 // code qui déclarera l'index du stockage de nos doublons (pour éviter une notice PHP)
118 $init_comment = "\n\n\t// Initialise le(s) critère(s) doublons\n";
119 $init_code = "\tif (!isset(\$doublons[\$d = $nom])) { \$doublons[\$d] = ''; }\n";
121 // on crée un sql_in avec la clé primaire de la table
122 // et la collection des doublons déjà emmagasinés dans le tableau
123 // $doublons et son index, ici $nom
125 // debut du code "sql_in('articles.id_article', "
126 $debut_in = "sql_in('" . $boucle->id_table
. '.' . $primary . "', ";
127 // lecture des données du doublon "$doublons[$doublon_index[] = "
128 // Attention : boucle->doublons désigne une variable qu'on affecte
129 $debut_doub = '$doublons[' . (!$not ?
'' : ($boucle->doublons
. "[]= "));
131 // le debut complet du code des doublons
132 $debut_doub = $debut_in . $debut_doub;
134 // nom du doublon "('article' . 'nom')]"
135 $fin_doub = "($nom)]";
137 // si on trouve un autre critère doublon,
138 // on fusionne pour avoir un seul IN, et on s'en va !
139 foreach ($boucle->where
as $k => $w) {
140 if (strpos($w[0], $debut_doub) === 0) {
141 // fusionner le sql_in (du where)
142 $boucle->where
[$k][0] = $debut_doub . $fin_doub . ' . ' . substr($w[0], strlen($debut_in));
143 // fusionner l'initialisation (du hash) pour faire plus joli
144 $x = strpos($boucle->hash
, $init_comment);
145 $len = strlen($init_comment);
147 substr($boucle->hash
, 0, $x +
$len) . $init_code . substr($boucle->hash
, $x +
$len);
153 // mettre l'ensemble dans un tableau pour que ce ne soit pas vu comme une constante
154 $boucle->where
[] = array($debut_doub . $fin_doub . ", '" . $not . "')");
156 // déclarer le doublon s'il n'existe pas encore
157 $boucle->hash
.= $init_comment . $init_code;
160 # la ligne suivante avait l'intention d'eviter une collecte deja faite
161 # mais elle fait planter une boucle a 2 critere doublons:
162 # {!doublons A}{doublons B}
163 # (de http://article.gmane.org/gmane.comp.web.spip.devel/31034)
164 # if ($crit->not) $boucle->doublons = "";
169 * Compile le critère {lang_select}
171 * Permet de restreindre ou non une boucle en affichant uniquement
172 * les éléments dans la langue en cours. Certaines boucles
173 * tel que articles et rubriques restreignent par défaut sur la langue
176 * Sans définir de valeur au critère, celui-ci utilise 'oui' comme
179 * @param string $idb Identifiant de la boucle
180 * @param array $boucles AST du squelette
181 * @param Critere $crit Paramètres du critère dans cette boucle
184 function critere_lang_select_dist($idb, &$boucles, $crit) {
185 if (!isset($crit->param
[1][0]) or !($param = $crit->param
[1][0]->texte
)) {
189 $param = ($param == 'oui') ?
'non' : 'oui';
191 $boucle = &$boucles[$idb];
192 $boucle->lang_select
= $param;
197 * Compile le critère {debut_xxx}
199 * Limite le nombre d'éléments affichés.
201 * Ce critère permet de faire commencer la limitation des résultats
202 * par une variable passée dans l’URL et commençant par 'debut_' tel que
203 * {debut_page,10}. Le second paramètre est le nombre de résultats à
206 * Note : il est plus simple d'utiliser le critère pagination.
208 * @param string $idb Identifiant de la boucle
209 * @param array $boucles AST du squelette
210 * @param Critere $crit Paramètres du critère dans cette boucle
213 function critere_debut_dist($idb, &$boucles, $crit) {
214 list($un, $deux) = $crit->param
;
216 $deux = $deux[0]->texte
;
218 $boucles[$idb]->limit
= 'intval($Pile[0]["debut' .
224 calculer_critere_DEFAUT_dist($idb, $boucles, $crit);
230 * Compile le critère `pagination` qui demande à paginer une boucle.
232 * Demande à paginer la boucle pour n'afficher qu'une partie des résultats,
233 * et gère l'affichage de la partie de page demandée par debut_xx dans
234 * dans l'environnement du squelette.
236 * Le premier paramètre indique le nombre d'éléments par page, le second,
237 * rarement utilisé permet de définir le nom de la variable désignant la
238 * page demandée (`debut_xx`), qui par défaut utilise l'identifiant de la boucle.
241 * @see balise_PAGINATION_dist()
242 * @link http://www.spip.net/3367 Le système de pagination
243 * @link http://www.spip.net/4867 Le critère pagination
248 * {pagination #ENV{pages,5}} etc
249 * {pagination 20 #ENV{truc,chose}} pour utiliser la variable debut_#ENV{truc,chose}
252 * @param string $idb Identifiant de la boucle
253 * @param array $boucles AST du squelette
254 * @param Critere $crit Paramètres du critère dans cette boucle
257 function critere_pagination_dist($idb, &$boucles, $crit) {
259 $boucle = &$boucles[$idb];
260 // definition de la taille de la page
261 $pas = !isset($crit->param
[0][0]) ?
"''"
262 : calculer_liste(array($crit->param
[0][0]), array(), $boucles, $boucle->id_parent
);
264 if (!preg_match(_CODE_QUOTE
, $pas, $r)) {
265 $pas = "((\$a = intval($pas)) ? \$a : 10)";
268 $pas = strval($r ?
$r : 10);
271 // Calcul du nommage de la pagination si il existe.
272 // La nouvelle syntaxe {pagination 20, nom} est prise en compte et privilégiée mais on reste
273 // compatible avec l'ancienne car certains cas fonctionnent correctement
275 // Calcul d'un nommage spécifique de la pagination si précisé.
276 // Syntaxe {pagination 20, nom}
277 if (isset($crit->param
[0][1])) {
278 $type = calculer_liste(array($crit->param
[0][1]), array(), $boucles, $boucle->id_parent
);
279 } // Ancienne syntaxe {pagination 20 nom} pour compatibilité
280 elseif (isset($crit->param
[1][0])) {
281 $type = calculer_liste(array($crit->param
[1][0]), array(), $boucles, $boucle->id_parent
);
284 $debut = ($type[0] !== "'") ?
"'debut'.$type" : ("'debut" . substr($type, 1));
285 $boucle->modificateur
['debut_nom'] = $type;
287 // tester si le numero de page demande est de la forme '@yyy'
288 'isset($Pile[0][' . $debut . ']) ? $Pile[0][' . $debut . '] : _request(' . $debut . ");\n"
289 . "\tif(substr(\$debut_boucle,0,1)=='@'){\n"
290 . "\t\t" . '$debut_boucle = $Pile[0][' . $debut . '] = quete_debut_pagination(\'' . $boucle->primary
. '\',$Pile[0][\'@' . $boucle->primary
. '\'] = substr($debut_boucle,1),' . $pas . ',$iter);' . "\n"
291 . "\t\t" . '$iter->seek(0);' . "\n"
293 . "\t" . '$debut_boucle = intval($debut_boucle)';
296 $command[\'pagination\'] = array((isset($Pile[0][' . $debut . ']) ? $Pile[0][' . $debut . '] : null), ' . $pas . ');';
298 $boucle->total_parties
= $pas;
299 calculer_parties($boucles, $idb, $partie, 'p+');
300 // ajouter la cle primaire dans le select pour pouvoir gerer la pagination referencee par @id
301 // sauf si pas de primaire, ou si primaire composee
302 // dans ce cas, on ne sait pas gerer une pagination indirecte
303 $t = $boucle->id_table
. '.' . $boucle->primary
;
305 and !preg_match('/[,\s]/', $boucle->primary
)
306 and !in_array($t, $boucle->select
)
308 $boucle->select
[] = $t;
314 * Compile le critère `recherche` qui permet de sélectionner des résultats
317 * Le texte cherché est pris dans le premier paramètre `{recherche xx}`
318 * ou à défaut dans la clé `recherche` de l'environnement du squelette.
321 * @link http://www.spip.net/3878
322 * @see inc_prepare_recherche_dist()
324 * @param string $idb Identifiant de la boucle
325 * @param array $boucles AST du squelette
326 * @param Critere $crit Paramètres du critère dans cette boucle
329 function critere_recherche_dist($idb, &$boucles, $crit) {
331 $boucle = &$boucles[$idb];
333 if (!$boucle->primary
or strpos($boucle->primary
, ',')) {
334 erreur_squelette(_T('zbug_critere_sur_table_sans_cle_primaire', array('critere' => 'recherche')), $boucle);
339 if (isset($crit->param
[0])) {
340 $quoi = calculer_liste($crit->param
[0], array(), $boucles, $boucles[$idb]->id_parent
);
342 $quoi = '(isset($Pile[0]["recherche"])?$Pile[0]["recherche"]:(isset($GLOBALS["recherche"])?$GLOBALS["recherche"]:""))';
345 $_modificateur = var_export($boucle->modificateur
, true);
349 if (!strlen(' . $quoi . ')){
350 list($rech_select, $rech_where) = array("0 as points","");
353 $prepare_recherche = charger_fonction(\'prepare_recherche\', \'inc\');
354 list($rech_select, $rech_where) = $prepare_recherche(' . $quoi . ', "' . $boucle->id_table
. '", "' . $crit->cond
. '","' . $boucle->sql_serveur
. '",' . $_modificateur . ',"' . $boucle->primary
. '");
359 $t = $boucle->id_table
. '.' . $boucle->primary
;
360 if (!in_array($t, $boucles[$idb]->select
)) {
361 $boucle->select
[] = $t;
362 } # pour postgres, neuneu ici
363 // jointure uniquement sur le serveur principal
364 // (on ne peut joindre une table d'un serveur distant avec la table des resultats du serveur principal)
365 if (!$boucle->sql_serveur
) {
366 $boucle->join
['resultats'] = array("'" . $boucle->id_table
. "'", "'id'", "'" . $boucle->primary
. "'");
367 $boucle->from
['resultats'] = 'spip_resultats';
369 $boucle->select
[] = '$rech_select';
370 //$boucle->where[]= "\$rech_where?'resultats.id=".$boucle->id_table.".".$boucle->primary."':''";
372 // et la recherche trouve
373 $boucle->where
[] = '$rech_where?$rech_where:\'\'';
377 * Compile le critère `traduction`
379 * Sélectionne toutes les traductions de l'élément courant (la boucle englobante)
380 * en différentes langues (y compris l'élément englobant)
382 * Équivalent à `(id_trad>0 AND id_trad=id_trad(precedent)) OR id_xx=id_xx(precedent)`
384 * @param string $idb Identifiant de la boucle
385 * @param array $boucles AST du squelette
386 * @param Critere $crit Paramètres du critère dans cette boucle
389 function critere_traduction_dist($idb, &$boucles, $crit) {
390 $boucle = &$boucles[$idb];
391 $prim = $boucle->primary
;
392 $table = $boucle->id_table
;
393 $arg = kwote(calculer_argument_precedent($idb, 'id_trad', $boucles));
394 $dprim = kwote(calculer_argument_precedent($idb, $prim, $boucles));
400 array("'='", "'$table.id_trad'", 0),
401 array("'='", "'$table.$prim'", $dprim)
405 array("'>'", "'$table.id_trad'", 0),
406 array("'='", "'$table.id_trad'", $arg)
413 * Compile le critère {origine_traduction}
415 * Sélectionne les éléments qui servent de base à des versions traduites
416 * (par exemple les articles "originaux" sur une boucle articles)
418 * Équivalent à (id_trad>0 AND id_xx=id_trad) OR (id_trad=0)
420 * @param string $idb Identifiant de la boucle
421 * @param array $boucles AST du squelette
422 * @param Critere $crit Paramètres du critère dans cette boucle
425 function critere_origine_traduction_dist($idb, &$boucles, $crit) {
426 $boucle = &$boucles[$idb];
427 $prim = $boucle->primary
;
428 $table = $boucle->id_table
;
433 array("'='", "'$table." . "id_trad'", "'$table.$prim'"),
434 array("'='", "'$table.id_trad'", "'0'")
436 $boucle->where
[] = ($crit->not ?
array("'NOT'", $c) : $c);
441 * Compile le critère {meme_parent}
443 * Sélectionne les éléments ayant le même parent que la boucle parente,
444 * c'est à dire les frères et sœurs.
446 * @param string $idb Identifiant de la boucle
447 * @param array $boucles AST du squelette
448 * @param Critere $crit Paramètres du critère dans cette boucle
451 function critere_meme_parent_dist($idb, &$boucles, $crit) {
453 $boucle = &$boucles[$idb];
454 $arg = kwote(calculer_argument_precedent($idb, 'id_parent', $boucles));
455 $id_parent = isset($GLOBALS['exceptions_des_tables'][$boucle->id_table
]['id_parent']) ?
456 $GLOBALS['exceptions_des_tables'][$boucle->id_table
]['id_parent'] :
458 $mparent = $boucle->id_table
. '.' . $id_parent;
460 if ($boucle->type_requete
== 'rubriques' or isset($GLOBALS['exceptions_des_tables'][$boucle->id_table
]['id_parent'])) {
461 $boucle->where
[] = array("'='", "'$mparent'", $arg);
463 } // le cas FORUMS est gere dans le plugin forum, dans la fonction critere_FORUMS_meme_parent_dist()
465 return (array('zbug_critere_inconnu', array('critere' => $crit->op
. ' ' . $boucle->type_requete
)));
471 * Compile le critère `branche` qui sélectionne dans une boucle les
472 * éléments appartenant à une branche d'une rubrique.
474 * Cherche l'identifiant de la rubrique en premier paramètre du critère `{branche XX}`
475 * s'il est renseigné, sinon, sans paramètre (`{branche}` tout court) dans les
476 * boucles parentes. On calcule avec lui la liste des identifiants
477 * de rubrique de toute la branche.
479 * La boucle qui possède ce critère cherche une liaison possible avec
480 * la colonne `id_rubrique`, et tentera de trouver une jointure avec une autre
481 * table si c'est nécessaire pour l'obtenir.
483 * Ce critère peut être rendu optionnel avec `{branche ?}` en remarquant
484 * cependant que le test s'effectue sur la présence d'un champ 'id_rubrique'
485 * sinon d'une valeur 'id_rubrique' dans l'environnement (et non 'branche'
488 * @link http://www.spip.net/@branche
490 * @param string $idb Identifiant de la boucle
491 * @param array $boucles AST du squelette
492 * @param Critere $crit Paramètres du critère dans cette boucle
495 function critere_branche_dist($idb, &$boucles, $crit) {
498 $boucle = &$boucles[$idb];
499 // prendre en priorite un identifiant en parametre {branche XX}
500 if (isset($crit->param
[0])) {
501 $arg = calculer_liste($crit->param
[0], array(), $boucles, $boucles[$idb]->id_parent
);
502 // sinon on le prend chez une boucle parente
504 $arg = kwote(calculer_argument_precedent($idb, 'id_rubrique', $boucles), $boucle->sql_serveur
, 'int NOT NULL');
507 //Trouver une jointure
508 $champ = "id_rubrique";
509 $desc = $boucle->show
;
510 //Seulement si necessaire
511 if (!array_key_exists($champ, $desc['field'])) {
512 $cle = trouver_jointure_champ($champ, $boucle);
513 $trouver_table = charger_fonction("trouver_table", "base");
514 $desc = $trouver_table($boucle->from
[$cle]);
515 if (count(trouver_champs_decomposes($champ, $desc)) > 1) {
516 $decompose = decompose_champ_id_objet($champ);
517 $champ = array_shift($decompose);
518 $boucle->where
[] = array("'='", _q($cle . "." . reset($decompose)), '"' . sql_quote(end($decompose)) . '"');
521 $cle = $boucle->id_table
;
524 $c = "sql_in('$cle" . ".$champ', calcul_branche_in($arg)"
525 . ($not ?
", 'NOT'" : '') . ")";
526 $boucle->where
[] = !$crit->cond ?
$c :
527 ("($arg ? $c : " . ($not ?
"'0=1'" : "'1=1'") . ')');
531 * Compile le critère `logo` qui liste les objets qui ont un logo
533 * @uses lister_objets_avec_logos()
534 * Pour obtenir les éléments qui ont un logo
536 * @param string $idb Identifiant de la boucle
537 * @param array $boucles AST du squelette
538 * @param Critere $crit Paramètres du critère dans cette boucle
541 function critere_logo_dist($idb, &$boucles, $crit) {
544 $boucle = &$boucles[$idb];
547 $boucle->id_table
. '.' . $boucle->primary
548 . "', lister_objets_avec_logos('" . $boucle->primary
. "'), '')";
551 $boucle->where
[] = array("'NOT'", $c);
553 $boucle->where
[] = $c;
559 * Compile le critère `fusion` qui regroupe les éléments selon une colonne.
561 * C'est la commande SQL «GROUP BY»
564 * @link http://www.spip.net/5166
567 * <BOUCLE_a(articles){fusion lang}>
570 * @param string $idb Identifiant de la boucle
571 * @param array $boucles AST du squelette
572 * @param Critere $crit Paramètres du critère dans cette boucle
575 function critere_fusion_dist($idb, &$boucles, $crit) {
576 if ($t = isset($crit->param
[0])) {
577 $t = $crit->param
[0];
578 if ($t[0]->type
== 'texte') {
580 if (preg_match("/^(.*)\.(.*)$/", $t, $r)) {
581 $t = table_objet_sql($r[1]);
582 $t = array_search($t, $boucles[$idb]->from
);
589 . calculer_critere_arg_dynamique($idb, $boucles, $t)
594 $boucles[$idb]->group
[] = $t;
595 if (!in_array($t, $boucles[$idb]->select
)) {
596 $boucles[$idb]->select
[] = $t;
599 return (array('zbug_critere_inconnu', array('critere' => $crit->op
. ' ?')));
604 * Compile le critère `{collecte}` qui permet de spécifier l'interclassement
605 * à utiliser pour les tris de la boucle.
607 * Cela permet avec le critère `{par}` de trier un texte selon
608 * l'interclassement indiqué. L'instruction s'appliquera sur les critères `{par}`
609 * qui succèdent ce critère, ainsi qu'au critère `{par}` précédent
610 * si aucun interclassement ne lui est déjà appliqué.
612 * Techniquement, c'est la commande SQL "COLLATE" qui utilisée.
613 * (elle peut être appliquée sur les order by, group by, where, like ...)
616 * - `{par titre}{collecte utf8_spanish_ci}` ou `{collecte utf8_spanish_ci}{par titre}`
617 * - `{par titre}{par surtitre}{collecte utf8_spanish_ci}` :
618 * Seul 'surtitre' (`par` précédent) utilisera l'interclassement
619 * - `{collecte utf8_spanish_ci}{par titre}{par surtitre}` :
620 * 'titre' et 'surtitre' utiliseront l'interclassement (tous les `par` suivants)
623 * Piège sur une éventuelle écriture peu probable :
624 * `{par a}{collecte c1}{par b}{collecte c2}` : le tri `{par b}`
625 * utiliserait l'interclassement c1 (et non c2 qui ne s'applique pas
626 * au `par` précédent s'il a déjà un interclassement demandé).
629 * @link http://www.spip.net/4028
630 * @see critere_par_dist() Le critère `{par}`
632 * @param string $idb Identifiant de la boucle
633 * @param array $boucles AST du squelette
634 * @param Critere $crit Paramètres du critère dans cette boucle
636 function critere_collecte_dist($idb, &$boucles, $crit) {
637 if (isset($crit->param
[0])) {
638 $_coll = calculer_liste($crit->param
[0], array(), $boucles, $boucles[$idb]->id_parent
);
639 $boucle = $boucles[$idb];
640 $boucle->modificateur
['collate'] = "($_coll ?' COLLATE '.$_coll:'')";
641 $n = count($boucle->order
);
642 if ($n && (strpos($boucle->order
[$n - 1], 'COLLATE') === false)) {
643 // l'instruction COLLATE doit être placée avant ASC ou DESC
644 // notamment lors de l'utilisation `{!par xxx}{collate yyy}`
646 (false !== $i = strpos($boucle->order
[$n - 1], 'ASC'))
647 OR (false !== $i = strpos($boucle->order
[$n - 1], 'DESC'))
649 $boucle->order
[$n - 1] = substr_replace($boucle->order
[$n - 1], "' . " . $boucle->modificateur
['collate'] . " . ' ", $i, 0);
651 $boucle->order
[$n - 1] .= " . " . $boucle->modificateur
['collate'];
655 return (array('zbug_critere_inconnu', array('critere' => $crit->op
. " " . count($boucles[$idb]->order
))));
659 // http://code.spip.net/@calculer_critere_arg_dynamique
660 function calculer_critere_arg_dynamique($idb, &$boucles, $crit, $suffix = '') {
661 $boucle = $boucles[$idb];
662 $alt = "('" . $boucle->id_table
. '.\' . $x' . $suffix . ')';
663 $var = '$champs_' . $idb;
664 $desc = (strpos($boucle->in
, "static $var =") !== false);
666 $desc = $boucle->show
['field'];
667 $desc = implode(',', array_map('_q', array_keys($desc)));
668 $boucles[$idb]->in
.= "\n\tstatic $var = array(" . $desc . ");";
671 $alt = "(in_array(\$x, $var) ? $alt :(\$x$suffix))";
673 $arg = calculer_liste($crit, array(), $boucles, $boucle->id_parent
);
675 return "((\$x = preg_replace(\"/\\W/\",'', $arg)) ? $alt : '')";
679 * Compile le critère `{par}` qui permet d'ordonner les résultats d'une boucle
681 * Demande à trier la boucle selon certains champs (en SQL, la commande `ORDER BY`).
682 * Si plusieurs tris sont demandés (plusieurs fois le critère `{par x}{par y}` dans une boucle ou plusieurs champs
683 * séparés par des virgules dans le critère `{par x, y, z}`), ils seront appliqués dans l'ordre.
685 * Quelques particularités :
686 * - `{par hasard}` : trie par hasard
687 * - `{par num titre}` : trie par numéro de titre
688 * - `{par multi titre}` : trie par la langue extraite d'une balise polyglotte `<multi>` sur le champ titre
689 * - `{!par date}` : trie par date inverse en utilisant le champ date principal déclaré pour la table (si c'est un objet éditorial).
690 * - `{!par points}` : trie par pertinence de résultat de recherche (avec le critère `{recherche}`)
691 * - `{par FUNCTION_SQL(n)}` : trie en utilisant une fonction SQL (peut dépendre du moteur SQL utilisé).
692 * Exemple : `{par SUBSTRING_INDEX(titre, ".", -1)}` (tri ~ alphabétique en ignorant les numéros de titres
693 * (exemple erroné car faux dès qu'un titre possède un point.)).
694 * - `{par table.champ}` : trie en effectuant une jointure sur la table indiquée.
695 * - `{par #BALISE}` : trie sur la valeur retournée par la balise (doit être un champ de la table, ou 'hasard').
700 * - `{par num titre, multi titre, hasard}`
703 * @link http://www.spip.net/5531
704 * @see critere_tri_dist() Le critère `{tri ...}`
705 * @see critere_inverse_dist() Le critère `{inverse}`
707 * @uses critere_parinverse()
709 * @param string $idb Identifiant de la boucle
710 * @param array $boucles AST du squelette
711 * @param Critere $crit Paramètres du critère dans cette boucle
713 function critere_par_dist($idb, &$boucles, $crit) {
714 return critere_parinverse($idb, $boucles, $crit);
718 * Calculs pour le critère `{par}` ou `{inverse}` pour ordonner les résultats d'une boucle
720 * Les expressions intermédiaires `{par expr champ}` sont calculées dans des fonctions
721 * `calculer_critere_par_expression_{expr}()` notamment `{par num champ}` ou `{par multi champ}`.
723 * @see critere_par_dist() Le critère `{par}` pour des exemples
725 * @uses calculer_critere_arg_dynamique() pour le calcul de `{par #ENV{tri}}`
726 * @uses calculer_critere_par_hasard() pour le calcul de `{par hasard}`
727 * @uses calculer_critere_par_champ()
728 * @see calculer_critere_par_expression_num() pour le calcul de `{par num x}`
729 * @see calculer_critere_par_expression_multi() pour le calcul de `{par multi x}`
731 * @param string $idb Identifiant de la boucle
732 * @param array $boucles AST du squelette
733 * @param Critere $crit Paramètres du critère dans cette boucle
735 function critere_parinverse($idb, &$boucles, $crit) {
736 $boucle = &$boucles[$idb];
738 $sens = $collecte = '';
740 $sens = " . ' DESC'";
742 if (isset($boucle->modificateur
['collate'])) {
743 $collecte = ' . ' . $boucle->modificateur
['collate'];
746 // Pour chaque paramètre du critère
747 foreach ($crit->param
as $tri) {
749 // tris specifiés dynamiquement {par #ENV{tri}}
750 if ($tri[0]->type
!= 'texte') {
751 // calculer le order dynamique qui verifie les champs
752 $order = calculer_critere_arg_dynamique($idb, $boucles, $tri, $sens);
753 // ajouter 'hasard' comme possibilité de tri dynamique
754 calculer_critere_par_hasard($idb, $boucles, $crit);
756 // tris textuels {par titre}
758 $par = array_shift($tri);
761 // tris de la forme {par expression champ} tel que {par num titre} ou {par multi titre}
762 if (preg_match(",^(\w+)[\s]+(.*)$,", $par, $m)) {
763 $expression = trim($m[1]);
764 $champ = trim($m[2]);
765 if (function_exists($f = 'calculer_critere_par_expression_' . $expression)) {
766 $order = $f($idb, $boucles, $crit, $tri, $champ);
768 return array('zbug_critere_inconnu', array('critere' => $crit->op
. " $par"));
771 // tris de la forme {par champ} ou {par FONCTION(champ)}
772 } elseif (preg_match(",^" . CHAMP_SQL_PLUS_FONC
. '$,is', $par, $match)) {
773 // {par FONCTION(champ)}
774 if (count($match) > 2) {
775 $par = substr($match[2], 1, -1);
778 // quelques cas spécifiques {par hasard}, {par date}
779 if ($par == 'hasard') {
780 $order = calculer_critere_par_hasard($idb, $boucles, $crit);
781 } elseif ($par == 'date' and !empty($boucle->show
['date'])) {
782 $order = "'" . $boucle->id_table
. "." . $boucle->show
['date'] . "'";
784 // cas général {par champ}, {par table.champ}, ...
785 $order = calculer_critere_par_champ($idb, $boucles, $crit, $par);
789 // on ne sait pas traiter…
791 return array('zbug_critere_inconnu', array('critere' => $crit->op
. " $par"));
794 // En cas d'erreur de squelette retournée par une fonction
795 if (is_array($order)) {
800 if (preg_match('/^\'([^"]*)\'$/', $order, $m)) {
802 if (strpos($t, '.') and !in_array($t, $boucle->select
)) {
803 $boucle->select
[] = $t;
810 if (preg_match("/^\s*'(.*)'\s*$/", $order, $r)) {
811 $order = "'$fct(" . $r[1] . ")'";
813 $order = "'$fct(' . $order . ')'";
816 $t = $order . $collecte . $sens;
817 if (preg_match("/^(.*)'\s*\.\s*'([^']*')$/", $t, $r)) {
821 $boucle->order
[] = $t;
826 * Calculs pour le critère `{par hasard}`
828 * Ajoute le générateur d'aléatoire au SELECT de la boucle.
830 * @param string $idb Identifiant de la boucle
831 * @param array $boucles AST du squelette
832 * @param Critere $crit Paramètres du critère dans cette boucle
833 * @return string Clause pour le Order by
835 function calculer_critere_par_hasard($idb, &$boucles, $crit) {
836 $boucle = &$boucles[$idb];
837 // Si ce n'est fait, ajouter un champ 'hasard' dans le select
838 $parha = "rand() AS hasard";
839 if (!in_array($parha, $boucle->select
)) {
840 $boucle->select
[] = $parha;
846 * Calculs pour le critère `{par num champ}` qui extrait le numéro préfixant un texte
848 * Tri par numéro de texte (tel que "10. titre"). Le numéro calculé est ajouté au SELECT
849 * de la boucle. L'écriture `{par num #ENV{tri}}` est aussi prise en compte.
852 * Les textes sans numéro valent 0 et sont donc placés avant les titres ayant des numéros.
853 * Utiliser `{par sinum champ, num champ}` pour avoir le comportement inverse.
855 * @see calculer_critere_par_expression_sinum() pour le critère `{par sinum champ}`
856 * @uses calculer_critere_par_champ()
858 * @param string $idb Identifiant de la boucle
859 * @param array $boucles AST du squelette
860 * @param Critere $crit Paramètres du critère dans cette boucle
861 * @param array $tri Paramètre en cours du critère
862 * @param string $champ Texte suivant l'expression ('titre' dans {par num titre})
863 * @return string Clause pour le Order by
865 function calculer_critere_par_expression_num($idb, &$boucles, $crit, $tri, $champ) {
866 $_champ = calculer_critere_par_champ($idb, $boucles, $crit, $champ, true);
867 if (is_array($_champ)) {
868 return array('zbug_critere_inconnu', array('critere' => $crit->op
. " num $champ"));
870 $boucle = &$boucles[$idb];
871 $texte = '0+' . $_champ;
872 $suite = calculer_liste($tri, array(), $boucles, $boucle->id_parent
);
873 if ($suite !== "''") {
874 $texte = "\" . ((\$x = $suite) ? ('$texte' . \$x) : '0')" . " . \"";
876 $as = 'num' . ($boucle->order ?
count($boucle->order
) : "");
877 $boucle->select
[] = $texte . " AS $as";
883 * Calculs pour le critère `{par sinum champ}` qui ordonne les champs avec numéros en premier
885 * Ajoute au SELECT la valeur 'sinum' qui vaut 0 si le champ a un numéro, 1 s'il n'en a pas.
886 * Ainsi `{par sinum titre, num titre, titre}` mettra les éléments sans numéro en fin de liste,
887 * contrairement à `{par num titre, titre}` seulement.
889 * @see calculer_critere_par_expression_num() pour le critère `{par num champ}`
890 * @uses calculer_critere_par_champ()
892 * @param string $idb Identifiant de la boucle
893 * @param array $boucles AST du squelette
894 * @param Critere $crit Paramètres du critère dans cette boucle
895 * @param array $tri Paramètre en cours du critère
896 * @param string $champ Texte suivant l'expression ('titre' dans {par sinum titre})
897 * @return string Clause pour le Order by
899 function calculer_critere_par_expression_sinum($idb, &$boucles, $crit, $tri, $champ) {
900 $_champ = calculer_critere_par_champ($idb, $boucles, $crit, $champ, true);
901 if (is_array($_champ)) {
902 return array('zbug_critere_inconnu', array('critere' => $crit->op
. " sinum $champ"));
904 $boucle = &$boucles[$idb];
905 $texte = '0+' . $_champ;
906 $suite = calculer_liste($tri, array(), $boucles, $boucle->id_parent
);
907 if ($suite !== "''") {
908 $texte = "\" . ((\$x = $suite) ? ('$texte' . \$x) : '0')" . " . \"";
910 $as = 'sinum' . ($boucle->order ?
count($boucle->order
) : "");
911 $boucle->select
[] = 'CASE (' . $texte . ') WHEN 0 THEN 1 ELSE 0 END AS ' . $as;
918 * Calculs pour le critère `{par multi champ}` qui extrait la langue en cours dans les textes
919 * ayant des balises `<multi>` (polyglottes)
921 * Ajoute le calcul du texte multi extrait dans le SELECT de la boucle.
922 * Il ne peut y avoir qu'un seul critère de tri `multi` par boucle.
924 * @uses calculer_critere_par_champ()
925 * @param string $idb Identifiant de la boucle
926 * @param array $boucles AST du squelette
927 * @param Critere $crit Paramètres du critère dans cette boucle
928 * @param array $tri Paramètre en cours du critère
929 * @param string $champ Texte suivant l'expression ('titre' dans {par multi titre})
930 * @return string Clause pour le Order by
932 function calculer_critere_par_expression_multi($idb, &$boucles, $crit, $tri, $champ) {
933 $_champ = calculer_critere_par_champ($idb, $boucles, $crit, $champ, true);
934 if (is_array($_champ)) {
935 return array('zbug_critere_inconnu', array('critere' => $crit->op
. " multi $champ"));
937 $boucle = &$boucles[$idb];
938 $boucle->select
[] = "\".sql_multi('" . $_champ . "', \$GLOBALS['spip_lang']).\"";
944 * Retourne le champ de tri demandé en ajoutant éventuellement les jointures nécessaires à la boucle.
946 * - si le champ existe dans la table, on l'utilise
947 * - si c'est une exception de jointure, on l'utilise (et crée la jointure au besoin)
948 * - si c'est un champ dont la jointure est déjà présente on la réutilise
949 * - si c'est un champ dont la jointure n'est pas présente, on la crée.
951 * @param string $idb Identifiant de la boucle
952 * @param array $boucles AST du squelette
953 * @param Critere $crit Paramètres du critère dans cette boucle
954 * @param string $par Nom du tri à analyser ('champ' ou 'table.champ')
955 * @param bool $raw Retourne le champ pour le compilateur ("'alias.champ'") ou brut ('alias.champ')
956 * @return array|string
958 function calculer_critere_par_champ($idb, &$boucles, $crit, $par, $raw = false) {
959 $boucle = &$boucles[$idb];
961 // le champ existe dans la table, pas de souci (le plus commun)
962 if (isset($desc['field'][$par])) {
963 $par = $boucle->id_table
. "." . $par;
965 // le champ est peut être une jointure
967 $table = $table_alias = false; // toutes les tables de jointure possibles
970 // le champ demandé est une exception de jointure {par titre_mot}
971 if (isset($GLOBALS['exceptions_des_jointures'][$par])) {
972 list($table, $champ) = $GLOBALS['exceptions_des_jointures'][$par];
973 } // la table de jointure est explicitement indiquée {par truc.muche}
974 elseif (preg_match("/^([^,]*)\.(.*)$/", $par, $r)) {
975 list(, $table, $champ) = $r;
976 $table_alias = $table; // c'est peut-être un alias de table {par L1.titre}
977 $table = table_objet_sql($table);
980 // Si on connait la table d'arrivée, on la demande donc explicitement
981 // Sinon on cherche le champ dans les tables possibles de jointures
982 // Si la table est déjà dans le from, on la réutilise.
983 if ($infos = chercher_champ_dans_tables($champ, $boucle->from
, $boucle->sql_serveur
, $table)) {
984 $par = $infos['alias'] . "." . $champ;
986 $boucle->jointures_explicites
987 and $alias = trouver_jointure_champ($champ, $boucle, explode(' ', $boucle->jointures_explicites
), false, $table)
989 $par = $alias . "." . $champ;
990 } elseif ($alias = trouver_jointure_champ($champ, $boucle, $boucle->jointures
, false, $table)) {
991 $par = $alias . "." . $champ;
992 // en spécifiant directement l'alias {par L2.titre} (situation hasardeuse tout de même)
995 and isset($boucle->from
[$table_alias])
996 and $infos = chercher_champ_dans_tables($champ, $boucle->from
, $boucle->sql_serveur
, $boucle->from
[$table_alias])
998 $par = $infos['alias'] . "." . $champ;
1000 // On avait table + champ, mais on ne les a pas trouvés
1001 return array('zbug_critere_inconnu', array('critere' => $crit->op
. " $par"));
1003 // Sinon tant pis, ca doit etre un champ synthetise (cf points)
1007 return $raw ?
$par : "'$par'";
1011 * Retourne un champ de tri en créant une jointure
1012 * si la table n'est pas présente dans le from de la boucle.
1015 * @param string $table Table du champ désiré
1016 * @param string $champ Champ désiré
1017 * @param Boucle $boucle Boucle en cours de compilation
1018 * @return string Champ pour le compilateur si trouvé, tel que "'alias.champ'", sinon vide.
1020 function critere_par_joint($table, $champ, &$boucle) {
1021 $t = array_search($table, $boucle->from
);
1023 $t = trouver_jointure_champ($champ, $boucle);
1025 return !$t ?
'' : ("'" . $t . '.' . $champ . "'");
1029 * Compile le critère `{inverse}` qui inverse l'ordre utilisé par le précédent critère `{par}`
1031 * Accèpte un paramètre pour déterminer le sens : `{inverse #X}` utilisera un tri croissant (ASC)
1032 * si la valeur retournée par `#X` est considérée vrai (`true`),
1033 * le sens contraire (DESC) sinon.
1036 * - `{par date}{inverse}`, équivalent à `{!par date}`
1037 * - `{par date}{inverse #ENV{sens}}` utilise la valeur d'environnement sens pour déterminer le sens.
1040 * @see critere_par_dist() Le critère `{par}`
1041 * @link http://www.spip.net/5530
1042 * @uses critere_parinverse()
1044 * @param string $idb Identifiant de la boucle
1045 * @param array $boucles AST du squelette
1046 * @param Critere $crit Paramètres du critère dans cette boucle
1048 function critere_inverse_dist($idb, &$boucles, $crit) {
1050 $boucle = &$boucles[$idb];
1051 // Classement par ordre inverse
1053 critere_parinverse($idb, $boucles, $crit);
1056 // Classement par ordre inverse fonction eventuelle de #ENV{...}
1057 if (isset($crit->param
[0])) {
1058 $critere = calculer_liste($crit->param
[0], array(), $boucles, $boucles[$idb]->id_parent
);
1059 $order = "(($critere)?' DESC':'')";
1062 $n = count($boucle->order
);
1064 if (isset($boucle->default_order
[0])) {
1065 $boucle->default_order
[0] .= ' . " DESC"';
1067 $boucle->default_order
[] = ' DESC';
1070 $t = $boucle->order
[$n - 1] . " . $order";
1071 if (preg_match("/^(.*)'\s*\.\s*'([^']*')$/", $t, $r)) {
1074 $boucle->order
[$n - 1] = $t;
1079 // http://code.spip.net/@critere_agenda_dist
1080 function critere_agenda_dist($idb, &$boucles, $crit) {
1081 $params = $crit->param
;
1083 if (count($params) < 1) {
1084 return array('zbug_critere_inconnu', array('critere' => $crit->op
. " ?"));
1087 $boucle = &$boucles[$idb];
1088 $parent = $boucle->id_parent
;
1089 $fields = $boucle->show
['field'];
1091 $date = array_shift($params);
1092 $type = array_shift($params);
1094 // la valeur $type doit etre connue a la compilation
1095 // donc etre forcement reduite a un litteral unique dans le source
1096 $type = is_object($type[0]) ?
$type[0]->texte
: null;
1098 // La valeur date doit designer un champ de la table SQL.
1099 // Si c'est un litteral unique dans le source, verifier a la compil,
1100 // sinon synthetiser le test de verif pour execution ulterieure
1101 // On prendra arbitrairement le premier champ si test negatif.
1102 if ((count($date) == 1) and ($date[0]->type
== 'texte')) {
1103 $date = $date[0]->texte
;
1104 if (!isset($fields[$date])) {
1105 return array('zbug_critere_inconnu', array('critere' => $crit->op
. " " . $date));
1108 $a = calculer_liste($date, array(), $boucles, $parent);
1109 $noms = array_keys($fields);
1111 $noms = join(" ", $noms);
1112 # bien laisser 2 espaces avant $nom pour que strpos<>0
1113 $cond = "(\$a=strval($a))AND\nstrpos(\" $noms \",\" \$a \")";
1114 $date = "'.(($cond)\n?\$a:\"$defaut\").'";
1116 $annee = $params ?
array_shift($params) : "";
1117 $annee = "\n" . 'sprintf("%04d", ($x = ' .
1118 calculer_liste($annee, array(), $boucles, $parent) .
1119 ') ? $x : date("Y"))';
1121 $mois = $params ?
array_shift($params) : "";
1122 $mois = "\n" . 'sprintf("%02d", ($x = ' .
1123 calculer_liste($mois, array(), $boucles, $parent) .
1124 ') ? $x : date("m"))';
1126 $jour = $params ?
array_shift($params) : "";
1127 $jour = "\n" . 'sprintf("%02d", ($x = ' .
1128 calculer_liste($jour, array(), $boucles, $parent) .
1129 ') ? $x : date("d"))';
1131 $annee2 = $params ?
array_shift($params) : "";
1132 $annee2 = "\n" . 'sprintf("%04d", ($x = ' .
1133 calculer_liste($annee2, array(), $boucles, $parent) .
1134 ') ? $x : date("Y"))';
1136 $mois2 = $params ?
array_shift($params) : "";
1137 $mois2 = "\n" . 'sprintf("%02d", ($x = ' .
1138 calculer_liste($mois2, array(), $boucles, $parent) .
1139 ') ? $x : date("m"))';
1141 $jour2 = $params ?
array_shift($params) : "";
1142 $jour2 = "\n" . 'sprintf("%02d", ($x = ' .
1143 calculer_liste($jour2, array(), $boucles, $parent) .
1144 ') ? $x : date("d"))';
1146 $date = $boucle->id_table
. ".$date";
1148 $quote_end = ",'" . $boucle->sql_serveur
. "','text'";
1149 if ($type == 'jour') {
1150 $boucle->where
[] = array(
1152 "'DATE_FORMAT($date, \'%Y%m%d\')'",
1153 ("sql_quote($annee . $mois . $jour$quote_end)")
1155 } elseif ($type == 'mois') {
1156 $boucle->where
[] = array(
1158 "'DATE_FORMAT($date, \'%Y%m\')'",
1159 ("sql_quote($annee . $mois$quote_end)")
1161 } elseif ($type == 'semaine') {
1162 $boucle->where
[] = array(
1166 "'DATE_FORMAT($date, \'%Y%m%d\')'",
1167 ("date_debut_semaine($annee, $mois, $jour)")
1171 "'DATE_FORMAT($date, \'%Y%m%d\')'",
1172 ("date_fin_semaine($annee, $mois, $jour)")
1175 } elseif (count($crit->param
) > 2) {
1176 $boucle->where
[] = array(
1180 "'DATE_FORMAT($date, \'%Y%m%d\')'",
1181 ("sql_quote($annee . $mois . $jour$quote_end)")
1183 array("'<='", "'DATE_FORMAT($date, \'%Y%m%d\')'", ("sql_quote($annee2 . $mois2 . $jour2$quote_end)"))
1186 // sinon on prend tout
1191 * Compile les critères {i,j} et {i/j}
1193 * Le critère {i,j} limite l'affiche de la boucle en commançant l'itération
1194 * au i-ème élément, et pour j nombre d'éléments.
1195 * Le critère {n-i,j} limite en commençant au n moins i-ème élément de boucle
1196 * Le critère {i,n-j} limite en terminant au n moins j-ème élément de boucle.
1198 * Le critère {i/j} affiche une part d'éléments de la boucle.
1199 * Commence à i*n/j élément et boucle n/j éléments. {2/4} affiche le second
1200 * quart des éléments d'une boucle.
1202 * Traduit si possible (absence de n dans {i,j}) la demande en une
1203 * expression LIMIT du gestionnaire SQL
1205 * @param string $idb Identifiant de la boucle
1206 * @param array $boucles AST du squelette
1207 * @param Critere $crit Paramètres du critère dans cette boucle
1210 function calculer_critere_parties($idb, &$boucles, $crit) {
1211 $boucle = &$boucles[$idb];
1212 $a1 = $crit->param
[0];
1213 $a2 = $crit->param
[1];
1216 list($a11, $a12) = calculer_critere_parties_aux($idb, $boucles, $a1);
1217 list($a21, $a22) = calculer_critere_parties_aux($idb, $boucles, $a2);
1219 if (($op == ',') && (is_numeric($a11) && (is_numeric($a21)))) {
1220 $boucle->limit
= $a11 . ',' . $a21;
1222 // 3 dans {1/3}, {2,3} ou {1,n-3}
1223 $boucle->total_parties
= ($a21 != 'n') ?
$a21 : $a22;
1224 // 2 dans {2/3}, {2,5}, {n-2,1}
1225 $partie = ($a11 != 'n') ?
$a11 : $a12;
1226 $mode = (($op == '/') ?
'/' :
1227 (($a11 == 'n') ?
'-' : '+') . (($a21 == 'n') ?
'-' : '+'));
1228 // cas simple {0,#ENV{truc}} compilons le en LIMIT :
1229 if ($a11 !== 'n' and $a21 !== 'n' and $mode == "++" and $op == ',') {
1231 (is_numeric($a11) ?
"'$a11'" : $a11)
1233 . (is_numeric($a21) ?
"'$a21'" : $a21);
1235 calculer_parties($boucles, $idb, $partie, $mode);
1241 * Compile certains critères {i,j} et {i/j}
1243 * Calcule une expression déterminant $debut_boucle et $fin_boucle (le
1244 * début et la fin des éléments de la boucle qui doivent être affichés)
1245 * et les déclare dans la propriété «mode_partie» de la boucle, qui se
1246 * charge également de déplacer le pointeur de boucle sur le premier
1247 * élément à afficher.
1249 * Place dans la propriété partie un test vérifiant que l'élément de
1250 * boucle en cours de lecture appartient bien à la plage autorisée.
1251 * Trop tôt, passe à l'élément suivant, trop tard, sort de l'itération de boucle.
1253 * @param array $boucles AST du squelette
1254 * @param string $id_boucle Identifiant de la boucle
1255 * @param string $debut Valeur ou code pour trouver le début (i dans {i,j})
1256 * @param string $mode
1257 * Mode (++, p+, +- ...) : 2 signes début & fin
1258 * - le signe - indique
1259 * -- qu'il faut soustraire debut du total {n-3,x}. 3 étant $debut
1260 * -- qu'il faut raccourcir la fin {x,n-3} de 3 elements. 3 étant $total_parties
1261 * - le signe p indique une pagination
1264 function calculer_parties(&$boucles, $id_boucle, $debut, $mode) {
1265 $total_parties = $boucles[$id_boucle]->total_parties
;
1267 preg_match(",([+-/p])([+-/])?,", $mode, $regs);
1268 list(, $op1, $op2) = array_pad($regs, 3, null);
1269 $nombre_boucle = "\$Numrows['$id_boucle']['total']";
1272 $pmoins1 = is_numeric($debut) ?
($debut - 1) : "($debut-1)";
1273 $totpos = is_numeric($total_parties) ?
($total_parties) :
1274 "($total_parties ? $total_parties : 1)";
1275 $fin = "ceil(($nombre_boucle * $debut )/$totpos) - 1";
1276 $debut = !$pmoins1 ?
0 : "ceil(($nombre_boucle * $pmoins1)/$totpos);";
1280 $debut = "$nombre_boucle - $debut;";
1285 $fin = '$debut_boucle + ' . $nombre_boucle . ' - '
1286 . (is_numeric($total_parties) ?
($total_parties +
1) :
1287 ($total_parties . ' - 1'));
1289 // {x,1} ou {pagination}
1290 $fin = '$debut_boucle'
1291 . (is_numeric($total_parties) ?
1292 (($total_parties == 1) ?
"" : (' + ' . ($total_parties - 1))) :
1293 ('+' . $total_parties . ' - 1'));
1296 // {pagination}, gerer le debut_xx=-1 pour tout voir
1298 $debut .= ";\n \$debut_boucle = ((\$tout=(\$debut_boucle == -1))?0:(\$debut_boucle))";
1299 $debut .= ";\n \$debut_boucle = max(0,min(\$debut_boucle,floor(($nombre_boucle-1)/($total_parties))*($total_parties)))";
1300 $fin = "(\$tout ? $nombre_boucle : $fin)";
1305 // $debut_boucle et $fin_boucle sont les indices SQL du premier
1306 // et du dernier demandes dans la boucle : 0 pour le premier,
1307 // n-1 pour le dernier ; donc total_boucle = 1 + debut - fin
1308 // Utiliser min pour rabattre $fin_boucle sur total_boucle.
1310 $boucles[$id_boucle]->mode_partie
= "\n\t"
1311 . '$debut_boucle = ' . $debut . ";\n "
1312 . "\$debut_boucle = intval(\$debut_boucle);\n "
1313 . '$fin_boucle = min(' . $fin . ", \$Numrows['$id_boucle']['total'] - 1);\n "
1314 . '$Numrows[\'' . $id_boucle . "']['grand_total'] = \$Numrows['$id_boucle']['total'];\n "
1315 . '$Numrows[\'' . $id_boucle . '\']["total"] = max(0,$fin_boucle - $debut_boucle + 1);'
1316 . "\n\tif (\$debut_boucle>0"
1317 . " AND \$debut_boucle < \$Numrows['$id_boucle']['grand_total']"
1318 . " AND \$iter->seek(\$debut_boucle,'continue'))"
1319 . "\n\t\t\$Numrows['$id_boucle']['compteur_boucle'] = \$debut_boucle;\n\t";
1321 $boucles[$id_boucle]->partie
= "
1322 if (\$Numrows['$id_boucle']['compteur_boucle'] <= \$debut_boucle) continue;
1323 if (\$Numrows['$id_boucle']['compteur_boucle']-1 > \$fin_boucle) break;";
1327 * Analyse un des éléments des critères {a,b} ou {a/b}
1329 * Pour l'élément demandé (a ou b) retrouve la valeur de l'élément,
1330 * et de combien il est soustrait si c'est le cas comme dans {a-3,b}
1332 * @param string $idb Identifiant de la boucle
1333 * @param array $boucles AST du squelette
1334 * @param array $param Paramètre à analyser (soit a, soit b dans {a,b} ou {a/b})
1335 * @return array Valeur de l'élément (peut être une expression PHP), Nombre soustrait
1337 function calculer_critere_parties_aux($idb, &$boucles, $param) {
1338 if ($param[0]->type
!= 'texte') {
1339 $a1 = calculer_liste(array($param[0]), array('id_mere' => $idb), $boucles, $boucles[$idb]->id_parent
);
1340 if (isset($param[1]->texte
)) {
1341 preg_match(',^ *(-([0-9]+))? *$,', $param[1]->texte
, $m);
1343 return array("intval($a1)", ((isset($m[2]) and $m[2]) ?
$m[2] : 0));
1345 return array("intval($a1)", 0);
1348 preg_match(',^ *(([0-9]+)|n) *(- *([0-9]+)? *)?$,', $param[0]->texte
, $m);
1351 return array($a1, 0);
1352 } elseif (!empty($m[4])) {
1353 return array($a1, $m[4]);
1355 return array($a1, calculer_liste(array($param[1]), array(), $boucles, $boucles[$idb]->id_parent
));
1362 * Compile les critères d'une boucle
1364 * Cette fonction d'aiguillage cherche des fonctions spécifiques déclarées
1365 * pour chaque critère demandé, dans l'ordre ci-dessous :
1367 * - critere_{serveur}_{table}_{critere}, sinon avec _dist
1368 * - critere_{serveur}_{critere}, sinon avec _dist
1369 * - critere_{table}_{critere}, sinon avec _dist
1370 * - critere_{critere}, sinon avec _dist
1371 * - calculer_critere_defaut, sinon avec _dist
1373 * Émet une erreur de squelette si un critère retourne une erreur.
1375 * @param string $idb
1376 * Identifiant de la boucle
1377 * @param array $boucles
1379 * @return string|array
1380 * string : Chaine vide sans erreur
1381 * array : Erreur sur un des critères
1383 function calculer_criteres($idb, &$boucles) {
1385 $boucle = $boucles[$idb];
1386 $table = strtoupper($boucle->type_requete
);
1387 $serveur = strtolower($boucle->sql_serveur
);
1389 $defaut = charger_fonction('DEFAUT', 'calculer_critere');
1390 // s'il y avait une erreur de syntaxe, propager cette info
1391 if (!is_array($boucle->criteres
)) {
1395 foreach ($boucle->criteres
as $crit) {
1396 $critere = $crit->op
;
1397 // critere personnalise ?
1400 ((!function_exists($f = "critere_" . $serveur . "_" . $table . "_" . $critere))
1401 and (!function_exists($f = $f . "_dist"))
1402 and (!function_exists($f = "critere_" . $serveur . "_" . $critere))
1403 and (!function_exists($f = $f . "_dist"))
1406 and (!function_exists($f = "critere_" . $table . "_" . $critere))
1407 and (!function_exists($f = $f . "_dist"))
1408 and (!function_exists($f = "critere_" . $critere))
1409 and (!function_exists($f = $f . "_dist"))
1411 // fonction critere standard
1414 // compile le critere
1415 $res = $f($idb, $boucles, $crit);
1417 // Gestion centralisee des erreurs pour pouvoir propager
1418 if (is_array($res)) {
1420 erreur_squelette($msg, $boucle);
1428 * Désemberlificote les guillements et échappe (ou fera échapper) le contenu...
1430 * Madeleine de Proust, revision MIT-1958 sqq, revision CERN-1989
1431 * hum, c'est kwoi cette fonxion ? on va dire qu'elle desemberlificote les guillemets...
1433 * http://code.spip.net/@kwote
1435 * @param string $lisp Code compilé
1436 * @param string $serveur Connecteur de bdd utilisé
1437 * @param string $type Type d'échappement (char, int...)
1438 * @return string Code compilé rééchappé
1440 function kwote($lisp, $serveur = '', $type = '') {
1441 if (preg_match(_CODE_QUOTE
, $lisp, $r)) {
1442 return $r[1] . "\"" . sql_quote(str_replace(array("\\'", "\\\\"), array("'", "\\"), $r[2]), $serveur, $type) . "\"";
1444 return "sql_quote($lisp, '$serveur', '" . str_replace("'", "\\'", $type) . "')";
1450 * Compile un critère possédant l'opérateur IN : {xx IN yy}
1452 * Permet de restreindre un champ sur une liste de valeurs tel que
1453 * {id_article IN 3,4} {id_article IN #LISTE{3,4}}
1455 * Si on a une liste de valeurs dans #ENV{x}, utiliser la double etoile
1456 * pour faire par exemple {id_article IN #ENV**{liste_articles}}
1458 * @param string $idb Identifiant de la boucle
1459 * @param array $boucles AST du squelette
1460 * @param Critere $crit Paramètres du critère dans cette boucle
1463 function critere_IN_dist($idb, &$boucles, $crit) {
1464 $r = calculer_critere_infixe($idb, $boucles, $crit);
1466 return (array('zbug_critere_inconnu', array('critere' => $crit->op
. " ?")));
1468 list($arg, $op, $val, $col, $where_complement) = $r;
1470 $in = critere_IN_cas($idb, $boucles, $crit->not ?
'NOT' : ($crit->exclus ?
'exclus' : ''), $arg, $op, $val, $col);
1472 // inserer la condition; exemple: {id_mot ?IN (66, 62, 64)}
1475 $pred = calculer_argument_precedent($idb, $col, $boucles);
1476 $where = array("'?'", $pred, $where, "''");
1477 if ($where_complement) // condition annexe du type "AND (objet='article')"
1479 $where_complement = array("'?'", $pred, $where_complement, "''");
1482 if ($crit->exclus
) {
1483 if (!preg_match(",^L[0-9]+[.],", $arg)) {
1484 $where = array("'NOT'", $where);
1486 // un not sur un critere de jointure se traduit comme un NOT IN avec une sous requete
1487 // c'est une sous requete identique a la requete principale sous la forme (SELF,$select,$where) avec $select et $where qui surchargent
1493 "'" . $boucles[$idb]->id_table
. "." . $boucles[$idb]->primary
. "'",
1494 array("'SELF'", "'" . $boucles[$idb]->id_table
. "." . $boucles[$idb]->primary
. "'", $where)
1500 $boucles[$idb]->where
[] = $where;
1501 if ($where_complement) // condition annexe du type "AND (objet='article')"
1503 $boucles[$idb]->where
[] = $where_complement;
1507 // http://code.spip.net/@critere_IN_cas
1508 function critere_IN_cas($idb, &$boucles, $crit2, $arg, $op, $val, $col) {
1509 static $num = array();
1510 $descr = $boucles[$idb]->descr
;
1511 $cpt = &$num[$descr['nom']][$descr['gram']][$idb];
1513 $var = '$in' . $cpt++
;
1514 $x = "\n\t$var = array();";
1515 foreach ($val as $k => $v) {
1516 if (preg_match(",^(\n//.*\n)?'(.*)'$,", $v, $r)) {
1517 // optimiser le traitement des constantes
1518 if (is_numeric($r[2])) {
1519 $x .= "\n\t$var" . "[]= $r[2];";
1521 $x .= "\n\t$var" . "[]= " . sql_quote($r[2]) . ";";
1524 // Pour permettre de passer des tableaux de valeurs
1525 // on repere l'utilisation brute de #ENV**{X},
1526 // c'est-a-dire sa traduction en ($PILE[0][X]).
1527 // et on deballe mais en rajoutant l'anti XSS
1528 $x .= "\n\tif (!(is_array(\$a = ($v))))\n\t\t$var" . "[]= \$a;\n\telse $var = array_merge($var, \$a);";
1532 $boucles[$idb]->in
.= $x;
1534 // inserer le tri par defaut selon les ordres du IN ...
1535 // avec une ecriture de type FIELD qui degrade les performances (du meme ordre qu'un regexp)
1536 // et que l'on limite donc strictement aux cas necessaires :
1537 // si ce n'est pas un !IN, et si il n'y a pas d'autre order dans la boucle
1539 $boucles[$idb]->default_order
[] = "((!sql_quote($var) OR sql_quote($var)===\"''\") ? 0 : ('FIELD($arg,' . sql_quote($var) . ')'))";
1542 return "sql_in('$arg',sql_quote($var)" . ($crit2 == 'NOT' ?
",'NOT'" : "") . ")";
1546 * Compile le critère {where}
1548 * Ajoute une contrainte sql WHERE, tout simplement pour faire le pont
1549 * entre php et squelettes, en utilisant la syntaxe attendue par
1550 * la propriété $where d'une Boucle.
1552 * @param string $idb Identifiant de la boucle
1553 * @param array $boucles AST du squelette
1554 * @param Critere $crit Paramètres du critère dans cette boucle
1557 function critere_where_dist($idb, &$boucles, $crit) {
1558 $boucle = &$boucles[$idb];
1559 if (isset($crit->param
[0])) {
1560 $_where = calculer_liste($crit->param
[0], array(), $boucles, $boucle->id_parent
);
1562 $_where = 'spip_sanitize_from_request(@$Pile[0]["where"],"where","vide")';
1566 $_where = "((\$zzw = $_where) ? \$zzw : '')";
1570 $_where = "array('NOT',$_where)";
1573 $boucle->where
[] = $_where;
1578 * Compile le critère `{tri}` permettant le tri dynamique d'un champ
1580 * Le critère `{tri}` gère un champ de tri qui peut être modifié dynamiquement par la balise `#TRI`.
1581 * Il s'utilise donc conjointement avec la balise `#TRI` dans la même boucle
1582 * pour génerér les liens qui permettent de changer le critère de tri et le sens du tri
1584 * @syntaxe `{tri [champ_par_defaut][,sens_par_defaut][,nom_variable]}`
1586 * - champ_par_defaut : un champ de la table sql
1587 * - sens_par_defaut : -1 ou inverse pour décroissant, 1 ou direct pour croissant
1588 * peut être un tableau pour préciser des sens par défaut associés à chaque champ
1589 * exemple : `array('titre' => 1, 'date' => -1)` pour trier par défaut
1590 * les titre croissants et les dates décroissantes
1591 * dans ce cas, quand un champ est utilisé pour le tri et n'est pas présent dans le tableau
1592 * c'est la première valeur qui est utilisée
1593 * - nom_variable : nom de la variable utilisée (par defaut `tri_{nomboucle}`)
1596 * {tri titre,inverse}
1598 * {tri titre,-1,truc}
1600 * Exemple d'utilisation :
1603 * <p>#TRI{titre,'Trier par titre'} | #TRI{date,'Trier par date'}</p>
1605 * <BOUCLE_articles(ARTICLES){tri titre}>
1606 * <li>#TITRE - [(#DATE|affdate_jourcourt)]</li>
1607 * </BOUCLE_articles>
1612 * Contraitement à `{par ...}`, `{tri}` ne peut prendre qu'un seul champ,
1613 * mais il peut être complété avec `{par ...}` pour indiquer des criteres secondaires
1616 * `{tri num titre}{par titre}` permet de faire un tri sur le rang (modifiable dynamiquement)
1617 * avec un second critère sur le titre en cas d'égalité des rangs
1619 * @link http://www.spip.net/5429
1620 * @see critere_par_dist() Le critère `{par ...}`
1621 * @see balise_TRI_dist() La balise `#TRI`
1623 * @param string $idb Identifiant de la boucle
1624 * @param array $boucles AST du squelette
1625 * @param Critere $crit Paramètres du critère dans cette boucle
1628 function critere_tri_dist($idb, &$boucles, $crit) {
1629 $boucle = &$boucles[$idb];
1631 // definition du champ par defaut
1632 $_champ_defaut = !isset($crit->param
[0][0]) ?
"''"
1633 : calculer_liste(array($crit->param
[0][0]), array(), $boucles, $boucle->id_parent
);
1634 $_sens_defaut = !isset($crit->param
[1][0]) ?
"1"
1635 : calculer_liste(array($crit->param
[1][0]), array(), $boucles, $boucle->id_parent
);
1636 $_variable = !isset($crit->param
[2][0]) ?
"'$idb'"
1637 : calculer_liste(array($crit->param
[2][0]), array(), $boucles, $boucle->id_parent
);
1639 $_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):'')";
1641 $_sens_defaut = "(is_array(\$s=$_sens_defaut)?(isset(\$s[\$st=$_tri])?\$s[\$st]:reset(\$s)):\$s)";
1642 $_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)";
1644 $boucle->modificateur
['tri_champ'] = $_tri;
1645 $boucle->modificateur
['tri_sens'] = $_sens;
1646 $boucle->modificateur
['tri_nom'] = $_variable;
1647 // faut il inserer un test sur l'existence de $tri parmi les champs de la table ?
1648 // evite des erreurs sql, mais peut empecher des tri sur jointure ...
1654 \$senstri = (\$senstri<0)?' DESC':'';
1657 $boucle->select
[] = "\".tri_champ_select(\$tri).\"";
1658 $boucle->order
[] = "tri_champ_order(\$tri,\$command['from']).\$senstri";
1661 # Criteres de comparaison
1664 * Compile un critère non déclaré explicitement
1666 * Compile les critères non déclarés, ainsi que les parties de boucles
1667 * avec les critères {0,1} ou {1/2}
1669 * @param string $idb Identifiant de la boucle
1670 * @param array $boucles AST du squelette
1671 * @param Critere $crit Paramètres du critère dans cette boucle
1674 function calculer_critere_DEFAUT_dist($idb, &$boucles, $crit) {
1675 // double cas particulier {0,1} et {1/2} repere a l'analyse lexicale
1676 if (($crit->op
== ",") or ($crit->op
== '/')) {
1677 return calculer_critere_parties($idb, $boucles, $crit);
1680 $r = calculer_critere_infixe($idb, $boucles, $crit);
1682 # // on produit une erreur seulement si le critere n'a pas de '?'
1683 # if (!$crit->cond) {
1684 return (array('zbug_critere_inconnu', array('critere' => $crit->op
)));
1687 calculer_critere_DEFAUT_args($idb, $boucles, $crit, $r);
1693 * Compile un critère non déclaré explicitement, dont on reçoit une analyse
1695 * Ajoute en fonction des arguments trouvés par calculer_critere_infixe()
1696 * les conditions WHERE à appliquer sur la boucle.
1698 * @see calculer_critere_infixe()
1700 * @param string $idb Identifiant de la boucle
1701 * @param array $boucles AST du squelette
1702 * @param Critere $crit Paramètres du critère dans cette boucle
1703 * @param array $args Description du critère
1704 * Cf. retour de calculer_critere_infixe()
1707 function calculer_critere_DEFAUT_args($idb, &$boucles, $crit, $args) {
1708 list($arg, $op, $val, $col, $where_complement) = $args;
1710 $where = array("'$op'", "'$arg'", $val[0]);
1712 // inserer la negation (cf !...)
1715 $where = array("'NOT'", $where);
1717 if ($crit->exclus
) {
1718 if (!preg_match(",^L[0-9]+[.],", $arg)) {
1719 $where = array("'NOT'", $where);
1721 // un not sur un critere de jointure se traduit comme un NOT IN avec une sous requete
1722 // c'est une sous requete identique a la requete principale sous la forme (SELF,$select,$where) avec $select et $where qui surchargent
1728 "'" . $boucles[$idb]->id_table
. "." . $boucles[$idb]->primary
. "'",
1729 array("'SELF'", "'" . $boucles[$idb]->id_table
. "." . $boucles[$idb]->primary
. "'", $where)
1735 // inserer la condition (cf {lang?})
1736 // traiter a part la date, elle est mise d'office par SPIP,
1738 $pred = calculer_argument_precedent($idb, $col, $boucles);
1739 if ($col == "date" or $col == "date_redac") {
1740 if ($pred == "\$Pile[0]['" . $col . "']") {
1741 $pred = "(\$Pile[0]['{$col}_default']?'':$pred)";
1745 if ($op == '=' and !$crit->not
) {
1748 "(is_array($pred))",
1749 critere_IN_cas($idb, $boucles, 'COND', $arg, $op, array($pred), $col),
1753 $where = array("'?'", "!(is_array($pred)?count($pred):strlen($pred))", "''", $where);
1754 if ($where_complement) // condition annexe du type "AND (objet='article')"
1756 $where_complement = array("'?'", "!(is_array($pred)?count($pred):strlen($pred))", "''", $where_complement);
1760 $boucles[$idb]->where
[] = $where;
1761 if ($where_complement) // condition annexe du type "AND (objet='article')"
1763 $boucles[$idb]->where
[] = $where_complement;
1769 * Décrit un critère non déclaré explicitement
1771 * Décrit un critère non déclaré comme {id_article} {id_article>3} en
1772 * retournant un tableau de l'analyse si la colonne (ou l'alias) existe vraiment.
1774 * Ajoute au passage pour chaque colonne utilisée (alias et colonne véritable)
1775 * un modificateur['criteres'][colonne].
1777 * S'occupe de rechercher des exceptions, tel que
1778 * - les id_parent, id_enfant, id_secteur,
1779 * - des colonnes avec des exceptions déclarées,
1780 * - des critères de date (jour_relatif, ...),
1781 * - des critères sur tables jointes explicites (mots.titre),
1782 * - des critères sur tables de jointure non explicite (id_mot sur une boucle articles...)
1785 * @param string $idb Identifiant de la boucle
1786 * @param array $boucles AST du squelette
1787 * @param Critere $crit Paramètres du critère dans cette boucle
1788 * @return array|string
1789 * Liste si on trouve le champ :
1791 * Opérande avant l'opérateur : souvent la colonne d'application du critère, parfois un calcul
1792 * plus complexe dans le cas des dates.
1794 * L'opérateur utilisé, tel que '='
1796 * Liste de codes PHP obtenant les valeurs des comparaisons (ex: id_article sur la boucle parente)
1797 * Souvent (toujours ?) un tableau d'un seul élément.
1799 * - $where_complement
1801 * Chaîne vide si on ne trouve pas le champ...
1803 function calculer_critere_infixe($idb, &$boucles, $crit) {
1805 $boucle = &$boucles[$idb];
1806 $type = $boucle->type_requete
;
1807 $table = $boucle->id_table
;
1808 $desc = $boucle->show
;
1811 list($fct, $col, $op, $val, $args_sql) =
1812 calculer_critere_infixe_ops($idb, $boucles, $crit);
1815 $where_complement = false;
1817 // Cas particulier : id_enfant => utiliser la colonne id_objet
1818 if ($col == 'id_enfant') {
1819 $col = $boucle->primary
;
1822 // Cas particulier : id_parent => verifier les exceptions de tables
1823 if ((in_array($col, array('id_parent', 'id_secteur')) and isset($GLOBALS['exceptions_des_tables'][$table][$col]))
1824 or (isset($GLOBALS['exceptions_des_tables'][$table][$col]) and is_string($GLOBALS['exceptions_des_tables'][$table][$col]))
1826 $col = $GLOBALS['exceptions_des_tables'][$table][$col];
1827 } // et possibilite de gerer un critere secteur sur des tables de plugins (ie forums)
1829 if (($col == 'id_secteur') and ($critere_secteur = charger_fonction("critere_secteur_$type", "public", true))) {
1830 $table = $critere_secteur($idb, $boucles, $val, $crit);
1833 // cas id_article=xx qui se mappe en id_objet=xx AND objet=article
1834 // sauf si exception declaree : sauter cette etape
1837 !isset($GLOBALS['exceptions_des_jointures'][table_objet_sql($table)][$col])
1838 and !isset($GLOBALS['exceptions_des_jointures'][$col])
1839 and count(trouver_champs_decomposes($col, $desc)) > 1
1841 $e = decompose_champ_id_objet($col);
1842 $col = array_shift($e);
1843 $where_complement = primary_doublee($e, $table);
1844 } // Cas particulier : expressions de date
1846 if ($c = calculer_critere_infixe_date($idb, $boucles, $col)) {
1847 list($col, $col_vraie) = $c;
1849 } // table explicitée {mots.titre}
1851 if (preg_match('/^(.*)\.(.*)$/', $col, $r)) {
1852 list(, $table, $col) = $r;
1855 $trouver_table = charger_fonction('trouver_table', 'base');
1856 if ($desc = $trouver_table($table, $boucle->sql_serveur
)
1857 and isset($desc['field'][$col])
1858 and $cle = array_search($desc['table'], $boucle->from
)
1862 $table = trouver_jointure_champ($col, $boucle, array($table), ($crit->cond
or $op != '='));
1864 #$table = calculer_critere_externe_init($boucle, array($table), $col, $desc, ($crit->cond OR $op!='='), true);
1869 // si le champ n'est pas trouvé dans la table,
1870 // on cherche si une jointure peut l'obtenir
1871 elseif (@!array_key_exists($col, $desc['field'])) {
1872 // Champ joker * des iterateurs DATA qui accepte tout
1873 if (@array_key_exists
('*', $desc['field'])) {
1874 $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
1877 $r = calculer_critere_infixe_externe($boucle, $crit, $op, $desc, $col, $col_alias, $table);
1881 list($col, $col_alias, $table, $where_complement, $desc) = $r;
1889 $col_vraie = ($col_vraie ?
$col_vraie : $col);
1890 // Dans tous les cas,
1891 // virer les guillemets eventuels autour d'un int (qui sont refuses par certains SQL)
1892 // et passer dans sql_quote avec le type si connu
1893 // et int sinon si la valeur est numerique
1894 // sinon introduire le vrai type du champ si connu dans le sql_quote (ou int NOT NULL sinon)
1895 // Ne pas utiliser intval, PHP tronquant les Bigint de SQL
1896 if ($op == '=' or in_array($op, $GLOBALS['table_criteres_infixes'])) {
1897 $type_cast_quote = (isset($desc['field'][$col_vraie]) ?
$desc['field'][$col_vraie] : 'int NOT NULL');
1898 // defaire le quote des int et les passer dans sql_quote avec le bon type de champ si on le connait, int sinon
1899 // prendre en compte le debug ou la valeur arrive avec un commentaire PHP en debut
1900 if (preg_match(",^\\A(\s*//.*?$\s*)?\"'(-?\d+)'\"\\z,ms", $val[0], $r)) {
1901 $val[0] = $r[1] . '"' . sql_quote($r[2], $boucle->sql_serveur
, $type_cast_quote) . '"';
1903 // sinon expliciter les
1904 // sql_quote(truc) en sql_quote(truc,'',type)
1905 // sql_quote(truc,serveur) en sql_quote(truc,serveur,type)
1906 // sql_quote(truc,serveur,'') en sql_quote(truc,serveur,type)
1908 // sql_quote(truc,'','varchar(10) DEFAULT \'oui\' COLLATE NOCASE')
1909 // sql_quote(truc,'','varchar')
1910 elseif (preg_match('/\Asql_quote[(](.*?)(,[^)]*?)?(,[^)]*(?:\(\d+\)[^)]*)?)?[)]\s*\z/ms', $val[0], $r)
1911 // si pas deja un type
1912 and (!isset($r[3]) or !$r[3] or !trim($r[3],", '"))
1915 . ((isset($r[2]) and $r[2]) ?
$r[2] : ",''")
1916 . ",'" . addslashes($type_cast_quote) . "'";
1917 $val[0] = "sql_quote($r)";
1919 elseif(strpos($val[0], '@@defaultcast@@') !== false
1920 and preg_match("/'@@defaultcast@@'\s*\)\s*\z/ms", $val[0], $r)) {
1921 $val[0] = substr($val[0], 0, -strlen($r[0])) . "'" . addslashes($type_cast_quote) . "')";
1925 if(strpos($val[0], '@@defaultcast@@') !== false
1926 and preg_match("/'@@defaultcast@@'\s*\)\s*\z/ms", $val[0], $r)) {
1927 $val[0] = substr($val[0], 0, -strlen($r[0])) . "'char')";
1930 // Indicateur pour permettre aux fonctionx boucle_X de modifier
1931 // leurs requetes par defaut, notamment le champ statut
1932 // Ne pas confondre champs de la table principale et des jointures
1933 if ($table === $boucle->id_table
) {
1934 $boucles[$idb]->modificateur
['criteres'][$col_vraie] = true;
1935 if ($col_alias != $col_vraie) {
1936 $boucles[$idb]->modificateur
['criteres'][$col_alias] = true;
1940 // ajout pour le cas special d'une condition sur le champ statut:
1941 // il faut alors interdire a la fonction de boucle
1942 // de mettre ses propres criteres de statut
1943 // http://www.spip.net/@statut (a documenter)
1944 // garde pour compatibilite avec code des plugins anterieurs, mais redondant avec la ligne precedente
1945 if ($col == 'statut') {
1946 $boucles[$idb]->statut
= true;
1949 // inserer le nom de la table SQL devant le nom du champ
1951 if ($col[0] == "`") {
1952 $arg = "$table." . substr($col, 1, -1);
1954 $arg = "$table.$col";
1960 // inserer la fonction SQL
1962 $arg = "$fct($arg$args_sql)";
1965 return array($arg, $op, $val, $col_alias, $where_complement);
1970 * Décrit un critère non déclaré explicitement, sur un champ externe à la table
1972 * Décrit un critère non déclaré comme {id_article} {id_article>3} qui correspond
1973 * à un champ non présent dans la table, et donc à retrouver par jointure si possible.
1975 * @param Boucle $boucle Description de la boucle
1976 * @param Critere $crit Paramètres du critère dans cette boucle
1977 * @param string $op L'opérateur utilisé, tel que '='
1978 * @param array $desc Description de la table
1979 * @param string $col Nom de la colonne à trouver (la véritable)
1980 * @param string $col_alias Alias de la colonne éventuel utilisé dans le critère ex: id_enfant
1981 * @param string $table Nom de la table SQL de la boucle
1982 * @return array|string
1983 * Liste si jointure possible :
1985 * - string $col_alias
1990 * Chaîne vide si on ne trouve pas le champ par jointure...
1992 function calculer_critere_infixe_externe($boucle, $crit, $op, $desc, $col, $col_alias, $table) {
1996 $calculer_critere_externe = 'calculer_critere_externe_init';
1997 // gestion par les plugins des jointures tordues
1998 // pas automatiques mais necessaires
1999 $table_sql = table_objet_sql($table);
2000 if (isset($GLOBALS['exceptions_des_jointures'][$table_sql])
2001 and is_array($GLOBALS['exceptions_des_jointures'][$table_sql])
2004 isset($GLOBALS['exceptions_des_jointures'][$table_sql][$col])
2006 isset($GLOBALS['exceptions_des_jointures'][$table_sql][''])
2009 $t = $GLOBALS['exceptions_des_jointures'][$table_sql];
2010 $index = isset($t[$col])
2011 ?
$t[$col] : (isset($t['']) ?
$t[''] : array());
2013 if (count($index) == 3) {
2014 list($t, $col, $calculer_critere_externe) = $index;
2015 } elseif (count($index) == 2) {
2016 list($t, $col) = $t[$col];
2017 } elseif (count($index) == 1) {
2018 list($calculer_critere_externe) = $index;
2022 } // jointure non declaree. La trouver.
2023 } elseif (isset($GLOBALS['exceptions_des_jointures'][$col])) {
2024 list($t, $col) = $GLOBALS['exceptions_des_jointures'][$col];
2027 } // jointure non declaree. La trouver.
2029 // ici on construit le from pour fournir $col en piochant dans les jointures
2031 // si des jointures explicites sont fournies, on cherche d'abord dans celles ci
2032 // permet de forcer une table de lien quand il y a ambiguite
2033 // <BOUCLE_(DOCUMENTS documents_liens){id_mot}>
2034 // alors que <BOUCLE_(DOCUMENTS){id_mot}> produit la meme chose que <BOUCLE_(DOCUMENTS mots_liens){id_mot}>
2036 if ($boucle->jointures_explicites
) {
2037 $jointures_explicites = explode(' ', $boucle->jointures_explicites
);
2038 $table = $calculer_critere_externe($boucle, $jointures_explicites, $col, $desc, ($crit->cond
or $op != '='), $t);
2041 // et sinon on cherche parmi toutes les jointures declarees
2043 $table = $calculer_critere_externe($boucle, $boucle->jointures
, $col, $desc, ($crit->cond
or $op != '='), $t);
2050 // il ne reste plus qu'a trouver le champ dans les from
2051 list($nom, $desc, $cle) = trouver_champ_exterieur($col, $boucle->from
, $boucle);
2053 if (count($cle) > 1 or reset($cle) !== $col) {
2054 $col_alias = $col; // id_article devient juste le nom d'origine
2055 if (count($cle) > 1 and reset($cle) == 'id_objet') {
2056 $e = decompose_champ_id_objet($col);
2057 $col = array_shift($e);
2058 $where = primary_doublee($e, $table);
2064 return array($col, $col_alias, $table, $where, $desc);
2069 * Calcule une condition WHERE entre un nom du champ et une valeur
2071 * Ne pas appliquer sql_quote lors de la compilation,
2072 * car on ne connait pas le serveur SQL
2074 * @todo Ce nom de fonction n'est pas très clair ?
2076 * @param array $decompose Liste nom du champ, code PHP pour obtenir la valeur
2077 * @param string $table Nom de la table
2079 * Liste de 3 éléments pour une description where du compilateur :
2084 function primary_doublee($decompose, $table) {
2085 $e1 = reset($decompose);
2086 $e2 = "sql_quote('" . end($decompose) . "')";
2088 return array("'='", "'$table." . $e1 . "'", $e2);
2092 * Champ hors table, ça ne peut être qu'une jointure.
2094 * On cherche la table du champ et on regarde si elle est déjà jointe
2095 * Si oui et qu'on y cherche un champ nouveau, pas de jointure supplementaire
2096 * Exemple: criteres {titre_mot=...}{type_mot=...}
2097 * Dans les 2 autres cas ==> jointure
2098 * (Exemple: criteres {type_mot=...}{type_mot=...} donne 2 jointures
2099 * pour selectioner ce qui a exactement ces 2 mots-cles.
2101 * @param Boucle $boucle
2102 * Description de la boucle
2103 * @param array $joints
2104 * Liste de jointures possibles (ex: $boucle->jointures ou $boucle->jointures_explicites)
2105 * @param string $col
2106 * Colonne cible de la jointure
2107 * @param array $desc
2108 * Description de la table
2110 * Flag pour savoir si le critère est conditionnel ou non
2111 * @param bool|string $checkarrivee
2112 * string : nom de la table jointe où on veut trouver le champ.
2113 * n'a normalement pas d'appel sans $checkarrivee.
2115 * Alias de la table de jointure (Lx)
2118 function calculer_critere_externe_init(&$boucle, $joints, $col, $desc, $cond, $checkarrivee = false) {
2119 // si on demande un truc du genre spip_mots
2120 // avec aussi spip_mots_liens dans les jointures dispo
2122 // il faut privilegier la jointure directe en 2 etapes spip_mots_liens, spip_mots
2124 and is_string($checkarrivee)
2125 and $a = table_objet($checkarrivee)
2126 and in_array($a . '_liens', $joints)
2128 if ($res = calculer_lien_externe_init($boucle, $joints, $col, $desc, $cond, $checkarrivee)) {
2132 foreach ($joints as $joint) {
2133 if ($arrivee = trouver_champ_exterieur($col, array($joint), $boucle, $checkarrivee)) {
2134 // alias de table dans le from
2135 $t = array_search($arrivee[0], $boucle->from
);
2136 // recuperer la cle id_xx eventuellement decomposee en (id_objet,objet)
2137 $cols = $arrivee[2];
2138 // mais on ignore la 3eme cle si presente qui correspond alors au point de depart
2139 if (count($cols) > 2) {
2143 // la table est déjà dans le FROM, on vérifie si le champ est utilisé.
2145 foreach ($cols as $col) {
2146 $c = '/\b' . $t . ".$col" . '\b/';
2147 if (trouver_champ($c, $boucle->where
)) {
2150 // mais ca peut etre dans le FIELD pour le Having
2151 $c = "/FIELD.$t" . ".$col,/";
2152 if (trouver_champ($c, $boucle->select
)) {
2161 array_pop($arrivee);
2162 if ($res = calculer_jointure($boucle, array($boucle->id_table
, $desc), $arrivee, $cols, $cond, 1)) {
2173 * Générer directement une jointure via une table de lien spip_xxx_liens
2174 * pour un critère {id_xxx}
2176 * @todo $checkarrivee doit être obligatoire ici ?
2178 * @param Boucle $boucle
2179 * Description de la boucle
2180 * @param array $joints
2181 * Liste de jointures possibles (ex: $boucle->jointures ou $boucle->jointures_explicites)
2182 * @param string $col
2183 * Colonne cible de la jointure
2184 * @param array $desc
2185 * Description de la table
2187 * Flag pour savoir si le critère est conditionnel ou non
2188 * @param bool|string $checkarrivee
2189 * string : nom de la table jointe où on veut trouver le champ.
2190 * n'a normalement pas d'appel sans $checkarrivee.
2192 * Alias de la table de jointure (Lx)
2194 function calculer_lien_externe_init(&$boucle, $joints, $col, $desc, $cond, $checkarrivee = false) {
2195 $primary_arrivee = id_table_objet($checkarrivee);
2197 // [FIXME] $checkarrivee peut-il arriver avec false ????
2198 $intermediaire = trouver_champ_exterieur($primary_arrivee, $joints, $boucle, $checkarrivee . "_liens");
2199 $arrivee = trouver_champ_exterieur($col, $joints, $boucle, $checkarrivee);
2201 if (!$intermediaire or !$arrivee) {
2204 array_pop($intermediaire); // enlever la cle en 3eme argument
2205 array_pop($arrivee); // enlever la cle en 3eme argument
2207 $res = fabrique_jointures($boucle,
2212 array(id_table_objet($desc['table_objet']), 'id_objet', 'objet', $desc['type'])
2214 array(reset($intermediaire), $arrivee, $primary_arrivee)
2215 ), $cond, $desc, $boucle->id_table
, array($col));
2222 * Recherche la présence d'un champ dans une valeur de tableau
2224 * @param string $champ
2225 * Expression régulière pour trouver un champ donné.
2226 * Exemple : /\barticles.titre\b/
2227 * @param array $where
2228 * Tableau de valeurs dans lesquels chercher le champ.
2230 * true si le champ est trouvé quelque part dans $where
2233 function trouver_champ($champ, $where) {
2234 if (!is_array($where)) {
2235 return preg_match($champ, $where);
2237 foreach ($where as $clause) {
2238 if (trouver_champ($champ, $clause)) {
2249 * Détermine l'operateur et les opérandes d'un critère non déclaré
2251 * Lorsque l'opérateur n'est pas explicite comme sur {id_article>0} c'est
2252 * l'opérateur '=' qui est utilisé.
2254 * Traite les cas particuliers id_parent, id_enfant, date, lang
2256 * @param string $idb Identifiant de la boucle
2257 * @param array $boucles AST du squelette
2258 * @param Critere $crit Paramètres du critère dans cette boucle
2261 * - string $fct Nom d'une fonction SQL sur le champ ou vide (ex: SUM)
2262 * - string $col Nom de la colonne SQL utilisée
2263 * - string $op Opérateur
2265 * Liste de codes PHP obtenant les valeurs des comparaisons (ex: id_article sur la boucle parente)
2266 * Souvent un tableau d'un seul élément.
2267 * - string $args_sql Suite des arguments du critère. ?
2269 function calculer_critere_infixe_ops($idb, &$boucles, $crit) {
2270 // cas d'une valeur comparee a elle-meme ou son referent
2271 if (count($crit->param
) == 0) {
2273 $col = $val = $crit->op
;
2274 if (preg_match('/^(.*)\.(.*)$/', $col, $r)) {
2277 // Cas special {lang} : aller chercher $GLOBALS['spip_lang']
2278 if ($val == 'lang') {
2279 $val = array(kwote('$GLOBALS[\'spip_lang\']'));
2282 if ($val == 'id_parent') {
2283 // Si id_parent, comparer l'id_parent avec l'id_objet
2284 // de la boucle superieure.... faudrait verifier qu'il existe
2285 // pour eviter l'erreur SQL
2286 $val = $boucles[$idb]->primary
;
2287 // mais si pas de boucle superieure, prendre id_parent dans l'env
2288 $defaut = "@\$Pile[0]['id_parent']";
2289 } elseif ($val == 'id_enfant') {
2290 // Si id_enfant, comparer l'id_objet avec l'id_parent
2291 // de la boucle superieure
2293 } elseif ($crit->cond
and ($col == "date" or $col == "date_redac")) {
2294 // un critere conditionnel sur date est traite a part
2295 // car la date est mise d'office par SPIP,
2296 $defaut = "(\$Pile[0]['{$col}_default']?'':\$Pile[0]['" . $col . "'])";
2299 $val = calculer_argument_precedent($idb, $val, $boucles, $defaut);
2300 $val = array(kwote($val));
2303 // comparaison explicite
2304 // le phraseur impose que le premier param soit du texte
2305 $params = $crit->param
;
2310 $col = array_shift($params);
2311 $col = $col[0]->texte
;
2314 $desc = array('id_mere' => $idb);
2315 $parent = $boucles[$idb]->id_parent
;
2317 // Dans le cas {x=='#DATE'} etc, defaire le travail du phraseur,
2318 // celui ne sachant pas ce qu'est un critere infixe
2319 // et a fortiori son 2e operande qu'entoure " ou '
2320 if (count($params) == 1
2321 and count($params[0]) == 3
2322 and $params[0][0]->type
== 'texte'
2323 and $params[0][2]->type
== 'texte'
2324 and ($p = $params[0][0]->texte
) == $params[0][2]->texte
2325 and (($p == "'") or ($p == '"'))
2326 and $params[0][1]->type
== 'champ'
2328 $val[] = "$p\\$p#" . $params[0][1]->nom_champ
. "\\$p$p";
2330 foreach ((($op != 'IN') ?
$params : calculer_vieux_in($params)) as $p) {
2331 $a = calculer_liste($p, $desc, $boucles, $parent);
2332 if (strcasecmp($op, 'IN') == 0) {
2335 $val[] = kwote($a, $boucles[$idb]->sql_serveur
, '@@defaultcast@@');
2336 } // toujours quoter en char ici
2341 $fct = $args_sql = '';
2343 // chercher FONCTION(champ) tel que CONCAT(titre,descriptif)
2344 if (preg_match('/^(.*)' . SQL_ARGS
. '$/', $col, $m)) {
2346 preg_match('/^\(([^,]*)(.*)\)$/', $m[2], $a);
2348 if (preg_match('/^(\S*)(\s+AS\s+.*)$/i', $col, $m)) {
2355 return array($fct, $col, $op, $val, $args_sql);
2358 // compatibilite ancienne version
2360 // http://code.spip.net/@calculer_vieux_in
2361 function calculer_vieux_in($params) {
2362 $deb = $params[0][0];
2363 $k = count($params) - 1;
2364 $last = $params[$k];
2365 $j = count($last) - 1;
2367 $n = isset($last->texte
) ?
strlen($last->texte
) : 0;
2369 if (!((isset($deb->texte
[0]) and $deb->texte
[0] == '(')
2370 && (isset($last->texte
[$n - 1]) and $last->texte
[$n - 1] == ')'))
2374 $params[0][0]->texte
= substr($deb->texte
, 1);
2375 // attention, on peut avoir k=0,j=0 ==> recalculer
2376 $last = $params[$k][$j];
2377 $n = strlen($last->texte
);
2378 $params[$k][$j]->texte
= substr($last->texte
, 0, $n - 1);
2380 foreach ($params as $v) {
2381 if ($v[0]->type
!= 'texte') {
2384 foreach (explode(',', $v[0]->texte
) as $x) {
2387 $newp[] = array($t);
2396 * Calcule les cas particuliers de critères de date
2398 * Lorsque la colonne correspond à un critère de date, tel que
2399 * jour, jour_relatif, jour_x, age, age_relatif, age_x...
2401 * @param string $idb Identifiant de la boucle
2402 * @param array $boucles AST du squelette
2403 * @param string $col Nom du champ demandé
2404 * @return string|array
2405 * chaine vide si ne correspond pas à une date,
2407 * - expression SQL de calcul de la date,
2408 * - nom de la colonne de date (si le calcul n'est pas relatif)
2410 function calculer_critere_infixe_date($idb, &$boucles, $col) {
2411 if (!preg_match(",^((age|jour|mois|annee)_relatif|date|mois|annee|jour|heure|age)(_[a-z_]+)?$,", $col, $regs)) {
2415 $boucle = $boucles[$idb];
2416 $table = $boucle->show
;
2418 // si c'est une colonne de la table, ne rien faire
2419 if (isset($table['field'][$col])) {
2423 if (!$table['date'] && !isset($GLOBALS['table_date'][$table['id_table']])) {
2426 $pred = $date_orig = isset($GLOBALS['table_date'][$table['id_table']]) ?
$GLOBALS['table_date'][$table['id_table']] : $table['date'];
2429 if (isset($regs[3]) and $suite = $regs[3]) {
2430 # Recherche de l'existence du champ date_xxxx,
2431 # si oui choisir ce champ, sinon choisir xxxx
2433 if (isset($table['field']["date$suite"])) {
2434 $date_orig = 'date' . $suite;
2436 $date_orig = substr($suite, 1);
2440 if (isset($regs[2]) and $rel = $regs[2]) {
2445 $date_compare = "\"' . normaliser_date(" .
2446 calculer_argument_precedent($idb, $pred, $boucles) .
2449 $col_vraie = $date_orig;
2450 $date_orig = $boucle->id_table
. '.' . $date_orig;
2457 $col = "DAYOFMONTH($date_orig)";
2460 $col = "MONTH($date_orig)";
2463 $col = "YEAR($date_orig)";
2466 $col = "DATE_FORMAT($date_orig, \\'%H:%i\\')";
2469 $col = calculer_param_date("NOW()", $date_orig);
2470 $col_vraie = "";// comparer a un int (par defaut)
2473 $col = calculer_param_date($date_compare, $date_orig);
2474 $col_vraie = "";// comparer a un int (par defaut)
2476 case 'jour_relatif':
2477 $col = "(TO_DAYS(" . $date_compare . ")-TO_DAYS(" . $date_orig . "))";
2478 $col_vraie = "";// comparer a un int (par defaut)
2480 case 'mois_relatif':
2481 $col = "MONTH(" . $date_compare . ")-MONTH(" .
2482 $date_orig . ")+12*(YEAR(" . $date_compare .
2483 ")-YEAR(" . $date_orig . "))";
2484 $col_vraie = "";// comparer a un int (par defaut)
2486 case 'annee_relatif':
2487 $col = "YEAR(" . $date_compare . ")-YEAR(" .
2489 $col_vraie = "";// comparer a un int (par defaut)
2493 return array($col, $col_vraie);
2497 * Calcule l'expression SQL permettant de trouver un nombre de jours écoulés.
2499 * Le calcul SQL retournera un nombre de jours écoulés entre la date comparée
2500 * et la colonne SQL indiquée
2502 * @param string $date_compare
2503 * Code PHP permettant d'obtenir le timestamp référent.
2504 * C'est à partir de lui que l'on compte les jours
2505 * @param string $date_orig
2506 * Nom de la colonne SQL qui possède la date
2508 * Expression SQL calculant le nombre de jours écoulé entre une valeur
2509 * de colonne SQL et une date.
2511 function calculer_param_date($date_compare, $date_orig) {
2512 if (preg_match(",'\" *\.(.*)\. *\"',", $date_compare, $r)) {
2513 $init = "'\" . (\$x = $r[1]) . \"'";
2514 $date_compare = '\'$x\'';
2516 $init = $date_compare;
2520 // optimisation : mais prevoir le support SQLite avant
2521 "TIMESTAMPDIFF(HOUR,$date_orig,$init)/24";
2525 * Compile le critère {source} d'une boucle DATA
2527 * Permet de déclarer le mode d'obtention des données dans une boucle
2528 * DATA (premier argument) et les données (la suite).
2531 * (DATA){source mode, "xxxxxx", arg, arg, arg}
2532 * (DATA){source tableau, #LISTE{un,deux,trois}}
2534 * @param string $idb Identifiant de la boucle
2535 * @param array $boucles AST du squelette
2536 * @param Critere $crit Paramètres du critère dans cette boucle
2538 function critere_DATA_source_dist($idb, &$boucles, $crit) {
2539 $boucle = &$boucles[$idb];
2542 foreach ($crit->param
as &$param) {
2544 calculer_liste($param, array(), $boucles, $boucles[$idb]->id_parent
));
2548 $command[\'sourcemode\'] = ' . array_shift($args) . ";\n";
2551 $command[\'source\'] = array(' . join(', ', $args) . ");\n";
2556 * Compile le critère {datasource} d'une boucle DATA
2558 * Permet de déclarer le mode d'obtention des données dans une boucle DATA
2560 * @deprecated Utiliser directement le critère {source}
2562 * @param string $idb Identifiant de la boucle
2563 * @param array $boucles AST du squelette
2564 * @param Critere $crit Paramètres du critère dans cette boucle
2566 function critere_DATA_datasource_dist($idb, &$boucles, $crit) {
2567 $boucle = &$boucles[$idb];
2569 $command[\'source\'] = array(' . calculer_liste($crit->param
[0], array(), $boucles, $boucles[$idb]->id_parent
) . ');
2570 $command[\'sourcemode\'] = ' . calculer_liste($crit->param
[1], array(), $boucles, $boucles[$idb]->id_parent
) . ';';
2575 * Compile le critère {datacache} d'une boucle DATA
2577 * Permet de transmettre une durée de cache (time to live) utilisée
2578 * pour certaines sources d'obtention des données (par exemple RSS),
2579 * indiquant alors au bout de combien de temps la donnée est à réobtenir.
2581 * La durée par défaut est 1 journée.
2583 * @param string $idb Identifiant de la boucle
2584 * @param array $boucles AST du squelette
2585 * @param Critere $crit Paramètres du critère dans cette boucle
2587 function critere_DATA_datacache_dist($idb, &$boucles, $crit) {
2588 $boucle = &$boucles[$idb];
2590 $command[\'datacache\'] = ' . calculer_liste($crit->param
[0], array(), $boucles, $boucles[$idb]->id_parent
) . ';';
2595 * Compile le critère {args} d'une boucle PHP
2597 * Permet de passer des arguments à un iterateur non-spip
2598 * (PHP:xxxIterator){args argument1, argument2, argument3}
2600 * @param string $idb Identifiant de la boucle
2601 * @param array $boucles AST du squelette
2602 * @param Critere $crit Paramètres du critère dans cette boucle
2604 function critere_php_args_dist($idb, &$boucles, $crit) {
2605 $boucle = &$boucles[$idb];
2606 $boucle->hash
.= '$command[\'args\']=array();';
2607 foreach ($crit->param
as $param) {
2609 $command[\'args\'][] = ' . calculer_liste($param, array(), $boucles, $boucles[$idb]->id_parent
) . ';';
2614 * Compile le critère {liste} d'une boucle DATA
2616 * Passe une liste de données à l'itérateur DATA
2619 * (DATA){liste X1, X2, X3}
2620 * équivalent à (DATA){source tableau,#LISTE{X1, X2, X3}}
2622 * @param string $idb Identifiant de la boucle
2623 * @param array $boucles AST du squelette
2624 * @param Critere $crit Paramètres du critère dans cette boucle
2626 function critere_DATA_liste_dist($idb, &$boucles, $crit) {
2627 $boucle = &$boucles[$idb];
2628 $boucle->hash
.= "\n\t" . '$command[\'liste\'] = array();' . "\n";
2629 foreach ($crit->param
as $param) {
2630 $boucle->hash
.= "\t" . '$command[\'liste\'][] = ' . calculer_liste($param, array(), $boucles,
2631 $boucles[$idb]->id_parent
) . ";\n";
2636 * Compile le critère {enum} d'une boucle DATA
2638 * Passe les valeurs de début et de fin d'une énumération, qui seront
2639 * vues comme une liste d'autant d'éléments à parcourir pour aller du
2642 * Cela utilisera la fonction range() de PHP.
2645 * (DATA){enum Xdebut, Xfin}
2648 * (DATA){enum 1.0,9.2}
2650 * @link http://php.net/manual/fr/function.range.php
2652 * @param string $idb Identifiant de la boucle
2653 * @param array $boucles AST du squelette
2654 * @param Critere $crit Paramètres du critère dans cette boucle
2656 function critere_DATA_enum_dist($idb, &$boucles, $crit) {
2657 $boucle = &$boucles[$idb];
2658 $boucle->hash
.= "\n\t" . '$command[\'enum\'] = array();' . "\n";
2659 foreach ($crit->param
as $param) {
2660 $boucle->hash
.= "\t" . '$command[\'enum\'][] = ' . calculer_liste($param, array(), $boucles,
2661 $boucles[$idb]->id_parent
) . ";\n";
2666 * Compile le critère {datapath} d'une boucle DATA
2668 * Extrait un chemin d'un tableau de données
2670 * (DATA){datapath query.results}
2672 * @param string $idb Identifiant de la boucle
2673 * @param array $boucles AST du squelette
2674 * @param Critere $crit Paramètres du critère dans cette boucle
2676 function critere_DATA_datapath_dist($idb, &$boucles, $crit) {
2677 $boucle = &$boucles[$idb];
2678 foreach ($crit->param
as $param) {
2680 $command[\'datapath\'][] = ' . calculer_liste($param, array(), $boucles, $boucles[$idb]->id_parent
) . ';';
2686 * Compile le critère {si}
2688 * Le critère {si condition} est applicable à toutes les boucles et conditionne
2689 * l'exécution de la boucle au résultat de la condition. La partie alternative
2690 * de la boucle est alors affichée si une condition n'est pas remplie (comme
2691 * lorsque la boucle ne ramène pas de résultat).
2692 * La différence étant que si la boucle devait réaliser une requête SQL
2693 * (par exemple une boucle ARTICLES), celle ci n'est pas réalisée si la
2694 * condition n'est pas remplie.
2696 * Les valeurs de la condition sont forcément extérieures à cette boucle
2697 * (sinon il faudrait l'exécuter pour connaître le résultat, qui doit tester
2698 * si on exécute la boucle !)
2700 * Si plusieurs critères {si} sont présents, ils sont cumulés :
2701 * si une seule des conditions n'est pas vérifiée, la boucle n'est pas exécutée.
2704 * {si #ENV{exec}|=={article}}
2705 * {si (#_contenu:GRAND_TOTAL|>{10})}
2706 * {si #AUTORISER{voir,articles}}
2708 * @param string $idb Identifiant de la boucle
2709 * @param array $boucles AST du squelette
2710 * @param Critere $crit Paramètres du critère dans cette boucle
2712 function critere_si_dist($idb, &$boucles, $crit) {
2713 $boucle = &$boucles[$idb];
2714 // il faut initialiser 1 fois le tableau a chaque appel de la boucle
2715 // (par exemple lorsque notre boucle est appelee dans une autre boucle)
2716 // mais ne pas l'initialiser n fois si il y a n criteres {si } dans la boucle !
2717 $boucle->hash
.= "\n\tif (!isset(\$si_init)) { \$command['si'] = array(); \$si_init = true; }\n";
2719 foreach ($crit->param
as $param) {
2720 $boucle->hash
.= "\t\$command['si'][] = "
2721 . calculer_liste($param, array(), $boucles, $boucles[$idb]->id_parent
) . ";\n";
2723 // interdire {si 0} aussi !
2725 $boucle->hash
.= '$command[\'si\'][] = 0;';
2730 * Compile le critère {tableau} d'une boucle POUR
2732 * {tableau #XX} pour compatibilite ascendante boucle POUR
2733 * ... préférer la notation (DATA){source tableau,#XX}
2735 * @deprecated Utiliser une boucle (DATA){source tableau,#XX}
2737 * @param string $idb Identifiant de la boucle
2738 * @param array $boucles AST du squelette
2739 * @param Critere $crit Paramètres du critère dans cette boucle
2741 function critere_POUR_tableau_dist($idb, &$boucles, $crit) {
2742 $boucle = &$boucles[$idb];
2744 $command[\'source\'] = array(' . calculer_liste($crit->param
[0], array(), $boucles, $boucles[$idb]->id_parent
) . ');
2745 $command[\'sourcemode\'] = \'table\';';
2750 * Compile le critère {noeud}
2752 * Trouver tous les objets qui ont des enfants (les noeuds de l'arbre)
2754 * {!noeud} retourne les feuilles
2756 * @global array $exceptions_des_tables
2758 * @param string $idb Identifiant de la boucle
2759 * @param array $boucles AST du squelette
2760 * @param Critere $crit Paramètres du critère dans cette boucle
2762 function critere_noeud_dist($idb, &$boucles, $crit) {
2765 $boucle = &$boucles[$idb];
2766 $primary = $boucle->primary
;
2768 if (!$primary or strpos($primary, ',')) {
2769 erreur_squelette(_T('zbug_doublon_sur_table_sans_cle_primaire'), $boucle);
2773 $table = $boucle->type_requete
;
2774 $table_sql = table_objet_sql(objet_type($table));
2776 $id_parent = isset($GLOBALS['exceptions_des_tables'][$boucle->id_table
]['id_parent']) ?
2777 $GLOBALS['exceptions_des_tables'][$boucle->id_table
]['id_parent'] :
2781 $where = array("'IN'", "'$boucle->id_table." . "$primary'", "'('.sql_get_select('$id_parent', '$table_sql').')'");
2783 $where = array("'NOT'", $where);
2786 $boucle->where
[] = $where;
2790 * Compile le critère {feuille}
2792 * Trouver tous les objets qui n'ont pas d'enfants (les feuilles de l'arbre)
2794 * {!feuille} retourne les noeuds
2796 * @global array $exceptions_des_tables
2797 * @param string $idb Identifiant de la boucle
2798 * @param array $boucles AST du squelette
2799 * @param Critere $crit Paramètres du critère dans cette boucle
2801 function critere_feuille_dist($idb, &$boucles, $crit) {
2803 $crit->not
= $not ?
false : true;
2804 critere_noeud_dist($idb, $boucles, $crit);