3 /***************************************************************************\
4 * SPIP, Systeme de publication pour l'internet *
6 * Copyright (c) 2001-2017 *
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 $c = "($arg ? $c : 1)";
555 $boucle->where
[] = array("'NOT'", $c);
557 $boucle->where
[] = $c;
563 * Compile le critère `fusion` qui regroupe les éléments selon une colonne.
565 * C'est la commande SQL «GROUP BY»
568 * @link http://www.spip.net/5166
571 * <BOUCLE_a(articles){fusion lang}>
574 * @param string $idb Identifiant de la boucle
575 * @param array $boucles AST du squelette
576 * @param Critere $crit Paramètres du critère dans cette boucle
579 function critere_fusion_dist($idb, &$boucles, $crit) {
580 if ($t = isset($crit->param
[0])) {
581 $t = $crit->param
[0];
582 if ($t[0]->type
== 'texte') {
584 if (preg_match("/^(.*)\.(.*)$/", $t, $r)) {
585 $t = table_objet_sql($r[1]);
586 $t = array_search($t, $boucles[$idb]->from
);
593 . calculer_critere_arg_dynamique($idb, $boucles, $t)
598 $boucles[$idb]->group
[] = $t;
599 if (!in_array($t, $boucles[$idb]->select
)) {
600 $boucles[$idb]->select
[] = $t;
603 return (array('zbug_critere_inconnu', array('critere' => $crit->op
. ' ?')));
608 * Compile le critère `{collecte}` qui permet de spécifier l'interclassement
609 * à utiliser pour les tris de la boucle.
611 * Cela permet avec le critère `{par}` de trier un texte selon
612 * l'interclassement indiqué. L'instruction s'appliquera sur les critères `{par}`
613 * qui succèdent ce critère, ainsi qu'au critère `{par}` précédent
614 * si aucun interclassement ne lui est déjà appliqué.
616 * Techniquement, c'est la commande SQL "COLLATE" qui utilisée.
617 * (elle peut être appliquée sur les order by, group by, where, like ...)
620 * - `{par titre}{collecte utf8_spanish_ci}` ou `{collecte utf8_spanish_ci}{par titre}`
621 * - `{par titre}{par surtitre}{collecte utf8_spanish_ci}` :
622 * Seul 'surtitre' (`par` précédent) utilisera l'interclassement
623 * - `{collecte utf8_spanish_ci}{par titre}{par surtitre}` :
624 * 'titre' et 'surtitre' utiliseront l'interclassement (tous les `par` suivants)
627 * Piège sur une éventuelle écriture peu probable :
628 * `{par a}{collecte c1}{par b}{collecte c2}` : le tri `{par b}`
629 * utiliserait l'interclassement c1 (et non c2 qui ne s'applique pas
630 * au `par` précédent s'il a déjà un interclassement demandé).
633 * @link http://www.spip.net/4028
634 * @see critere_par_dist() Le critère `{par}`
636 * @param string $idb Identifiant de la boucle
637 * @param array $boucles AST du squelette
638 * @param Critere $crit Paramètres du critère dans cette boucle
640 function critere_collecte_dist($idb, &$boucles, $crit) {
641 if (isset($crit->param
[0])) {
642 $_coll = calculer_liste($crit->param
[0], array(), $boucles, $boucles[$idb]->id_parent
);
643 $boucle = $boucles[$idb];
644 $boucle->modificateur
['collate'] = "($_coll ?' COLLATE '.$_coll:'')";
645 $n = count($boucle->order
);
646 if ($n && (strpos($boucle->order
[$n - 1], 'COLLATE') === false)) {
647 // l'instruction COLLATE doit être placée avant ASC ou DESC
648 // notamment lors de l'utilisation `{!par xxx}{collate yyy}`
650 (false !== $i = strpos($boucle->order
[$n - 1], 'ASC'))
651 OR (false !== $i = strpos($boucle->order
[$n - 1], 'DESC'))
653 $boucle->order
[$n - 1] = substr_replace($boucle->order
[$n - 1], "' . " . $boucle->modificateur
['collate'] . " . ' ", $i, 0);
655 $boucle->order
[$n - 1] .= " . " . $boucle->modificateur
['collate'];
659 return (array('zbug_critere_inconnu', array('critere' => $crit->op
. " " . count($boucles[$idb]->order
))));
663 // http://code.spip.net/@calculer_critere_arg_dynamique
664 function calculer_critere_arg_dynamique($idb, &$boucles, $crit, $suffix = '') {
665 $boucle = $boucles[$idb];
666 $alt = "('" . $boucle->id_table
. '.\' . $x' . $suffix . ')';
667 $var = '$champs_' . $idb;
668 $desc = (strpos($boucle->in
, "static $var =") !== false);
670 $desc = $boucle->show
['field'];
671 $desc = implode(',', array_map('_q', array_keys($desc)));
672 $boucles[$idb]->in
.= "\n\tstatic $var = array(" . $desc . ");";
675 $alt = "(in_array(\$x, $var) ? $alt :(\$x$suffix))";
677 $arg = calculer_liste($crit, array(), $boucles, $boucle->id_parent
);
679 return "((\$x = preg_replace(\"/\\W/\",'', $arg)) ? $alt : '')";
683 * Compile le critère `{par}` qui permet d'ordonner les résultats d'une boucle
685 * Demande à trier la boucle selon certains champs (en SQL, la commande `ORDER BY`).
686 * Si plusieurs tris sont demandés (plusieurs fois le critère `{par x}{par y}` dans une boucle ou plusieurs champs
687 * séparés par des virgules dans le critère `{par x, y, z}`), ils seront appliqués dans l'ordre.
689 * Quelques particularités :
690 * - `{par hasard}` : trie par hasard
691 * - `{par num titre}` : trie par numéro de titre
692 * - `{par multi titre}` : trie par la langue extraite d'une balise polyglotte `<multi>` sur le champ titre
693 * - `{!par date}` : trie par date inverse en utilisant le champ date principal déclaré pour la table (si c'est un objet éditorial).
694 * - `{!par points}` : trie par pertinence de résultat de recherche (avec le critère `{recherche}`)
695 * - `{par FUNCTION_SQL(n)}` : trie en utilisant une fonction SQL (peut dépendre du moteur SQL utilisé).
696 * Exemple : `{par SUBSTRING_INDEX(titre, ".", -1)}` (tri ~ alphabétique en ignorant les numéros de titres
697 * (exemple erroné car faux dès qu'un titre possède un point.)).
698 * - `{par table.champ}` : trie en effectuant une jointure sur la table indiquée.
699 * - `{par #BALISE}` : trie sur la valeur retournée par la balise (doit être un champ de la table, ou 'hasard').
704 * - `{par num titre, multi titre, hasard}`
707 * @link http://www.spip.net/5531
708 * @see critere_tri_dist() Le critère `{tri ...}`
709 * @see critere_inverse_dist() Le critère `{inverse}`
711 * @uses critere_parinverse()
713 * @param string $idb Identifiant de la boucle
714 * @param array $boucles AST du squelette
715 * @param Critere $crit Paramètres du critère dans cette boucle
717 function critere_par_dist($idb, &$boucles, $crit) {
718 return critere_parinverse($idb, $boucles, $crit);
722 * Calculs pour le critère `{par}` ou `{inverse}` pour ordonner les résultats d'une boucle
724 * Les expressions intermédiaires `{par expr champ}` sont calculées dans des fonctions
725 * `calculer_critere_par_expression_{expr}()` notamment `{par num champ}` ou `{par multi champ}`.
727 * @see critere_par_dist() Le critère `{par}` pour des exemples
729 * @uses calculer_critere_arg_dynamique() pour le calcul de `{par #ENV{tri}}`
730 * @uses calculer_critere_par_hasard() pour le calcul de `{par hasard}`
731 * @uses calculer_critere_par_champ()
732 * @see calculer_critere_par_expression_num() pour le calcul de `{par num x}`
733 * @see calculer_critere_par_expression_multi() pour le calcul de `{par multi x}`
735 * @param string $idb Identifiant de la boucle
736 * @param array $boucles AST du squelette
737 * @param Critere $crit Paramètres du critère dans cette boucle
739 function critere_parinverse($idb, &$boucles, $crit) {
740 $boucle = &$boucles[$idb];
742 $sens = $collecte = '';
744 $sens = " . ' DESC'";
746 if (isset($boucle->modificateur
['collate'])) {
747 $collecte = ' . ' . $boucle->modificateur
['collate'];
750 // Pour chaque paramètre du critère
751 foreach ($crit->param
as $tri) {
753 // tris specifiés dynamiquement {par #ENV{tri}}
754 if ($tri[0]->type
!= 'texte') {
755 // calculer le order dynamique qui verifie les champs
756 $order = calculer_critere_arg_dynamique($idb, $boucles, $tri, $sens);
757 // ajouter 'hasard' comme possibilité de tri dynamique
758 calculer_critere_par_hasard($idb, $boucles, $crit);
760 // tris textuels {par titre}
762 $par = array_shift($tri);
765 // tris de la forme {par expression champ} tel que {par num titre} ou {par multi titre}
766 if (preg_match(",^(\w+)[\s]+(.*)$,", $par, $m)) {
767 $expression = trim($m[1]);
768 $champ = trim($m[2]);
769 if (function_exists($f = 'calculer_critere_par_expression_' . $expression)) {
770 $order = $f($idb, $boucles, $crit, $tri, $champ);
772 return array('zbug_critere_inconnu', array('critere' => $crit->op
. " $par"));
775 // tris de la forme {par champ} ou {par FONCTION(champ)}
776 } elseif (preg_match(",^" . CHAMP_SQL_PLUS_FONC
. '$,is', $par, $match)) {
777 // {par FONCTION(champ)}
778 if (count($match) > 2) {
779 $par = substr($match[2], 1, -1);
782 // quelques cas spécifiques {par hasard}, {par date}
783 if ($par == 'hasard') {
784 $order = calculer_critere_par_hasard($idb, $boucles, $crit);
785 } elseif ($par == 'date' and !empty($boucle->show
['date'])) {
786 $order = "'" . $boucle->id_table
. "." . $boucle->show
['date'] . "'";
788 // cas général {par champ}, {par table.champ}, ...
789 $order = calculer_critere_par_champ($idb, $boucles, $crit, $par);
793 // on ne sait pas traiter…
795 return array('zbug_critere_inconnu', array('critere' => $crit->op
. " $par"));
798 // En cas d'erreur de squelette retournée par une fonction
799 if (is_array($order)) {
804 if (preg_match('/^\'([^"]*)\'$/', $order, $m)) {
806 if (strpos($t, '.') and !in_array($t, $boucle->select
)) {
807 $boucle->select
[] = $t;
814 if (preg_match("/^\s*'(.*)'\s*$/", $order, $r)) {
815 $order = "'$fct(" . $r[1] . ")'";
817 $order = "'$fct(' . $order . ')'";
820 $t = $order . $collecte . $sens;
821 if (preg_match("/^(.*)'\s*\.\s*'([^']*')$/", $t, $r)) {
825 $boucle->order
[] = $t;
830 * Calculs pour le critère `{par hasard}`
832 * Ajoute le générateur d'aléatoire au SELECT de la boucle.
834 * @param string $idb Identifiant de la boucle
835 * @param array $boucles AST du squelette
836 * @param Critere $crit Paramètres du critère dans cette boucle
837 * @return string Clause pour le Order by
839 function calculer_critere_par_hasard($idb, &$boucles, $crit) {
840 $boucle = &$boucles[$idb];
841 // Si ce n'est fait, ajouter un champ 'hasard' dans le select
842 $parha = "rand() AS hasard";
843 if (!in_array($parha, $boucle->select
)) {
844 $boucle->select
[] = $parha;
850 * Calculs pour le critère `{par num champ}` qui extrait le numéro préfixant un texte
852 * Tri par numéro de texte (tel que "10. titre"). Le numéro calculé est ajouté au SELECT
853 * de la boucle. L'écriture `{par num #ENV{tri}}` est aussi prise en compte.
856 * Les textes sans numéro valent 0 et sont donc placés avant les titres ayant des numéros.
857 * Utiliser `{par sinum champ, num champ}` pour avoir le comportement inverse.
859 * @see calculer_critere_par_expression_sinum() pour le critère `{par sinum champ}`
860 * @uses calculer_critere_par_champ()
862 * @param string $idb Identifiant de la boucle
863 * @param array $boucles AST du squelette
864 * @param Critere $crit Paramètres du critère dans cette boucle
865 * @param array $tri Paramètre en cours du critère
866 * @param string $champ Texte suivant l'expression ('titre' dans {par num titre})
867 * @return string Clause pour le Order by
869 function calculer_critere_par_expression_num($idb, &$boucles, $crit, $tri, $champ) {
870 $_champ = calculer_critere_par_champ($idb, $boucles, $crit, $champ, true);
871 if (is_array($_champ)) {
872 return array('zbug_critere_inconnu', array('critere' => $crit->op
. " num $champ"));
874 $boucle = &$boucles[$idb];
875 $texte = '0+' . $_champ;
876 $suite = calculer_liste($tri, array(), $boucles, $boucle->id_parent
);
877 if ($suite !== "''") {
878 $texte = "\" . ((\$x = $suite) ? ('$texte' . \$x) : '0')" . " . \"";
880 $as = 'num' . ($boucle->order ?
count($boucle->order
) : "");
881 $boucle->select
[] = $texte . " AS $as";
887 * Calculs pour le critère `{par sinum champ}` qui ordonne les champs avec numéros en premier
889 * Ajoute au SELECT la valeur 'sinum' qui vaut 0 si le champ a un numéro, 1 s'il n'en a pas.
890 * Ainsi `{par sinum titre, num titre, titre}` mettra les éléments sans numéro en fin de liste,
891 * contrairement à `{par num titre, titre}` seulement.
893 * @see calculer_critere_par_expression_num() pour le critère `{par num champ}`
894 * @uses calculer_critere_par_champ()
896 * @param string $idb Identifiant de la boucle
897 * @param array $boucles AST du squelette
898 * @param Critere $crit Paramètres du critère dans cette boucle
899 * @param array $tri Paramètre en cours du critère
900 * @param string $champ Texte suivant l'expression ('titre' dans {par sinum titre})
901 * @return string Clause pour le Order by
903 function calculer_critere_par_expression_sinum($idb, &$boucles, $crit, $tri, $champ) {
904 $_champ = calculer_critere_par_champ($idb, $boucles, $crit, $champ, true);
905 if (is_array($_champ)) {
906 return array('zbug_critere_inconnu', array('critere' => $crit->op
. " sinum $champ"));
908 $boucle = &$boucles[$idb];
909 $texte = '0+' . $_champ;
910 $suite = calculer_liste($tri, array(), $boucles, $boucle->id_parent
);
911 if ($suite !== "''") {
912 $texte = "\" . ((\$x = $suite) ? ('$texte' . \$x) : '0')" . " . \"";
914 $as = 'sinum' . ($boucle->order ?
count($boucle->order
) : "");
915 $boucle->select
[] = 'CASE (' . $texte . ') WHEN 0 THEN 1 ELSE 0 END AS ' . $as;
922 * Calculs pour le critère `{par multi champ}` qui extrait la langue en cours dans les textes
923 * ayant des balises `<multi>` (polyglottes)
925 * Ajoute le calcul du texte multi extrait dans le SELECT de la boucle.
926 * Il ne peut y avoir qu'un seul critère de tri `multi` par boucle.
928 * @uses calculer_critere_par_champ()
929 * @param string $idb Identifiant de la boucle
930 * @param array $boucles AST du squelette
931 * @param Critere $crit Paramètres du critère dans cette boucle
932 * @param array $tri Paramètre en cours du critère
933 * @param string $champ Texte suivant l'expression ('titre' dans {par multi titre})
934 * @return string Clause pour le Order by
936 function calculer_critere_par_expression_multi($idb, &$boucles, $crit, $tri, $champ) {
937 $_champ = calculer_critere_par_champ($idb, $boucles, $crit, $champ, true);
938 if (is_array($_champ)) {
939 return array('zbug_critere_inconnu', array('critere' => $crit->op
. " multi $champ"));
941 $boucle = &$boucles[$idb];
942 $boucle->select
[] = "\".sql_multi('" . $_champ . "', \$GLOBALS['spip_lang']).\"";
948 * Retourne le champ de tri demandé en ajoutant éventuellement les jointures nécessaires à la boucle.
950 * - si le champ existe dans la table, on l'utilise
951 * - si c'est une exception de jointure, on l'utilise (et crée la jointure au besoin)
952 * - si c'est un champ dont la jointure est déjà présente on la réutilise
953 * - si c'est un champ dont la jointure n'est pas présente, on la crée.
955 * @param string $idb Identifiant de la boucle
956 * @param array $boucles AST du squelette
957 * @param Critere $crit Paramètres du critère dans cette boucle
958 * @param string $par Nom du tri à analyser ('champ' ou 'table.champ')
959 * @param bool $raw Retourne le champ pour le compilateur ("'alias.champ'") ou brut ('alias.champ')
960 * @return array|string
962 function calculer_critere_par_champ($idb, &$boucles, $crit, $par, $raw = false) {
963 $boucle = &$boucles[$idb];
965 // le champ existe dans la table, pas de souci (le plus commun)
966 if (isset($desc['field'][$par])) {
967 $par = $boucle->id_table
. "." . $par;
969 // le champ est peut être une jointure
971 $table = $table_alias = false; // toutes les tables de jointure possibles
974 // le champ demandé est une exception de jointure {par titre_mot}
975 if (isset($GLOBALS['exceptions_des_jointures'][$par])) {
976 list($table, $champ) = $GLOBALS['exceptions_des_jointures'][$par];
977 } // la table de jointure est explicitement indiquée {par truc.muche}
978 elseif (preg_match("/^([^,]*)\.(.*)$/", $par, $r)) {
979 list(, $table, $champ) = $r;
980 $table_alias = $table; // c'est peut-être un alias de table {par L1.titre}
981 $table = table_objet_sql($table);
984 // Si on connait la table d'arrivée, on la demande donc explicitement
985 // Sinon on cherche le champ dans les tables possibles de jointures
986 // Si la table est déjà dans le from, on la réutilise.
987 if ($infos = chercher_champ_dans_tables($champ, $boucle->from
, $boucle->sql_serveur
, $table)) {
988 $par = $infos['alias'] . "." . $champ;
990 $boucle->jointures_explicites
991 and $alias = trouver_jointure_champ($champ, $boucle, explode(' ', $boucle->jointures_explicites
), false, $table)
993 $par = $alias . "." . $champ;
994 } elseif ($alias = trouver_jointure_champ($champ, $boucle, $boucle->jointures
, false, $table)) {
995 $par = $alias . "." . $champ;
996 // en spécifiant directement l'alias {par L2.titre} (situation hasardeuse tout de même)
999 and isset($boucle->from
[$table_alias])
1000 and $infos = chercher_champ_dans_tables($champ, $boucle->from
, $boucle->sql_serveur
, $boucle->from
[$table_alias])
1002 $par = $infos['alias'] . "." . $champ;
1004 // On avait table + champ, mais on ne les a pas trouvés
1005 return array('zbug_critere_inconnu', array('critere' => $crit->op
. " $par"));
1007 // Sinon tant pis, ca doit etre un champ synthetise (cf points)
1011 return $raw ?
$par : "'$par'";
1015 * Retourne un champ de tri en créant une jointure
1016 * si la table n'est pas présente dans le from de la boucle.
1019 * @param string $table Table du champ désiré
1020 * @param string $champ Champ désiré
1021 * @param Boucle $boucle Boucle en cours de compilation
1022 * @return string Champ pour le compilateur si trouvé, tel que "'alias.champ'", sinon vide.
1024 function critere_par_joint($table, $champ, &$boucle) {
1025 $t = array_search($table, $boucle->from
);
1027 $t = trouver_jointure_champ($champ, $boucle);
1029 return !$t ?
'' : ("'" . $t . '.' . $champ . "'");
1033 * Compile le critère `{inverse}` qui inverse l'ordre utilisé par le précédent critère `{par}`
1035 * Accèpte un paramètre pour déterminer le sens : `{inverse #X}` utilisera un tri croissant (ASC)
1036 * si la valeur retournée par `#X` est considérée vrai (`true`),
1037 * le sens contraire (DESC) sinon.
1040 * - `{par date}{inverse}`, équivalent à `{!par date}`
1041 * - `{par date}{inverse #ENV{sens}}` utilise la valeur d'environnement sens pour déterminer le sens.
1044 * @see critere_par_dist() Le critère `{par}`
1045 * @link http://www.spip.net/5530
1046 * @uses critere_parinverse()
1048 * @param string $idb Identifiant de la boucle
1049 * @param array $boucles AST du squelette
1050 * @param Critere $crit Paramètres du critère dans cette boucle
1052 function critere_inverse_dist($idb, &$boucles, $crit) {
1054 $boucle = &$boucles[$idb];
1055 // Classement par ordre inverse
1057 critere_parinverse($idb, $boucles, $crit);
1060 // Classement par ordre inverse fonction eventuelle de #ENV{...}
1061 if (isset($crit->param
[0])) {
1062 $critere = calculer_liste($crit->param
[0], array(), $boucles, $boucles[$idb]->id_parent
);
1063 $order = "(($critere)?' DESC':'')";
1066 $n = count($boucle->order
);
1068 if (isset($boucle->default_order
[0])) {
1069 $boucle->default_order
[0] .= ' . " DESC"';
1071 $boucle->default_order
[] = ' DESC';
1074 $t = $boucle->order
[$n - 1] . " . $order";
1075 if (preg_match("/^(.*)'\s*\.\s*'([^']*')$/", $t, $r)) {
1078 $boucle->order
[$n - 1] = $t;
1083 // http://code.spip.net/@critere_agenda_dist
1084 function critere_agenda_dist($idb, &$boucles, $crit) {
1085 $params = $crit->param
;
1087 if (count($params) < 1) {
1088 return array('zbug_critere_inconnu', array('critere' => $crit->op
. " ?"));
1091 $boucle = &$boucles[$idb];
1092 $parent = $boucle->id_parent
;
1093 $fields = $boucle->show
['field'];
1095 $date = array_shift($params);
1096 $type = array_shift($params);
1098 // la valeur $type doit etre connue a la compilation
1099 // donc etre forcement reduite a un litteral unique dans le source
1100 $type = is_object($type[0]) ?
$type[0]->texte
: null;
1102 // La valeur date doit designer un champ de la table SQL.
1103 // Si c'est un litteral unique dans le source, verifier a la compil,
1104 // sinon synthetiser le test de verif pour execution ulterieure
1105 // On prendra arbitrairement le premier champ si test negatif.
1106 if ((count($date) == 1) and ($date[0]->type
== 'texte')) {
1107 $date = $date[0]->texte
;
1108 if (!isset($fields[$date])) {
1109 return array('zbug_critere_inconnu', array('critere' => $crit->op
. " " . $date));
1112 $a = calculer_liste($date, array(), $boucles, $parent);
1113 $noms = array_keys($fields);
1115 $noms = join(" ", $noms);
1116 # bien laisser 2 espaces avant $nom pour que strpos<>0
1117 $cond = "(\$a=strval($a))AND\nstrpos(\" $noms \",\" \$a \")";
1118 $date = "'.(($cond)\n?\$a:\"$defaut\").'";
1120 $annee = $params ?
array_shift($params) : "";
1121 $annee = "\n" . 'sprintf("%04d", ($x = ' .
1122 calculer_liste($annee, array(), $boucles, $parent) .
1123 ') ? $x : date("Y"))';
1125 $mois = $params ?
array_shift($params) : "";
1126 $mois = "\n" . 'sprintf("%02d", ($x = ' .
1127 calculer_liste($mois, array(), $boucles, $parent) .
1128 ') ? $x : date("m"))';
1130 $jour = $params ?
array_shift($params) : "";
1131 $jour = "\n" . 'sprintf("%02d", ($x = ' .
1132 calculer_liste($jour, array(), $boucles, $parent) .
1133 ') ? $x : date("d"))';
1135 $annee2 = $params ?
array_shift($params) : "";
1136 $annee2 = "\n" . 'sprintf("%04d", ($x = ' .
1137 calculer_liste($annee2, array(), $boucles, $parent) .
1138 ') ? $x : date("Y"))';
1140 $mois2 = $params ?
array_shift($params) : "";
1141 $mois2 = "\n" . 'sprintf("%02d", ($x = ' .
1142 calculer_liste($mois2, array(), $boucles, $parent) .
1143 ') ? $x : date("m"))';
1145 $jour2 = $params ?
array_shift($params) : "";
1146 $jour2 = "\n" . 'sprintf("%02d", ($x = ' .
1147 calculer_liste($jour2, array(), $boucles, $parent) .
1148 ') ? $x : date("d"))';
1150 $date = $boucle->id_table
. ".$date";
1152 $quote_end = ",'" . $boucle->sql_serveur
. "','text'";
1153 if ($type == 'jour') {
1154 $boucle->where
[] = array(
1156 "'DATE_FORMAT($date, \'%Y%m%d\')'",
1157 ("sql_quote($annee . $mois . $jour$quote_end)")
1159 } elseif ($type == 'mois') {
1160 $boucle->where
[] = array(
1162 "'DATE_FORMAT($date, \'%Y%m\')'",
1163 ("sql_quote($annee . $mois$quote_end)")
1165 } elseif ($type == 'semaine') {
1166 $boucle->where
[] = array(
1170 "'DATE_FORMAT($date, \'%Y%m%d\')'",
1171 ("date_debut_semaine($annee, $mois, $jour)")
1175 "'DATE_FORMAT($date, \'%Y%m%d\')'",
1176 ("date_fin_semaine($annee, $mois, $jour)")
1179 } elseif (count($crit->param
) > 2) {
1180 $boucle->where
[] = array(
1184 "'DATE_FORMAT($date, \'%Y%m%d\')'",
1185 ("sql_quote($annee . $mois . $jour$quote_end)")
1187 array("'<='", "'DATE_FORMAT($date, \'%Y%m%d\')'", ("sql_quote($annee2 . $mois2 . $jour2$quote_end)"))
1190 // sinon on prend tout
1195 * Compile les critères {i,j} et {i/j}
1197 * Le critère {i,j} limite l'affiche de la boucle en commançant l'itération
1198 * au i-ème élément, et pour j nombre d'éléments.
1199 * Le critère {n-i,j} limite en commençant au n moins i-ème élément de boucle
1200 * Le critère {i,n-j} limite en terminant au n moins j-ème élément de boucle.
1202 * Le critère {i/j} affiche une part d'éléments de la boucle.
1203 * Commence à i*n/j élément et boucle n/j éléments. {2/4} affiche le second
1204 * quart des éléments d'une boucle.
1206 * Traduit si possible (absence de n dans {i,j}) la demande en une
1207 * expression LIMIT du gestionnaire SQL
1209 * @param string $idb Identifiant de la boucle
1210 * @param array $boucles AST du squelette
1211 * @param Critere $crit Paramètres du critère dans cette boucle
1214 function calculer_critere_parties($idb, &$boucles, $crit) {
1215 $boucle = &$boucles[$idb];
1216 $a1 = $crit->param
[0];
1217 $a2 = $crit->param
[1];
1220 list($a11, $a12) = calculer_critere_parties_aux($idb, $boucles, $a1);
1221 list($a21, $a22) = calculer_critere_parties_aux($idb, $boucles, $a2);
1223 if (($op == ',') && (is_numeric($a11) && (is_numeric($a21)))) {
1224 $boucle->limit
= $a11 . ',' . $a21;
1226 // 3 dans {1/3}, {2,3} ou {1,n-3}
1227 $boucle->total_parties
= ($a21 != 'n') ?
$a21 : $a22;
1228 // 2 dans {2/3}, {2,5}, {n-2,1}
1229 $partie = ($a11 != 'n') ?
$a11 : $a12;
1230 $mode = (($op == '/') ?
'/' :
1231 (($a11 == 'n') ?
'-' : '+') . (($a21 == 'n') ?
'-' : '+'));
1232 // cas simple {0,#ENV{truc}} compilons le en LIMIT :
1233 if ($a11 !== 'n' and $a21 !== 'n' and $mode == "++" and $op == ',') {
1235 (is_numeric($a11) ?
"'$a11'" : $a11)
1237 . (is_numeric($a21) ?
"'$a21'" : $a21);
1239 calculer_parties($boucles, $idb, $partie, $mode);
1245 * Compile certains critères {i,j} et {i/j}
1247 * Calcule une expression déterminant $debut_boucle et $fin_boucle (le
1248 * début et la fin des éléments de la boucle qui doivent être affichés)
1249 * et les déclare dans la propriété «mode_partie» de la boucle, qui se
1250 * charge également de déplacer le pointeur de boucle sur le premier
1251 * élément à afficher.
1253 * Place dans la propriété partie un test vérifiant que l'élément de
1254 * boucle en cours de lecture appartient bien à la plage autorisée.
1255 * Trop tôt, passe à l'élément suivant, trop tard, sort de l'itération de boucle.
1257 * @param array $boucles AST du squelette
1258 * @param string $id_boucle Identifiant de la boucle
1259 * @param string $debut Valeur ou code pour trouver le début (i dans {i,j})
1260 * @param string $mode
1261 * Mode (++, p+, +- ...) : 2 signes début & fin
1262 * - le signe - indique
1263 * -- qu'il faut soustraire debut du total {n-3,x}. 3 étant $debut
1264 * -- qu'il faut raccourcir la fin {x,n-3} de 3 elements. 3 étant $total_parties
1265 * - le signe p indique une pagination
1268 function calculer_parties(&$boucles, $id_boucle, $debut, $mode) {
1269 $total_parties = $boucles[$id_boucle]->total_parties
;
1271 preg_match(",([+-/p])([+-/])?,", $mode, $regs);
1272 list(, $op1, $op2) = array_pad($regs, 3, null);
1273 $nombre_boucle = "\$Numrows['$id_boucle']['total']";
1276 $pmoins1 = is_numeric($debut) ?
($debut - 1) : "($debut-1)";
1277 $totpos = is_numeric($total_parties) ?
($total_parties) :
1278 "($total_parties ? $total_parties : 1)";
1279 $fin = "ceil(($nombre_boucle * $debut )/$totpos) - 1";
1280 $debut = !$pmoins1 ?
0 : "ceil(($nombre_boucle * $pmoins1)/$totpos);";
1284 $debut = "$nombre_boucle - $debut;";
1289 $fin = '$debut_boucle + ' . $nombre_boucle . ' - '
1290 . (is_numeric($total_parties) ?
($total_parties +
1) :
1291 ($total_parties . ' - 1'));
1293 // {x,1} ou {pagination}
1294 $fin = '$debut_boucle'
1295 . (is_numeric($total_parties) ?
1296 (($total_parties == 1) ?
"" : (' + ' . ($total_parties - 1))) :
1297 ('+' . $total_parties . ' - 1'));
1300 // {pagination}, gerer le debut_xx=-1 pour tout voir
1302 $debut .= ";\n \$debut_boucle = ((\$tout=(\$debut_boucle == -1))?0:(\$debut_boucle))";
1303 $debut .= ";\n \$debut_boucle = max(0,min(\$debut_boucle,floor(($nombre_boucle-1)/($total_parties))*($total_parties)))";
1304 $fin = "(\$tout ? $nombre_boucle : $fin)";
1309 // $debut_boucle et $fin_boucle sont les indices SQL du premier
1310 // et du dernier demandes dans la boucle : 0 pour le premier,
1311 // n-1 pour le dernier ; donc total_boucle = 1 + debut - fin
1312 // Utiliser min pour rabattre $fin_boucle sur total_boucle.
1314 $boucles[$id_boucle]->mode_partie
= "\n\t"
1315 . '$debut_boucle = ' . $debut . ";\n "
1316 . "\$debut_boucle = intval(\$debut_boucle);\n "
1317 . '$fin_boucle = min(' . $fin . ", \$Numrows['$id_boucle']['total'] - 1);\n "
1318 . '$Numrows[\'' . $id_boucle . "']['grand_total'] = \$Numrows['$id_boucle']['total'];\n "
1319 . '$Numrows[\'' . $id_boucle . '\']["total"] = max(0,$fin_boucle - $debut_boucle + 1);'
1320 . "\n\tif (\$debut_boucle>0"
1321 . " AND \$debut_boucle < \$Numrows['$id_boucle']['grand_total']"
1322 . " AND \$iter->seek(\$debut_boucle,'continue'))"
1323 . "\n\t\t\$Numrows['$id_boucle']['compteur_boucle'] = \$debut_boucle;\n\t";
1325 $boucles[$id_boucle]->partie
= "
1326 if (\$Numrows['$id_boucle']['compteur_boucle'] <= \$debut_boucle) continue;
1327 if (\$Numrows['$id_boucle']['compteur_boucle']-1 > \$fin_boucle) break;";
1331 * Analyse un des éléments des critères {a,b} ou {a/b}
1333 * Pour l'élément demandé (a ou b) retrouve la valeur de l'élément,
1334 * et de combien il est soustrait si c'est le cas comme dans {a-3,b}
1336 * @param string $idb Identifiant de la boucle
1337 * @param array $boucles AST du squelette
1338 * @param array $param Paramètre à analyser (soit a, soit b dans {a,b} ou {a/b})
1339 * @return array Valeur de l'élément (peut être une expression PHP), Nombre soustrait
1341 function calculer_critere_parties_aux($idb, &$boucles, $param) {
1342 if ($param[0]->type
!= 'texte') {
1343 $a1 = calculer_liste(array($param[0]), array('id_mere' => $idb), $boucles, $boucles[$idb]->id_parent
);
1344 if (isset($param[1]->texte
)) {
1345 preg_match(',^ *(-([0-9]+))? *$,', $param[1]->texte
, $m);
1347 return array("intval($a1)", ((isset($m[2]) and $m[2]) ?
$m[2] : 0));
1349 return array("intval($a1)", 0);
1352 preg_match(',^ *(([0-9]+)|n) *(- *([0-9]+)? *)?$,', $param[0]->texte
, $m);
1355 return array($a1, 0);
1356 } elseif (!empty($m[4])) {
1357 return array($a1, $m[4]);
1359 return array($a1, calculer_liste(array($param[1]), array(), $boucles, $boucles[$idb]->id_parent
));
1366 * Compile les critères d'une boucle
1368 * Cette fonction d'aiguillage cherche des fonctions spécifiques déclarées
1369 * pour chaque critère demandé, dans l'ordre ci-dessous :
1371 * - critere_{serveur}_{table}_{critere}, sinon avec _dist
1372 * - critere_{serveur}_{critere}, sinon avec _dist
1373 * - critere_{table}_{critere}, sinon avec _dist
1374 * - critere_{critere}, sinon avec _dist
1375 * - calculer_critere_defaut, sinon avec _dist
1377 * Émet une erreur de squelette si un critère retourne une erreur.
1379 * @param string $idb
1380 * Identifiant de la boucle
1381 * @param array $boucles
1383 * @return string|array
1384 * string : Chaine vide sans erreur
1385 * array : Erreur sur un des critères
1387 function calculer_criteres($idb, &$boucles) {
1389 $boucle = $boucles[$idb];
1390 $table = strtoupper($boucle->type_requete
);
1391 $serveur = strtolower($boucle->sql_serveur
);
1393 $defaut = charger_fonction('DEFAUT', 'calculer_critere');
1394 // s'il y avait une erreur de syntaxe, propager cette info
1395 if (!is_array($boucle->criteres
)) {
1399 foreach ($boucle->criteres
as $crit) {
1400 $critere = $crit->op
;
1401 // critere personnalise ?
1404 ((!function_exists($f = "critere_" . $serveur . "_" . $table . "_" . $critere))
1405 and (!function_exists($f = $f . "_dist"))
1406 and (!function_exists($f = "critere_" . $serveur . "_" . $critere))
1407 and (!function_exists($f = $f . "_dist"))
1410 and (!function_exists($f = "critere_" . $table . "_" . $critere))
1411 and (!function_exists($f = $f . "_dist"))
1412 and (!function_exists($f = "critere_" . $critere))
1413 and (!function_exists($f = $f . "_dist"))
1415 // fonction critere standard
1418 // compile le critere
1419 $res = $f($idb, $boucles, $crit);
1421 // Gestion centralisee des erreurs pour pouvoir propager
1422 if (is_array($res)) {
1424 erreur_squelette($msg, $boucle);
1432 * Désemberlificote les guillements et échappe (ou fera échapper) le contenu...
1434 * Madeleine de Proust, revision MIT-1958 sqq, revision CERN-1989
1435 * hum, c'est kwoi cette fonxion ? on va dire qu'elle desemberlificote les guillemets...
1437 * http://code.spip.net/@kwote
1439 * @param string $lisp Code compilé
1440 * @param string $serveur Connecteur de bdd utilisé
1441 * @param string $type Type d'échappement (char, int...)
1442 * @return string Code compilé rééchappé
1444 function kwote($lisp, $serveur = '', $type = '') {
1445 if (preg_match(_CODE_QUOTE
, $lisp, $r)) {
1446 return $r[1] . "\"" . sql_quote(str_replace(array("\\'", "\\\\"), array("'", "\\"), $r[2]), $serveur, $type) . "\"";
1448 return "sql_quote($lisp, '$serveur', '" . str_replace("'", "\\'", $type) . "')";
1454 * Compile un critère possédant l'opérateur IN : {xx IN yy}
1456 * Permet de restreindre un champ sur une liste de valeurs tel que
1457 * {id_article IN 3,4} {id_article IN #LISTE{3,4}}
1459 * Si on a une liste de valeurs dans #ENV{x}, utiliser la double etoile
1460 * pour faire par exemple {id_article IN #ENV**{liste_articles}}
1462 * @param string $idb Identifiant de la boucle
1463 * @param array $boucles AST du squelette
1464 * @param Critere $crit Paramètres du critère dans cette boucle
1467 function critere_IN_dist($idb, &$boucles, $crit) {
1468 $r = calculer_critere_infixe($idb, $boucles, $crit);
1470 return (array('zbug_critere_inconnu', array('critere' => $crit->op
. " ?")));
1472 list($arg, $op, $val, $col, $where_complement) = $r;
1474 $in = critere_IN_cas($idb, $boucles, $crit->not ?
'NOT' : ($crit->exclus ?
'exclus' : ''), $arg, $op, $val, $col);
1476 // inserer la condition; exemple: {id_mot ?IN (66, 62, 64)}
1479 $pred = calculer_argument_precedent($idb, $col, $boucles);
1480 $where = array("'?'", $pred, $where, "''");
1481 if ($where_complement) // condition annexe du type "AND (objet='article')"
1483 $where_complement = array("'?'", $pred, $where_complement, "''");
1486 if ($crit->exclus
) {
1487 if (!preg_match(",^L[0-9]+[.],", $arg)) {
1488 $where = array("'NOT'", $where);
1490 // un not sur un critere de jointure se traduit comme un NOT IN avec une sous requete
1491 // c'est une sous requete identique a la requete principale sous la forme (SELF,$select,$where) avec $select et $where qui surchargent
1497 "'" . $boucles[$idb]->id_table
. "." . $boucles[$idb]->primary
. "'",
1498 array("'SELF'", "'" . $boucles[$idb]->id_table
. "." . $boucles[$idb]->primary
. "'", $where)
1504 $boucles[$idb]->where
[] = $where;
1505 if ($where_complement) // condition annexe du type "AND (objet='article')"
1507 $boucles[$idb]->where
[] = $where_complement;
1511 // http://code.spip.net/@critere_IN_cas
1512 function critere_IN_cas($idb, &$boucles, $crit2, $arg, $op, $val, $col) {
1513 static $num = array();
1514 $descr = $boucles[$idb]->descr
;
1515 $cpt = &$num[$descr['nom']][$descr['gram']][$idb];
1517 $var = '$in' . $cpt++
;
1518 $x = "\n\t$var = array();";
1519 foreach ($val as $k => $v) {
1520 if (preg_match(",^(\n//.*\n)?'(.*)'$,", $v, $r)) {
1521 // optimiser le traitement des constantes
1522 if (is_numeric($r[2])) {
1523 $x .= "\n\t$var" . "[]= $r[2];";
1525 $x .= "\n\t$var" . "[]= " . sql_quote($r[2]) . ";";
1528 // Pour permettre de passer des tableaux de valeurs
1529 // on repere l'utilisation brute de #ENV**{X},
1530 // c'est-a-dire sa traduction en ($PILE[0][X]).
1531 // et on deballe mais en rajoutant l'anti XSS
1532 $x .= "\n\tif (!(is_array(\$a = ($v))))\n\t\t$var" . "[]= \$a;\n\telse $var = array_merge($var, \$a);";
1536 $boucles[$idb]->in
.= $x;
1538 // inserer le tri par defaut selon les ordres du IN ...
1539 // avec une ecriture de type FIELD qui degrade les performances (du meme ordre qu'un regexp)
1540 // et que l'on limite donc strictement aux cas necessaires :
1541 // si ce n'est pas un !IN, et si il n'y a pas d'autre order dans la boucle
1543 $boucles[$idb]->default_order
[] = "((!sql_quote($var) OR sql_quote($var)===\"''\") ? 0 : ('FIELD($arg,' . sql_quote($var) . ')'))";
1546 return "sql_in('$arg',sql_quote($var)" . ($crit2 == 'NOT' ?
",'NOT'" : "") . ")";
1550 * Compile le critère {where}
1552 * Ajoute une contrainte sql WHERE, tout simplement pour faire le pont
1553 * entre php et squelettes, en utilisant la syntaxe attendue par
1554 * la propriété $where d'une Boucle.
1556 * @param string $idb Identifiant de la boucle
1557 * @param array $boucles AST du squelette
1558 * @param Critere $crit Paramètres du critère dans cette boucle
1561 function critere_where_dist($idb, &$boucles, $crit) {
1562 $boucle = &$boucles[$idb];
1563 if (isset($crit->param
[0])) {
1564 $_where = calculer_liste($crit->param
[0], array(), $boucles, $boucle->id_parent
);
1566 $_where = '@$Pile[0]["where"]';
1570 $_where = "(($_where) ? ($_where) : '')";
1574 $_where = "array('NOT',$_where)";
1577 $boucle->where
[] = $_where;
1582 * Compile le critère `{tri}` permettant le tri dynamique d'un champ
1584 * Le critère `{tri}` gère un champ de tri qui peut être modifié dynamiquement par la balise `#TRI`.
1585 * Il s'utilise donc conjointement avec la balise `#TRI` dans la même boucle
1586 * pour génerér les liens qui permettent de changer le critère de tri et le sens du tri
1588 * @syntaxe `{tri [champ_par_defaut][,sens_par_defaut][,nom_variable]}`
1590 * - champ_par_defaut : un champ de la table sql
1591 * - sens_par_defaut : -1 ou inverse pour décroissant, 1 ou direct pour croissant
1592 * peut être un tableau pour préciser des sens par défaut associés à chaque champ
1593 * exemple : `array('titre' => 1, 'date' => -1)` pour trier par défaut
1594 * les titre croissants et les dates décroissantes
1595 * dans ce cas, quand un champ est utilisé pour le tri et n'est pas présent dans le tableau
1596 * c'est la première valeur qui est utilisée
1597 * - nom_variable : nom de la variable utilisée (par defaut `tri_{nomboucle}`)
1600 * {tri titre,inverse}
1602 * {tri titre,-1,truc}
1604 * Exemple d'utilisation :
1607 * <p>#TRI{titre,'Trier par titre'} | #TRI{date,'Trier par date'}</p>
1609 * <BOUCLE_articles(ARTICLES){tri titre}>
1610 * <li>#TITRE - [(#DATE|affdate_jourcourt)]</li>
1611 * </BOUCLE_articles>
1616 * Contraitement à `{par ...}`, `{tri}` ne peut prendre qu'un seul champ,
1617 * mais il peut être complété avec `{par ...}` pour indiquer des criteres secondaires
1620 * `{tri num titre}{par titre}` permet de faire un tri sur le rang (modifiable dynamiquement)
1621 * avec un second critère sur le titre en cas d'égalité des rangs
1623 * @link http://www.spip.net/5429
1624 * @see critere_par_dist() Le critère `{par ...}`
1625 * @see balise_TRI_dist() La balise `#TRI`
1627 * @param string $idb Identifiant de la boucle
1628 * @param array $boucles AST du squelette
1629 * @param Critere $crit Paramètres du critère dans cette boucle
1632 function critere_tri_dist($idb, &$boucles, $crit) {
1633 $boucle = &$boucles[$idb];
1635 // definition du champ par defaut
1636 $_champ_defaut = !isset($crit->param
[0][0]) ?
"''"
1637 : calculer_liste(array($crit->param
[0][0]), array(), $boucles, $boucle->id_parent
);
1638 $_sens_defaut = !isset($crit->param
[1][0]) ?
"1"
1639 : calculer_liste(array($crit->param
[1][0]), array(), $boucles, $boucle->id_parent
);
1640 $_variable = !isset($crit->param
[2][0]) ?
"'$idb'"
1641 : calculer_liste(array($crit->param
[2][0]), array(), $boucles, $boucle->id_parent
);
1643 $_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):'')";
1645 $_sens_defaut = "(is_array(\$s=$_sens_defaut)?(isset(\$s[\$st=$_tri])?\$s[\$st]:reset(\$s)):\$s)";
1646 $_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)";
1648 $boucle->modificateur
['tri_champ'] = $_tri;
1649 $boucle->modificateur
['tri_sens'] = $_sens;
1650 $boucle->modificateur
['tri_nom'] = $_variable;
1651 // faut il inserer un test sur l'existence de $tri parmi les champs de la table ?
1652 // evite des erreurs sql, mais peut empecher des tri sur jointure ...
1658 \$senstri = (\$senstri<0)?' DESC':'';
1661 $boucle->select
[] = "\".tri_champ_select(\$tri).\"";
1662 $boucle->order
[] = "tri_champ_order(\$tri,\$command['from']).\$senstri";
1665 # Criteres de comparaison
1668 * Compile un critère non déclaré explicitement
1670 * Compile les critères non déclarés, ainsi que les parties de boucles
1671 * avec les critères {0,1} ou {1/2}
1673 * @param string $idb Identifiant de la boucle
1674 * @param array $boucles AST du squelette
1675 * @param Critere $crit Paramètres du critère dans cette boucle
1678 function calculer_critere_DEFAUT_dist($idb, &$boucles, $crit) {
1679 // double cas particulier {0,1} et {1/2} repere a l'analyse lexicale
1680 if (($crit->op
== ",") or ($crit->op
== '/')) {
1681 return calculer_critere_parties($idb, $boucles, $crit);
1684 $r = calculer_critere_infixe($idb, $boucles, $crit);
1686 # // on produit une erreur seulement si le critere n'a pas de '?'
1687 # if (!$crit->cond) {
1688 return (array('zbug_critere_inconnu', array('critere' => $crit->op
)));
1691 calculer_critere_DEFAUT_args($idb, $boucles, $crit, $r);
1697 * Compile un critère non déclaré explicitement, dont on reçoit une analyse
1699 * Ajoute en fonction des arguments trouvés par calculer_critere_infixe()
1700 * les conditions WHERE à appliquer sur la boucle.
1702 * @see calculer_critere_infixe()
1704 * @param string $idb Identifiant de la boucle
1705 * @param array $boucles AST du squelette
1706 * @param Critere $crit Paramètres du critère dans cette boucle
1707 * @param array $args Description du critère
1708 * Cf. retour de calculer_critere_infixe()
1711 function calculer_critere_DEFAUT_args($idb, &$boucles, $crit, $args) {
1712 list($arg, $op, $val, $col, $where_complement) = $args;
1714 $where = array("'$op'", "'$arg'", $val[0]);
1716 // inserer la negation (cf !...)
1719 $where = array("'NOT'", $where);
1721 if ($crit->exclus
) {
1722 if (!preg_match(",^L[0-9]+[.],", $arg)) {
1723 $where = array("'NOT'", $where);
1725 // un not sur un critere de jointure se traduit comme un NOT IN avec une sous requete
1726 // c'est une sous requete identique a la requete principale sous la forme (SELF,$select,$where) avec $select et $where qui surchargent
1732 "'" . $boucles[$idb]->id_table
. "." . $boucles[$idb]->primary
. "'",
1733 array("'SELF'", "'" . $boucles[$idb]->id_table
. "." . $boucles[$idb]->primary
. "'", $where)
1739 // inserer la condition (cf {lang?})
1740 // traiter a part la date, elle est mise d'office par SPIP,
1742 $pred = calculer_argument_precedent($idb, $col, $boucles);
1743 if ($col == "date" or $col == "date_redac") {
1744 if ($pred == "\$Pile[0]['" . $col . "']") {
1745 $pred = "(\$Pile[0]['{$col}_default']?'':$pred)";
1749 if ($op == '=' and !$crit->not
) {
1752 "(is_array($pred))",
1753 critere_IN_cas($idb, $boucles, 'COND', $arg, $op, array($pred), $col),
1757 $where = array("'?'", "!(is_array($pred)?count($pred):strlen($pred))", "''", $where);
1758 if ($where_complement) // condition annexe du type "AND (objet='article')"
1760 $where_complement = array("'?'", "!(is_array($pred)?count($pred):strlen($pred))", "''", $where_complement);
1764 $boucles[$idb]->where
[] = $where;
1765 if ($where_complement) // condition annexe du type "AND (objet='article')"
1767 $boucles[$idb]->where
[] = $where_complement;
1773 * Décrit un critère non déclaré explicitement
1775 * Décrit un critère non déclaré comme {id_article} {id_article>3} en
1776 * retournant un tableau de l'analyse si la colonne (ou l'alias) existe vraiment.
1778 * Ajoute au passage pour chaque colonne utilisée (alias et colonne véritable)
1779 * un modificateur['criteres'][colonne].
1781 * S'occupe de rechercher des exceptions, tel que
1782 * - les id_parent, id_enfant, id_secteur,
1783 * - des colonnes avec des exceptions déclarées,
1784 * - des critères de date (jour_relatif, ...),
1785 * - des critères sur tables jointes explicites (mots.titre),
1786 * - des critères sur tables de jointure non explicite (id_mot sur une boucle articles...)
1789 * @param string $idb Identifiant de la boucle
1790 * @param array $boucles AST du squelette
1791 * @param Critere $crit Paramètres du critère dans cette boucle
1792 * @return array|string
1793 * Liste si on trouve le champ :
1795 * Opérande avant l'opérateur : souvent la colonne d'application du critère, parfois un calcul
1796 * plus complexe dans le cas des dates.
1798 * L'opérateur utilisé, tel que '='
1800 * Liste de codes PHP obtenant les valeurs des comparaisons (ex: id_article sur la boucle parente)
1801 * Souvent (toujours ?) un tableau d'un seul élément.
1803 * - $where_complement
1805 * Chaîne vide si on ne trouve pas le champ...
1807 function calculer_critere_infixe($idb, &$boucles, $crit) {
1809 $boucle = &$boucles[$idb];
1810 $type = $boucle->type_requete
;
1811 $table = $boucle->id_table
;
1812 $desc = $boucle->show
;
1815 list($fct, $col, $op, $val, $args_sql) =
1816 calculer_critere_infixe_ops($idb, $boucles, $crit);
1819 $where_complement = false;
1821 // Cas particulier : id_enfant => utiliser la colonne id_objet
1822 if ($col == 'id_enfant') {
1823 $col = $boucle->primary
;
1826 // Cas particulier : id_parent => verifier les exceptions de tables
1827 if ((in_array($col, array('id_parent', 'id_secteur')) and isset($GLOBALS['exceptions_des_tables'][$table][$col]))
1828 or (isset($GLOBALS['exceptions_des_tables'][$table][$col]) and is_string($GLOBALS['exceptions_des_tables'][$table][$col]))
1830 $col = $GLOBALS['exceptions_des_tables'][$table][$col];
1831 } // et possibilite de gerer un critere secteur sur des tables de plugins (ie forums)
1833 if (($col == 'id_secteur') and ($critere_secteur = charger_fonction("critere_secteur_$type", "public", true))) {
1834 $table = $critere_secteur($idb, $boucles, $val, $crit);
1837 // cas id_article=xx qui se mappe en id_objet=xx AND objet=article
1838 // sauf si exception declaree : sauter cette etape
1841 !isset($GLOBALS['exceptions_des_jointures'][table_objet_sql($table)][$col])
1842 and !isset($GLOBALS['exceptions_des_jointures'][$col])
1843 and count(trouver_champs_decomposes($col, $desc)) > 1
1845 $e = decompose_champ_id_objet($col);
1846 $col = array_shift($e);
1847 $where_complement = primary_doublee($e, $table);
1848 } // Cas particulier : expressions de date
1850 if ($c = calculer_critere_infixe_date($idb, $boucles, $col)) {
1851 list($col, $col_vraie) = $c;
1853 } // table explicitée {mots.titre}
1855 if (preg_match('/^(.*)\.(.*)$/', $col, $r)) {
1856 list(, $table, $col) = $r;
1859 $trouver_table = charger_fonction('trouver_table', 'base');
1860 if ($desc = $trouver_table($table, $boucle->sql_serveur
)
1861 and isset($desc['field'][$col])
1862 and $cle = array_search($desc['table'], $boucle->from
)
1866 $table = trouver_jointure_champ($col, $boucle, array($table), ($crit->cond
or $op != '='));
1868 #$table = calculer_critere_externe_init($boucle, array($table), $col, $desc, ($crit->cond OR $op!='='), true);
1873 // si le champ n'est pas trouvé dans la table,
1874 // on cherche si une jointure peut l'obtenir
1875 elseif (@!array_key_exists($col, $desc['field'])) {
1876 // Champ joker * des iterateurs DATA qui accepte tout
1877 if (@array_key_exists
('*', $desc['field'])) {
1878 $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
1881 $r = calculer_critere_infixe_externe($boucle, $crit, $op, $desc, $col, $col_alias, $table);
1885 list($col, $col_alias, $table, $where_complement, $desc) = $r;
1893 $col_vraie = ($col_vraie ?
$col_vraie : $col);
1894 // Dans tous les cas,
1895 // virer les guillemets eventuels autour d'un int (qui sont refuses par certains SQL)
1896 // et passer dans sql_quote avec le type si connu
1897 // et int sinon si la valeur est numerique
1898 // sinon introduire le vrai type du champ si connu dans le sql_quote (ou int NOT NULL sinon)
1899 // Ne pas utiliser intval, PHP tronquant les Bigint de SQL
1900 if ($op == '=' or in_array($op, $GLOBALS['table_criteres_infixes'])) {
1902 // defaire le quote des int et les passer dans sql_quote avec le bon type de champ si on le connait, int sinon
1903 // prendre en compte le debug ou la valeur arrive avec un commentaire PHP en debut
1904 if (preg_match(",^\\A(\s*//.*?$\s*)?\"'(-?\d+)'\"\\z,ms", $val[0], $r)) {
1905 $val[0] = $r[1] . '"' . sql_quote($r[2], $boucle->sql_serveur
,
1906 (isset($desc['field'][$col_vraie]) ?
$desc['field'][$col_vraie] : 'int NOT NULL')) . '"';
1909 // sinon expliciter les
1910 // sql_quote(truc) en sql_quote(truc,'',type)
1911 // sql_quote(truc,serveur) en sql_quote(truc,serveur,type)
1912 // sql_quote(truc,serveur,'') en sql_quote(truc,serveur,type)
1914 // sql_quote(truc,'','varchar(10) DEFAULT \'oui\' COLLATE NOCASE')
1915 // sql_quote(truc,'','varchar')
1916 elseif (preg_match('/\Asql_quote[(](.*?)(,[^)]*?)?(,[^)]*(?:\(\d+\)[^)]*)?)?[)]\s*\z/ms', $val[0], $r)
1917 // si pas deja un type
1918 and (!isset($r[3]) or !$r[3] or !trim($r[3],", '"))
1921 . ((isset($r[2]) and $r[2]) ?
$r[2] : ",''")
1922 . ",'" . (isset($desc['field'][$col_vraie]) ?
addslashes($desc['field'][$col_vraie]) : 'int NOT NULL') . "'";
1923 $val[0] = "sql_quote($r)";
1926 // Indicateur pour permettre aux fonctionx boucle_X de modifier
1927 // leurs requetes par defaut, notamment le champ statut
1928 // Ne pas confondre champs de la table principale et des jointures
1929 if ($table === $boucle->id_table
) {
1930 $boucles[$idb]->modificateur
['criteres'][$col_vraie] = true;
1931 if ($col_alias != $col_vraie) {
1932 $boucles[$idb]->modificateur
['criteres'][$col_alias] = true;
1936 // ajout pour le cas special d'une condition sur le champ statut:
1937 // il faut alors interdire a la fonction de boucle
1938 // de mettre ses propres criteres de statut
1939 // http://www.spip.net/@statut (a documenter)
1940 // garde pour compatibilite avec code des plugins anterieurs, mais redondant avec la ligne precedente
1941 if ($col == 'statut') {
1942 $boucles[$idb]->statut
= true;
1945 // inserer le nom de la table SQL devant le nom du champ
1947 if ($col[0] == "`") {
1948 $arg = "$table." . substr($col, 1, -1);
1950 $arg = "$table.$col";
1956 // inserer la fonction SQL
1958 $arg = "$fct($arg$args_sql)";
1961 return array($arg, $op, $val, $col_alias, $where_complement);
1966 * Décrit un critère non déclaré explicitement, sur un champ externe à la table
1968 * Décrit un critère non déclaré comme {id_article} {id_article>3} qui correspond
1969 * à un champ non présent dans la table, et donc à retrouver par jointure si possible.
1971 * @param Boucle $boucle Description de la boucle
1972 * @param Critere $crit Paramètres du critère dans cette boucle
1973 * @param string $op L'opérateur utilisé, tel que '='
1974 * @param array $desc Description de la table
1975 * @param string $col Nom de la colonne à trouver (la véritable)
1976 * @param string $col_alias Alias de la colonne éventuel utilisé dans le critère ex: id_enfant
1977 * @param string $table Nom de la table SQL de la boucle
1978 * @return array|string
1979 * Liste si jointure possible :
1981 * - string $col_alias
1986 * Chaîne vide si on ne trouve pas le champ par jointure...
1988 function calculer_critere_infixe_externe($boucle, $crit, $op, $desc, $col, $col_alias, $table) {
1992 $calculer_critere_externe = 'calculer_critere_externe_init';
1993 // gestion par les plugins des jointures tordues
1994 // pas automatiques mais necessaires
1995 $table_sql = table_objet_sql($table);
1996 if (isset($GLOBALS['exceptions_des_jointures'][$table_sql])
1997 and is_array($GLOBALS['exceptions_des_jointures'][$table_sql])
2000 isset($GLOBALS['exceptions_des_jointures'][$table_sql][$col])
2002 isset($GLOBALS['exceptions_des_jointures'][$table_sql][''])
2005 $t = $GLOBALS['exceptions_des_jointures'][$table_sql];
2006 $index = isset($t[$col])
2007 ?
$t[$col] : (isset($t['']) ?
$t[''] : array());
2009 if (count($index) == 3) {
2010 list($t, $col, $calculer_critere_externe) = $index;
2011 } elseif (count($index) == 2) {
2012 list($t, $col) = $t[$col];
2013 } elseif (count($index) == 1) {
2014 list($calculer_critere_externe) = $index;
2018 } // jointure non declaree. La trouver.
2019 } elseif (isset($GLOBALS['exceptions_des_jointures'][$col])) {
2020 list($t, $col) = $GLOBALS['exceptions_des_jointures'][$col];
2023 } // jointure non declaree. La trouver.
2025 // ici on construit le from pour fournir $col en piochant dans les jointures
2027 // si des jointures explicites sont fournies, on cherche d'abord dans celles ci
2028 // permet de forcer une table de lien quand il y a ambiguite
2029 // <BOUCLE_(DOCUMENTS documents_liens){id_mot}>
2030 // alors que <BOUCLE_(DOCUMENTS){id_mot}> produit la meme chose que <BOUCLE_(DOCUMENTS mots_liens){id_mot}>
2032 if ($boucle->jointures_explicites
) {
2033 $jointures_explicites = explode(' ', $boucle->jointures_explicites
);
2034 $table = $calculer_critere_externe($boucle, $jointures_explicites, $col, $desc, ($crit->cond
or $op != '='), $t);
2037 // et sinon on cherche parmi toutes les jointures declarees
2039 $table = $calculer_critere_externe($boucle, $boucle->jointures
, $col, $desc, ($crit->cond
or $op != '='), $t);
2046 // il ne reste plus qu'a trouver le champ dans les from
2047 list($nom, $desc, $cle) = trouver_champ_exterieur($col, $boucle->from
, $boucle);
2049 if (count($cle) > 1 or reset($cle) !== $col) {
2050 $col_alias = $col; // id_article devient juste le nom d'origine
2051 if (count($cle) > 1 and reset($cle) == 'id_objet') {
2052 $e = decompose_champ_id_objet($col);
2053 $col = array_shift($e);
2054 $where = primary_doublee($e, $table);
2060 return array($col, $col_alias, $table, $where, $desc);
2065 * Calcule une condition WHERE entre un nom du champ et une valeur
2067 * Ne pas appliquer sql_quote lors de la compilation,
2068 * car on ne connait pas le serveur SQL
2070 * @todo Ce nom de fonction n'est pas très clair ?
2072 * @param array $decompose Liste nom du champ, code PHP pour obtenir la valeur
2073 * @param string $table Nom de la table
2075 * Liste de 3 éléments pour une description where du compilateur :
2080 function primary_doublee($decompose, $table) {
2081 $e1 = reset($decompose);
2082 $e2 = "sql_quote('" . end($decompose) . "')";
2084 return array("'='", "'$table." . $e1 . "'", $e2);
2088 * Champ hors table, ça ne peut être qu'une jointure.
2090 * On cherche la table du champ et on regarde si elle est déjà jointe
2091 * Si oui et qu'on y cherche un champ nouveau, pas de jointure supplementaire
2092 * Exemple: criteres {titre_mot=...}{type_mot=...}
2093 * Dans les 2 autres cas ==> jointure
2094 * (Exemple: criteres {type_mot=...}{type_mot=...} donne 2 jointures
2095 * pour selectioner ce qui a exactement ces 2 mots-cles.
2097 * @param Boucle $boucle
2098 * Description de la boucle
2099 * @param array $joints
2100 * Liste de jointures possibles (ex: $boucle->jointures ou $boucle->jointures_explicites)
2101 * @param string $col
2102 * Colonne cible de la jointure
2103 * @param array $desc
2104 * Description de la table
2106 * Flag pour savoir si le critère est conditionnel ou non
2107 * @param bool|string $checkarrivee
2108 * string : nom de la table jointe où on veut trouver le champ.
2109 * n'a normalement pas d'appel sans $checkarrivee.
2111 * Alias de la table de jointure (Lx)
2114 function calculer_critere_externe_init(&$boucle, $joints, $col, $desc, $cond, $checkarrivee = false) {
2115 // si on demande un truc du genre spip_mots
2116 // avec aussi spip_mots_liens dans les jointures dispo
2118 // il faut privilegier la jointure directe en 2 etapes spip_mots_liens, spip_mots
2120 and is_string($checkarrivee)
2121 and $a = table_objet($checkarrivee)
2122 and in_array($a . '_liens', $joints)
2124 if ($res = calculer_lien_externe_init($boucle, $joints, $col, $desc, $cond, $checkarrivee)) {
2128 foreach ($joints as $joint) {
2129 if ($arrivee = trouver_champ_exterieur($col, array($joint), $boucle, $checkarrivee)) {
2130 // alias de table dans le from
2131 $t = array_search($arrivee[0], $boucle->from
);
2132 // recuperer la cle id_xx eventuellement decomposee en (id_objet,objet)
2133 $cols = $arrivee[2];
2134 // mais on ignore la 3eme cle si presente qui correspond alors au point de depart
2135 if (count($cols) > 2) {
2139 // la table est déjà dans le FROM, on vérifie si le champ est utilisé.
2141 foreach ($cols as $col) {
2142 $c = '/\b' . $t . ".$col" . '\b/';
2143 if (trouver_champ($c, $boucle->where
)) {
2146 // mais ca peut etre dans le FIELD pour le Having
2147 $c = "/FIELD.$t" . ".$col,/";
2148 if (trouver_champ($c, $boucle->select
)) {
2157 array_pop($arrivee);
2158 if ($res = calculer_jointure($boucle, array($boucle->id_table
, $desc), $arrivee, $cols, $cond, 1)) {
2169 * Générer directement une jointure via une table de lien spip_xxx_liens
2170 * pour un critère {id_xxx}
2172 * @todo $checkarrivee doit être obligatoire ici ?
2174 * @param Boucle $boucle
2175 * Description de la boucle
2176 * @param array $joints
2177 * Liste de jointures possibles (ex: $boucle->jointures ou $boucle->jointures_explicites)
2178 * @param string $col
2179 * Colonne cible de la jointure
2180 * @param array $desc
2181 * Description de la table
2183 * Flag pour savoir si le critère est conditionnel ou non
2184 * @param bool|string $checkarrivee
2185 * string : nom de la table jointe où on veut trouver le champ.
2186 * n'a normalement pas d'appel sans $checkarrivee.
2188 * Alias de la table de jointure (Lx)
2190 function calculer_lien_externe_init(&$boucle, $joints, $col, $desc, $cond, $checkarrivee = false) {
2191 $primary_arrivee = id_table_objet($checkarrivee);
2193 // [FIXME] $checkarrivee peut-il arriver avec false ????
2194 $intermediaire = trouver_champ_exterieur($primary_arrivee, $joints, $boucle, $checkarrivee . "_liens");
2195 $arrivee = trouver_champ_exterieur($col, $joints, $boucle, $checkarrivee);
2197 if (!$intermediaire or !$arrivee) {
2200 array_pop($intermediaire); // enlever la cle en 3eme argument
2201 array_pop($arrivee); // enlever la cle en 3eme argument
2203 $res = fabrique_jointures($boucle,
2208 array(id_table_objet($desc['table_objet']), 'id_objet', 'objet', $desc['type'])
2210 array(reset($intermediaire), $arrivee, $primary_arrivee)
2211 ), $cond, $desc, $boucle->id_table
, array($col));
2218 * Recherche la présence d'un champ dans une valeur de tableau
2220 * @param string $champ
2221 * Expression régulière pour trouver un champ donné.
2222 * Exemple : /\barticles.titre\b/
2223 * @param array $where
2224 * Tableau de valeurs dans lesquels chercher le champ.
2226 * true si le champ est trouvé quelque part dans $where
2229 function trouver_champ($champ, $where) {
2230 if (!is_array($where)) {
2231 return preg_match($champ, $where);
2233 foreach ($where as $clause) {
2234 if (trouver_champ($champ, $clause)) {
2245 * Détermine l'operateur et les opérandes d'un critère non déclaré
2247 * Lorsque l'opérateur n'est pas explicite comme sur {id_article>0} c'est
2248 * l'opérateur '=' qui est utilisé.
2250 * Traite les cas particuliers id_parent, id_enfant, date, lang
2252 * @param string $idb Identifiant de la boucle
2253 * @param array $boucles AST du squelette
2254 * @param Critere $crit Paramètres du critère dans cette boucle
2257 * - string $fct Nom d'une fonction SQL sur le champ ou vide (ex: SUM)
2258 * - string $col Nom de la colonne SQL utilisée
2259 * - string $op Opérateur
2261 * Liste de codes PHP obtenant les valeurs des comparaisons (ex: id_article sur la boucle parente)
2262 * Souvent un tableau d'un seul élément.
2263 * - string $args_sql Suite des arguments du critère. ?
2265 function calculer_critere_infixe_ops($idb, &$boucles, $crit) {
2266 // cas d'une valeur comparee a elle-meme ou son referent
2267 if (count($crit->param
) == 0) {
2269 $col = $val = $crit->op
;
2270 if (preg_match('/^(.*)\.(.*)$/', $col, $r)) {
2273 // Cas special {lang} : aller chercher $GLOBALS['spip_lang']
2274 if ($val == 'lang') {
2275 $val = array(kwote('$GLOBALS[\'spip_lang\']'));
2278 if ($val == 'id_parent') {
2279 // Si id_parent, comparer l'id_parent avec l'id_objet
2280 // de la boucle superieure.... faudrait verifier qu'il existe
2281 // pour eviter l'erreur SQL
2282 $val = $boucles[$idb]->primary
;
2283 // mais si pas de boucle superieure, prendre id_parent dans l'env
2284 $defaut = "@\$Pile[0]['id_parent']";
2285 } elseif ($val == 'id_enfant') {
2286 // Si id_enfant, comparer l'id_objet avec l'id_parent
2287 // de la boucle superieure
2289 } elseif ($crit->cond
and ($col == "date" or $col == "date_redac")) {
2290 // un critere conditionnel sur date est traite a part
2291 // car la date est mise d'office par SPIP,
2292 $defaut = "(\$Pile[0]['{$col}_default']?'':\$Pile[0]['" . $col . "'])";
2295 $val = calculer_argument_precedent($idb, $val, $boucles, $defaut);
2296 $val = array(kwote($val));
2299 // comparaison explicite
2300 // le phraseur impose que le premier param soit du texte
2301 $params = $crit->param
;
2306 $col = array_shift($params);
2307 $col = $col[0]->texte
;
2310 $desc = array('id_mere' => $idb);
2311 $parent = $boucles[$idb]->id_parent
;
2313 // Dans le cas {x=='#DATE'} etc, defaire le travail du phraseur,
2314 // celui ne sachant pas ce qu'est un critere infixe
2315 // et a fortiori son 2e operande qu'entoure " ou '
2316 if (count($params) == 1
2317 and count($params[0]) == 3
2318 and $params[0][0]->type
== 'texte'
2319 and $params[0][2]->type
== 'texte'
2320 and ($p = $params[0][0]->texte
) == $params[0][2]->texte
2321 and (($p == "'") or ($p == '"'))
2322 and $params[0][1]->type
== 'champ'
2324 $val[] = "$p\\$p#" . $params[0][1]->nom_champ
. "\\$p$p";
2326 foreach ((($op != 'IN') ?
$params : calculer_vieux_in($params)) as $p) {
2327 $a = calculer_liste($p, $desc, $boucles, $parent);
2328 if (strcasecmp($op, 'IN') == 0) {
2331 $val[] = kwote($a, $boucles[$idb]->sql_serveur
, 'char');
2332 } // toujours quoter en char ici
2337 $fct = $args_sql = '';
2339 // chercher FONCTION(champ) tel que CONCAT(titre,descriptif)
2340 if (preg_match('/^(.*)' . SQL_ARGS
. '$/', $col, $m)) {
2342 preg_match('/^\(([^,]*)(.*)\)$/', $m[2], $a);
2344 if (preg_match('/^(\S*)(\s+AS\s+.*)$/i', $col, $m)) {
2351 return array($fct, $col, $op, $val, $args_sql);
2354 // compatibilite ancienne version
2356 // http://code.spip.net/@calculer_vieux_in
2357 function calculer_vieux_in($params) {
2358 $deb = $params[0][0];
2359 $k = count($params) - 1;
2360 $last = $params[$k];
2361 $j = count($last) - 1;
2363 $n = isset($last->texte
) ?
strlen($last->texte
) : 0;
2365 if (!((isset($deb->texte
[0]) and $deb->texte
[0] == '(')
2366 && (isset($last->texte
[$n - 1]) and $last->texte
[$n - 1] == ')'))
2370 $params[0][0]->texte
= substr($deb->texte
, 1);
2371 // attention, on peut avoir k=0,j=0 ==> recalculer
2372 $last = $params[$k][$j];
2373 $n = strlen($last->texte
);
2374 $params[$k][$j]->texte
= substr($last->texte
, 0, $n - 1);
2376 foreach ($params as $v) {
2377 if ($v[0]->type
!= 'texte') {
2380 foreach (explode(',', $v[0]->texte
) as $x) {
2383 $newp[] = array($t);
2392 * Calcule les cas particuliers de critères de date
2394 * Lorsque la colonne correspond à un critère de date, tel que
2395 * jour, jour_relatif, jour_x, age, age_relatif, age_x...
2397 * @param string $idb Identifiant de la boucle
2398 * @param array $boucles AST du squelette
2399 * @param string $col Nom du champ demandé
2400 * @return string|array
2401 * chaine vide si ne correspond pas à une date,
2403 * - expression SQL de calcul de la date,
2404 * - nom de la colonne de date (si le calcul n'est pas relatif)
2406 function calculer_critere_infixe_date($idb, &$boucles, $col) {
2407 if (!preg_match(",^((age|jour|mois|annee)_relatif|date|mois|annee|jour|heure|age)(_[a-z]+)?$,", $col, $regs)) {
2411 $boucle = $boucles[$idb];
2412 $table = $boucle->show
;
2414 // si c'est une colonne de la table, ne rien faire
2415 if (isset($table['field'][$col])) {
2419 if (!$table['date'] && !isset($GLOBALS['table_date'][$table['id_table']])) {
2422 $pred = $date_orig = isset($GLOBALS['table_date'][$table['id_table']]) ?
$GLOBALS['table_date'][$table['id_table']] : $table['date'];
2425 if (isset($regs[3]) and $suite = $regs[3]) {
2426 # Recherche de l'existence du champ date_xxxx,
2427 # si oui choisir ce champ, sinon choisir xxxx
2429 if (isset($table['field']["date$suite"])) {
2430 $date_orig = 'date' . $suite;
2432 $date_orig = substr($suite, 1);
2436 if (isset($regs[2]) and $rel = $regs[2]) {
2441 $date_compare = "\"' . normaliser_date(" .
2442 calculer_argument_precedent($idb, $pred, $boucles) .
2445 $col_vraie = $date_orig;
2446 $date_orig = $boucle->id_table
. '.' . $date_orig;
2453 $col = "DAYOFMONTH($date_orig)";
2456 $col = "MONTH($date_orig)";
2459 $col = "YEAR($date_orig)";
2462 $col = "DATE_FORMAT($date_orig, \\'%H:%i\\')";
2465 $col = calculer_param_date("NOW()", $date_orig);
2466 $col_vraie = "";// comparer a un int (par defaut)
2469 $col = calculer_param_date($date_compare, $date_orig);
2470 $col_vraie = "";// comparer a un int (par defaut)
2472 case 'jour_relatif':
2473 $col = "(TO_DAYS(" . $date_compare . ")-TO_DAYS(" . $date_orig . "))";
2474 $col_vraie = "";// comparer a un int (par defaut)
2476 case 'mois_relatif':
2477 $col = "MONTH(" . $date_compare . ")-MONTH(" .
2478 $date_orig . ")+12*(YEAR(" . $date_compare .
2479 ")-YEAR(" . $date_orig . "))";
2480 $col_vraie = "";// comparer a un int (par defaut)
2482 case 'annee_relatif':
2483 $col = "YEAR(" . $date_compare . ")-YEAR(" .
2485 $col_vraie = "";// comparer a un int (par defaut)
2489 return array($col, $col_vraie);
2493 * Calcule l'expression SQL permettant de trouver un nombre de jours écoulés.
2495 * Le calcul SQL retournera un nombre de jours écoulés entre la date comparée
2496 * et la colonne SQL indiquée
2498 * @param string $date_compare
2499 * Code PHP permettant d'obtenir le timestamp référent.
2500 * C'est à partir de lui que l'on compte les jours
2501 * @param string $date_orig
2502 * Nom de la colonne SQL qui possède la date
2504 * Expression SQL calculant le nombre de jours écoulé entre une valeur
2505 * de colonne SQL et une date.
2507 function calculer_param_date($date_compare, $date_orig) {
2508 if (preg_match(",'\" *\.(.*)\. *\"',", $date_compare, $r)) {
2509 $init = "'\" . (\$x = $r[1]) . \"'";
2510 $date_compare = '\'$x\'';
2512 $init = $date_compare;
2516 // optimisation : mais prevoir le support SQLite avant
2517 "TIMESTAMPDIFF(HOUR,$date_orig,$init)/24";
2521 * Compile le critère {source} d'une boucle DATA
2523 * Permet de déclarer le mode d'obtention des données dans une boucle
2524 * DATA (premier argument) et les données (la suite).
2527 * (DATA){source mode, "xxxxxx", arg, arg, arg}
2528 * (DATA){source tableau, #LISTE{un,deux,trois}}
2530 * @param string $idb Identifiant de la boucle
2531 * @param array $boucles AST du squelette
2532 * @param Critere $crit Paramètres du critère dans cette boucle
2534 function critere_DATA_source_dist($idb, &$boucles, $crit) {
2535 $boucle = &$boucles[$idb];
2538 foreach ($crit->param
as &$param) {
2540 calculer_liste($param, array(), $boucles, $boucles[$idb]->id_parent
));
2544 $command[\'sourcemode\'] = ' . array_shift($args) . ";\n";
2547 $command[\'source\'] = array(' . join(', ', $args) . ");\n";
2552 * Compile le critère {datasource} d'une boucle DATA
2554 * Permet de déclarer le mode d'obtention des données dans une boucle DATA
2556 * @deprecated Utiliser directement le critère {source}
2558 * @param string $idb Identifiant de la boucle
2559 * @param array $boucles AST du squelette
2560 * @param Critere $crit Paramètres du critère dans cette boucle
2562 function critere_DATA_datasource_dist($idb, &$boucles, $crit) {
2563 $boucle = &$boucles[$idb];
2565 $command[\'source\'] = array(' . calculer_liste($crit->param
[0], array(), $boucles, $boucles[$idb]->id_parent
) . ');
2566 $command[\'sourcemode\'] = ' . calculer_liste($crit->param
[1], array(), $boucles, $boucles[$idb]->id_parent
) . ';';
2571 * Compile le critère {datacache} d'une boucle DATA
2573 * Permet de transmettre une durée de cache (time to live) utilisée
2574 * pour certaines sources d'obtention des données (par exemple RSS),
2575 * indiquant alors au bout de combien de temps la donnée est à réobtenir.
2577 * La durée par défaut est 1 journée.
2579 * @param string $idb Identifiant de la boucle
2580 * @param array $boucles AST du squelette
2581 * @param Critere $crit Paramètres du critère dans cette boucle
2583 function critere_DATA_datacache_dist($idb, &$boucles, $crit) {
2584 $boucle = &$boucles[$idb];
2586 $command[\'datacache\'] = ' . calculer_liste($crit->param
[0], array(), $boucles, $boucles[$idb]->id_parent
) . ';';
2591 * Compile le critère {args} d'une boucle PHP
2593 * Permet de passer des arguments à un iterateur non-spip
2594 * (PHP:xxxIterator){args argument1, argument2, argument3}
2596 * @param string $idb Identifiant de la boucle
2597 * @param array $boucles AST du squelette
2598 * @param Critere $crit Paramètres du critère dans cette boucle
2600 function critere_php_args_dist($idb, &$boucles, $crit) {
2601 $boucle = &$boucles[$idb];
2602 $boucle->hash
.= '$command[\'args\']=array();';
2603 foreach ($crit->param
as $param) {
2605 $command[\'args\'][] = ' . calculer_liste($param, array(), $boucles, $boucles[$idb]->id_parent
) . ';';
2610 * Compile le critère {liste} d'une boucle DATA
2612 * Passe une liste de données à l'itérateur DATA
2615 * (DATA){liste X1, X2, X3}
2616 * équivalent à (DATA){source tableau,#LISTE{X1, X2, X3}}
2618 * @param string $idb Identifiant de la boucle
2619 * @param array $boucles AST du squelette
2620 * @param Critere $crit Paramètres du critère dans cette boucle
2622 function critere_DATA_liste_dist($idb, &$boucles, $crit) {
2623 $boucle = &$boucles[$idb];
2624 $boucle->hash
.= "\n\t" . '$command[\'liste\'] = array();' . "\n";
2625 foreach ($crit->param
as $param) {
2626 $boucle->hash
.= "\t" . '$command[\'liste\'][] = ' . calculer_liste($param, array(), $boucles,
2627 $boucles[$idb]->id_parent
) . ";\n";
2632 * Compile le critère {enum} d'une boucle DATA
2634 * Passe les valeurs de début et de fin d'une énumération, qui seront
2635 * vues comme une liste d'autant d'éléments à parcourir pour aller du
2638 * Cela utilisera la fonction range() de PHP.
2641 * (DATA){enum Xdebut, Xfin}
2644 * (DATA){enum 1.0,9.2}
2646 * @link http://php.net/manual/fr/function.range.php
2648 * @param string $idb Identifiant de la boucle
2649 * @param array $boucles AST du squelette
2650 * @param Critere $crit Paramètres du critère dans cette boucle
2652 function critere_DATA_enum_dist($idb, &$boucles, $crit) {
2653 $boucle = &$boucles[$idb];
2654 $boucle->hash
.= "\n\t" . '$command[\'enum\'] = array();' . "\n";
2655 foreach ($crit->param
as $param) {
2656 $boucle->hash
.= "\t" . '$command[\'enum\'][] = ' . calculer_liste($param, array(), $boucles,
2657 $boucles[$idb]->id_parent
) . ";\n";
2662 * Compile le critère {datapath} d'une boucle DATA
2664 * Extrait un chemin d'un tableau de données
2666 * (DATA){datapath query.results}
2668 * @param string $idb Identifiant de la boucle
2669 * @param array $boucles AST du squelette
2670 * @param Critere $crit Paramètres du critère dans cette boucle
2672 function critere_DATA_datapath_dist($idb, &$boucles, $crit) {
2673 $boucle = &$boucles[$idb];
2674 foreach ($crit->param
as $param) {
2676 $command[\'datapath\'][] = ' . calculer_liste($param, array(), $boucles, $boucles[$idb]->id_parent
) . ';';
2682 * Compile le critère {si}
2684 * Le critère {si condition} est applicable à toutes les boucles et conditionne
2685 * l'exécution de la boucle au résultat de la condition. La partie alternative
2686 * de la boucle est alors affichée si une condition n'est pas remplie (comme
2687 * lorsque la boucle ne ramène pas de résultat).
2688 * La différence étant que si la boucle devait réaliser une requête SQL
2689 * (par exemple une boucle ARTICLES), celle ci n'est pas réalisée si la
2690 * condition n'est pas remplie.
2692 * Les valeurs de la condition sont forcément extérieures à cette boucle
2693 * (sinon il faudrait l'exécuter pour connaître le résultat, qui doit tester
2694 * si on exécute la boucle !)
2696 * Si plusieurs critères {si} sont présents, ils sont cumulés :
2697 * si une seule des conditions n'est pas vérifiée, la boucle n'est pas exécutée.
2700 * {si #ENV{exec}|=={article}}
2701 * {si (#_contenu:GRAND_TOTAL|>{10})}
2702 * {si #AUTORISER{voir,articles}}
2704 * @param string $idb Identifiant de la boucle
2705 * @param array $boucles AST du squelette
2706 * @param Critere $crit Paramètres du critère dans cette boucle
2708 function critere_si_dist($idb, &$boucles, $crit) {
2709 $boucle = &$boucles[$idb];
2710 // il faut initialiser 1 fois le tableau a chaque appel de la boucle
2711 // (par exemple lorsque notre boucle est appelee dans une autre boucle)
2712 // mais ne pas l'initialiser n fois si il y a n criteres {si } dans la boucle !
2713 $boucle->hash
.= "\n\tif (!isset(\$si_init)) { \$command['si'] = array(); \$si_init = true; }\n";
2715 foreach ($crit->param
as $param) {
2716 $boucle->hash
.= "\t\$command['si'][] = "
2717 . calculer_liste($param, array(), $boucles, $boucles[$idb]->id_parent
) . ";\n";
2719 // interdire {si 0} aussi !
2721 $boucle->hash
.= '$command[\'si\'][] = 0;';
2726 * Compile le critère {tableau} d'une boucle POUR
2728 * {tableau #XX} pour compatibilite ascendante boucle POUR
2729 * ... préférer la notation (DATA){source tableau,#XX}
2731 * @deprecated Utiliser une boucle (DATA){source tableau,#XX}
2733 * @param string $idb Identifiant de la boucle
2734 * @param array $boucles AST du squelette
2735 * @param Critere $crit Paramètres du critère dans cette boucle
2737 function critere_POUR_tableau_dist($idb, &$boucles, $crit) {
2738 $boucle = &$boucles[$idb];
2740 $command[\'source\'] = array(' . calculer_liste($crit->param
[0], array(), $boucles, $boucles[$idb]->id_parent
) . ');
2741 $command[\'sourcemode\'] = \'table\';';
2746 * Compile le critère {noeud}
2748 * Trouver tous les objets qui ont des enfants (les noeuds de l'arbre)
2750 * {!noeud} retourne les feuilles
2752 * @global array $exceptions_des_tables
2754 * @param string $idb Identifiant de la boucle
2755 * @param array $boucles AST du squelette
2756 * @param Critere $crit Paramètres du critère dans cette boucle
2758 function critere_noeud_dist($idb, &$boucles, $crit) {
2761 $boucle = &$boucles[$idb];
2762 $primary = $boucle->primary
;
2764 if (!$primary or strpos($primary, ',')) {
2765 erreur_squelette(_T('zbug_doublon_sur_table_sans_cle_primaire'), $boucle);
2769 $table = $boucle->type_requete
;
2770 $table_sql = table_objet_sql(objet_type($table));
2772 $id_parent = isset($GLOBALS['exceptions_des_tables'][$boucle->id_table
]['id_parent']) ?
2773 $GLOBALS['exceptions_des_tables'][$boucle->id_table
]['id_parent'] :
2777 $where = array("'IN'", "'$boucle->id_table." . "$primary'", "'('.sql_get_select('$id_parent', '$table_sql').')'");
2779 $where = array("'NOT'", $where);
2782 $boucle->where
[] = $where;
2786 * Compile le critère {feuille}
2788 * Trouver tous les objets qui n'ont pas d'enfants (les feuilles de l'arbre)
2790 * {!feuille} retourne les noeuds
2792 * @global array $exceptions_des_tables
2793 * @param string $idb Identifiant de la boucle
2794 * @param array $boucles AST du squelette
2795 * @param Critere $crit Paramètres du critère dans cette boucle
2797 function critere_feuille_dist($idb, &$boucles, $crit) {
2799 $crit->not
= $not ?
false : true;
2800 critere_noeud_dist($idb, $boucles, $crit);