[SPIP] v3.2.7-->v3.2.9
[lhc/web/www.git] / www / ecrire / public / criteres.php
1 <?php
2
3 /***************************************************************************\
4 * SPIP, Systeme de publication pour l'internet *
5 * *
6 * Copyright (c) 2001-2019 *
7 * Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James *
8 * *
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 \***************************************************************************/
12
13 /**
14 * Définition des {criteres} d'une boucle
15 *
16 * @package SPIP\Core\Compilateur\Criteres
17 **/
18
19 if (!defined('_ECRIRE_INC_VERSION')) {
20 return;
21 }
22
23 /**
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'
27 **/
28 define('_CODE_QUOTE', ",^(\n//[^\n]*\n)? *'(.*)' *$,");
29
30
31 /**
32 * Compile le critère {racine}
33 *
34 * Ce critère sélectionne les éléments à la racine d'une hiérarchie,
35 * c'est à dire ayant id_parent=0
36 *
37 * @link http://www.spip.net/@racine
38 *
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
42 * @return void
43 **/
44 function critere_racine_dist($idb, &$boucles, $crit) {
45
46 $not = $crit->not;
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'] :
50 'id_parent';
51
52 $c = array("'='", "'$boucle->id_table." . "$id_parent'", 0);
53 $boucle->where[] = ($crit->not ? array("'NOT'", $c) : $c);
54 }
55
56
57 /**
58 * Compile le critère {exclus}
59 *
60 * Exclut du résultat l’élément dans lequel on se trouve déjà
61 *
62 * @link http://www.spip.net/@exclus
63 *
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
67 * @return void
68 **/
69 function critere_exclus_dist($idb, &$boucles, $crit) {
70 $not = $crit->not;
71 $boucle = &$boucles[$idb];
72 $id = $boucle->primary;
73
74 if ($not or !$id) {
75 return (array('zbug_critere_inconnu', array('critere' => $not . $crit->op)));
76 }
77 $arg = kwote(calculer_argument_precedent($idb, $id, $boucles));
78 $boucle->where[] = array("'!='", "'$boucle->id_table." . "$id'", $arg);
79 }
80
81
82 /**
83 * Compile le critère {doublons} ou {unique}
84 *
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.
87 *
88 * Il est possible de spécifier un nom au doublon tel que {doublons sommaire}
89 *
90 * @link http://www.spip.net/@doublons
91 *
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
95 * @return void
96 **/
97 function critere_doublons_dist($idb, &$boucles, $crit) {
98 $boucle = &$boucles[$idb];
99 $primary = $boucle->primary;
100
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'));
104 }
105
106 $not = ($crit->not ? '' : 'NOT');
107
108 // le doublon s'applique sur un type de boucle (article)
109 $nom = "'" . $boucle->type_requete . "'";
110
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);
115 }
116
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";
120
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
124
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 . "[]= "));
130
131 // le debut complet du code des doublons
132 $debut_doub = $debut_in . $debut_doub;
133
134 // nom du doublon "('article' . 'nom')]"
135 $fin_doub = "($nom)]";
136
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);
146 $boucle->hash =
147 substr($boucle->hash, 0, $x + $len) . $init_code . substr($boucle->hash, $x + $len);
148
149 return;
150 }
151 }
152
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 . "')");
155
156 // déclarer le doublon s'il n'existe pas encore
157 $boucle->hash .= $init_comment . $init_code;
158
159
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 = "";
165 }
166
167
168 /**
169 * Compile le critère {lang_select}
170 *
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
174 * en cours.
175 *
176 * Sans définir de valeur au critère, celui-ci utilise 'oui' comme
177 * valeur par défaut.
178 *
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
182 * @return void
183 **/
184 function critere_lang_select_dist($idb, &$boucles, $crit) {
185 if (!isset($crit->param[1][0]) or !($param = $crit->param[1][0]->texte)) {
186 $param = 'oui';
187 }
188 if ($crit->not) {
189 $param = ($param == 'oui') ? 'non' : 'oui';
190 }
191 $boucle = &$boucles[$idb];
192 $boucle->lang_select = $param;
193 }
194
195
196 /**
197 * Compile le critère {debut_xxx}
198 *
199 * Limite le nombre d'éléments affichés.
200 *
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 à
204 * afficher.
205 *
206 * Note : il est plus simple d'utiliser le critère pagination.
207 *
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
211 * @return void
212 **/
213 function critere_debut_dist($idb, &$boucles, $crit) {
214 list($un, $deux) = $crit->param;
215 $un = $un[0]->texte;
216 $deux = $deux[0]->texte;
217 if ($deux) {
218 $boucles[$idb]->limit = 'intval($Pile[0]["debut' .
219 $un .
220 '"]) . ",' .
221 $deux .
222 '"';
223 } else {
224 calculer_critere_DEFAUT_dist($idb, $boucles, $crit);
225 }
226 }
227
228
229 /**
230 * Compile le critère `pagination` qui demande à paginer une boucle.
231 *
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.
235 *
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.
239 *
240 * @critere
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
244 * @example
245 * ```
246 * {pagination}
247 * {pagination 20}
248 * {pagination #ENV{pages,5}} etc
249 * {pagination 20 #ENV{truc,chose}} pour utiliser la variable debut_#ENV{truc,chose}
250 * ```
251 *
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
255 * @return void
256 **/
257 function critere_pagination_dist($idb, &$boucles, $crit) {
258
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);
263
264 if (!preg_match(_CODE_QUOTE, $pas, $r)) {
265 $pas = "((\$a = intval($pas)) ? \$a : 10)";
266 } else {
267 $r = intval($r[2]);
268 $pas = strval($r ? $r : 10);
269 }
270
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
274 $type = "'$idb'";
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);
282 }
283
284 $debut = ($type[0] !== "'") ? "'debut'.$type" : ("'debut" . substr($type, 1));
285 $boucle->modificateur['debut_nom'] = $type;
286 $partie =
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"
292 . "\t}\n"
293 . "\t" . '$debut_boucle = intval($debut_boucle)';
294
295 $boucle->hash .= '
296 $command[\'pagination\'] = array((isset($Pile[0][' . $debut . ']) ? $Pile[0][' . $debut . '] : null), ' . $pas . ');';
297
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;
304 if ($boucle->primary
305 and !preg_match('/[,\s]/', $boucle->primary)
306 and !in_array($t, $boucle->select)
307 ) {
308 $boucle->select[] = $t;
309 }
310 }
311
312
313 /**
314 * Compile le critère `recherche` qui permet de sélectionner des résultats
315 * d'une recherche.
316 *
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.
319 *
320 * @critere
321 * @link http://www.spip.net/3878
322 * @see inc_prepare_recherche_dist()
323 *
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
327 * @return void
328 **/
329 function critere_recherche_dist($idb, &$boucles, $crit) {
330
331 $boucle = &$boucles[$idb];
332
333 if (!$boucle->primary or strpos($boucle->primary, ',')) {
334 erreur_squelette(_T('zbug_critere_sur_table_sans_cle_primaire', array('critere' => 'recherche')), $boucle);
335
336 return;
337 }
338
339 if (isset($crit->param[0])) {
340 $quoi = calculer_liste($crit->param[0], array(), $boucles, $boucles[$idb]->id_parent);
341 } else {
342 $quoi = '(isset($Pile[0]["recherche"])?$Pile[0]["recherche"]:(isset($GLOBALS["recherche"])?$GLOBALS["recherche"]:""))';
343 }
344
345 $_modificateur = var_export($boucle->modificateur, true);
346 $boucle->hash .= '
347 // RECHERCHE'
348 . ($crit->cond ? '
349 if (!strlen(' . $quoi . ')){
350 list($rech_select, $rech_where) = array("0 as points","");
351 } else' : '') . '
352 {
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 . '");
355 }
356 ';
357
358
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';
368 }
369 $boucle->select[] = '$rech_select';
370 //$boucle->where[]= "\$rech_where?'resultats.id=".$boucle->id_table.".".$boucle->primary."':''";
371
372 // et la recherche trouve
373 $boucle->where[] = '$rech_where?$rech_where:\'\'';
374 }
375
376 /**
377 * Compile le critère `traduction`
378 *
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)
381 *
382 * Équivalent à `(id_trad>0 AND id_trad=id_trad(precedent)) OR id_xx=id_xx(precedent)`
383 *
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
387 * @return void
388 **/
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));
395 $boucle->where[] =
396 array(
397 "'OR'",
398 array(
399 "'AND'",
400 array("'='", "'$table.id_trad'", 0),
401 array("'='", "'$table.$prim'", $dprim)
402 ),
403 array(
404 "'AND'",
405 array("'>'", "'$table.id_trad'", 0),
406 array("'='", "'$table.id_trad'", $arg)
407 )
408 );
409 }
410
411
412 /**
413 * Compile le critère {origine_traduction}
414 *
415 * Sélectionne les éléments qui servent de base à des versions traduites
416 * (par exemple les articles "originaux" sur une boucle articles)
417 *
418 * Équivalent à (id_trad>0 AND id_xx=id_trad) OR (id_trad=0)
419 *
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
423 * @return void
424 **/
425 function critere_origine_traduction_dist($idb, &$boucles, $crit) {
426 $boucle = &$boucles[$idb];
427 $prim = $boucle->primary;
428 $table = $boucle->id_table;
429
430 $c =
431 array(
432 "'OR'",
433 array("'='", "'$table." . "id_trad'", "'$table.$prim'"),
434 array("'='", "'$table.id_trad'", "'0'")
435 );
436 $boucle->where[] = ($crit->not ? array("'NOT'", $c) : $c);
437 }
438
439
440 /**
441 * Compile le critère {meme_parent}
442 *
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.
445 *
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
449 * @return void
450 **/
451 function critere_meme_parent_dist($idb, &$boucles, $crit) {
452
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'] :
457 'id_parent';
458 $mparent = $boucle->id_table . '.' . $id_parent;
459
460 if ($boucle->type_requete == 'rubriques' or isset($GLOBALS['exceptions_des_tables'][$boucle->id_table]['id_parent'])) {
461 $boucle->where[] = array("'='", "'$mparent'", $arg);
462
463 } // le cas FORUMS est gere dans le plugin forum, dans la fonction critere_FORUMS_meme_parent_dist()
464 else {
465 return (array('zbug_critere_inconnu', array('critere' => $crit->op . ' ' . $boucle->type_requete)));
466 }
467 }
468
469
470 /**
471 * Compile le critère `branche` qui sélectionne dans une boucle les
472 * éléments appartenant à une branche d'une rubrique.
473 *
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.
478 *
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.
482 *
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'
486 * donc).
487 *
488 * @link http://www.spip.net/@branche
489 *
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
493 * @return void
494 **/
495 function critere_branche_dist($idb, &$boucles, $crit) {
496
497 $not = $crit->not;
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
503 } else {
504 $arg = kwote(calculer_argument_precedent($idb, 'id_rubrique', $boucles), $boucle->sql_serveur, 'int NOT NULL');
505 }
506
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)) . '"');
519 }
520 } else {
521 $cle = $boucle->id_table;
522 }
523
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'") . ')');
528 }
529
530 /**
531 * Compile le critère `logo` qui liste les objets qui ont un logo
532 *
533 * @uses lister_objets_avec_logos()
534 * Pour obtenir les éléments qui ont un logo
535 *
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
539 * @return void
540 **/
541 function critere_logo_dist($idb, &$boucles, $crit) {
542
543 $not = $crit->not;
544 $boucle = &$boucles[$idb];
545
546 $c = "sql_in('" .
547 $boucle->id_table . '.' . $boucle->primary
548 . "', lister_objets_avec_logos('" . $boucle->primary . "'), '')";
549
550 if ($not) {
551 $boucle->where[] = array("'NOT'", $c);
552 } else {
553 $boucle->where[] = $c;
554 }
555 }
556
557
558 /**
559 * Compile le critère `fusion` qui regroupe les éléments selon une colonne.
560 *
561 * C'est la commande SQL «GROUP BY»
562 *
563 * @critere
564 * @link http://www.spip.net/5166
565 * @example
566 * ```
567 * <BOUCLE_a(articles){fusion lang}>
568 * ```
569 *
570 * @param string $idb Identifiant de la boucle
571 * @param array $boucles AST du squelette
572 * @param Critere $crit Paramètres du critère dans cette boucle
573 * @return void
574 **/
575 function critere_fusion_dist($idb, &$boucles, $crit) {
576 if ($t = isset($crit->param[0])) {
577 $t = $crit->param[0];
578 if ($t[0]->type == 'texte') {
579 $t = $t[0]->texte;
580 if (preg_match("/^(.*)\.(.*)$/", $t, $r)) {
581 $t = table_objet_sql($r[1]);
582 $t = array_search($t, $boucles[$idb]->from);
583 if ($t) {
584 $t .= '.' . $r[2];
585 }
586 }
587 } else {
588 $t = '".'
589 . calculer_critere_arg_dynamique($idb, $boucles, $t)
590 . '."';
591 }
592 }
593 if ($t) {
594 $boucles[$idb]->group[] = $t;
595 if (!in_array($t, $boucles[$idb]->select)) {
596 $boucles[$idb]->select[] = $t;
597 }
598 } else {
599 return (array('zbug_critere_inconnu', array('critere' => $crit->op . ' ?')));
600 }
601 }
602
603 /**
604 * Compile le critère `{collecte}` qui permet de spécifier l'interclassement
605 * à utiliser pour les tris de la boucle.
606 *
607 * Cela permet avec le critère `{par}` de trier un texte selon
608 * l'interclassement indiqué. L'instruction s'appliquera sur les critères `{par}`
609 * qui succèdent ce critère, ainsi qu'au critère `{par}` précédent
610 * si aucun interclassement ne lui est déjà appliqué.
611 *
612 * Techniquement, c'est la commande SQL "COLLATE" qui utilisée.
613 * (elle peut être appliquée sur les order by, group by, where, like ...)
614 *
615 * @example
616 * - `{par titre}{collecte utf8_spanish_ci}` ou `{collecte utf8_spanish_ci}{par titre}`
617 * - `{par titre}{par surtitre}{collecte utf8_spanish_ci}` :
618 * Seul 'surtitre' (`par` précédent) utilisera l'interclassement
619 * - `{collecte utf8_spanish_ci}{par titre}{par surtitre}` :
620 * 'titre' et 'surtitre' utiliseront l'interclassement (tous les `par` suivants)
621 *
622 * @note
623 * Piège sur une éventuelle écriture peu probable :
624 * `{par a}{collecte c1}{par b}{collecte c2}` : le tri `{par b}`
625 * utiliserait l'interclassement c1 (et non c2 qui ne s'applique pas
626 * au `par` précédent s'il a déjà un interclassement demandé).
627 *
628 * @critere
629 * @link http://www.spip.net/4028
630 * @see critere_par_dist() Le critère `{par}`
631 *
632 * @param string $idb Identifiant de la boucle
633 * @param array $boucles AST du squelette
634 * @param Critere $crit Paramètres du critère dans cette boucle
635 */
636 function critere_collecte_dist($idb, &$boucles, $crit) {
637 if (isset($crit->param[0])) {
638 $_coll = calculer_liste($crit->param[0], array(), $boucles, $boucles[$idb]->id_parent);
639 $boucle = $boucles[$idb];
640 $boucle->modificateur['collate'] = "($_coll ?' COLLATE '.$_coll:'')";
641 $n = count($boucle->order);
642 if ($n && (strpos($boucle->order[$n - 1], 'COLLATE') === false)) {
643 // l'instruction COLLATE doit être placée avant ASC ou DESC
644 // notamment lors de l'utilisation `{!par xxx}{collate yyy}`
645 if (
646 (false !== $i = strpos($boucle->order[$n - 1], 'ASC'))
647 OR (false !== $i = strpos($boucle->order[$n - 1], 'DESC'))
648 ) {
649 $boucle->order[$n - 1] = substr_replace($boucle->order[$n - 1], "' . " . $boucle->modificateur['collate'] . " . ' ", $i, 0);
650 } else {
651 $boucle->order[$n - 1] .= " . " . $boucle->modificateur['collate'];
652 }
653 }
654 } else {
655 return (array('zbug_critere_inconnu', array('critere' => $crit->op . " " . count($boucles[$idb]->order))));
656 }
657 }
658
659 // http://code.spip.net/@calculer_critere_arg_dynamique
660 function calculer_critere_arg_dynamique($idb, &$boucles, $crit, $suffix = '') {
661 $boucle = $boucles[$idb];
662 $alt = "('" . $boucle->id_table . '.\' . $x' . $suffix . ')';
663 $var = '$champs_' . $idb;
664 $desc = (strpos($boucle->in, "static $var =") !== false);
665 if (!$desc) {
666 $desc = $boucle->show['field'];
667 $desc = implode(',', array_map('_q', array_keys($desc)));
668 $boucles[$idb]->in .= "\n\tstatic $var = array(" . $desc . ");";
669 }
670 if ($desc) {
671 $alt = "(in_array(\$x, $var) ? $alt :(\$x$suffix))";
672 }
673 $arg = calculer_liste($crit, array(), $boucles, $boucle->id_parent);
674
675 return "((\$x = preg_replace(\"/\\W/\",'', $arg)) ? $alt : '')";
676 }
677
678 /**
679 * Compile le critère `{par}` qui permet d'ordonner les résultats d'une boucle
680 *
681 * Demande à trier la boucle selon certains champs (en SQL, la commande `ORDER BY`).
682 * Si plusieurs tris sont demandés (plusieurs fois le critère `{par x}{par y}` dans une boucle ou plusieurs champs
683 * séparés par des virgules dans le critère `{par x, y, z}`), ils seront appliqués dans l'ordre.
684 *
685 * Quelques particularités :
686 * - `{par hasard}` : trie par hasard
687 * - `{par num titre}` : trie par numéro de titre
688 * - `{par multi titre}` : trie par la langue extraite d'une balise polyglotte `<multi>` sur le champ titre
689 * - `{!par date}` : trie par date inverse en utilisant le champ date principal déclaré pour la table (si c'est un objet éditorial).
690 * - `{!par points}` : trie par pertinence de résultat de recherche (avec le critère `{recherche}`)
691 * - `{par FUNCTION_SQL(n)}` : trie en utilisant une fonction SQL (peut dépendre du moteur SQL utilisé).
692 * Exemple : `{par SUBSTRING_INDEX(titre, ".", -1)}` (tri ~ alphabétique en ignorant les numéros de titres
693 * (exemple erroné car faux dès qu'un titre possède un point.)).
694 * - `{par table.champ}` : trie en effectuant une jointure sur la table indiquée.
695 * - `{par #BALISE}` : trie sur la valeur retournée par la balise (doit être un champ de la table, ou 'hasard').
696 *
697 * @example
698 * - `{par titre}`
699 * - `{!par date}`
700 * - `{par num titre, multi titre, hasard}`
701 *
702 * @critere
703 * @link http://www.spip.net/5531
704 * @see critere_tri_dist() Le critère `{tri ...}`
705 * @see critere_inverse_dist() Le critère `{inverse}`
706 *
707 * @uses critere_parinverse()
708 *
709 * @param string $idb Identifiant de la boucle
710 * @param array $boucles AST du squelette
711 * @param Critere $crit Paramètres du critère dans cette boucle
712 */
713 function critere_par_dist($idb, &$boucles, $crit) {
714 return critere_parinverse($idb, $boucles, $crit);
715 }
716
717 /**
718 * Calculs pour le critère `{par}` ou `{inverse}` pour ordonner les résultats d'une boucle
719 *
720 * Les expressions intermédiaires `{par expr champ}` sont calculées dans des fonctions
721 * `calculer_critere_par_expression_{expr}()` notamment `{par num champ}` ou `{par multi champ}`.
722 *
723 * @see critere_par_dist() Le critère `{par}` pour des exemples
724 *
725 * @uses calculer_critere_arg_dynamique() pour le calcul de `{par #ENV{tri}}`
726 * @uses calculer_critere_par_hasard() pour le calcul de `{par hasard}`
727 * @uses calculer_critere_par_champ()
728 * @see calculer_critere_par_expression_num() pour le calcul de `{par num x}`
729 * @see calculer_critere_par_expression_multi() pour le calcul de `{par multi x}`
730 *
731 * @param string $idb Identifiant de la boucle
732 * @param array $boucles AST du squelette
733 * @param Critere $crit Paramètres du critère dans cette boucle
734 */
735 function critere_parinverse($idb, &$boucles, $crit) {
736 $boucle = &$boucles[$idb];
737
738 $sens = $collecte = '';
739 if ($crit->not) {
740 $sens = " . ' DESC'";
741 }
742 if (isset($boucle->modificateur['collate'])) {
743 $collecte = ' . ' . $boucle->modificateur['collate'];
744 }
745
746 // Pour chaque paramètre du critère
747 foreach ($crit->param as $tri) {
748 $order = $fct = '';
749 // tris specifiés dynamiquement {par #ENV{tri}}
750 if ($tri[0]->type != 'texte') {
751 // calculer le order dynamique qui verifie les champs
752 $order = calculer_critere_arg_dynamique($idb, $boucles, $tri, $sens);
753 // ajouter 'hasard' comme possibilité de tri dynamique
754 calculer_critere_par_hasard($idb, $boucles, $crit);
755 }
756 // tris textuels {par titre}
757 else {
758 $par = array_shift($tri);
759 $par = $par->texte;
760
761 // tris de la forme {par expression champ} tel que {par num titre} ou {par multi titre}
762 if (preg_match(",^(\w+)[\s]+(.*)$,", $par, $m)) {
763 $expression = trim($m[1]);
764 $champ = trim($m[2]);
765 if (function_exists($f = 'calculer_critere_par_expression_' . $expression)) {
766 $order = $f($idb, $boucles, $crit, $tri, $champ);
767 } else {
768 return array('zbug_critere_inconnu', array('critere' => $crit->op . " $par"));
769 }
770
771 // tris de la forme {par champ} ou {par FONCTION(champ)}
772 } elseif (preg_match(",^" . CHAMP_SQL_PLUS_FONC . '$,is', $par, $match)) {
773 // {par FONCTION(champ)}
774 if (count($match) > 2) {
775 $par = substr($match[2], 1, -1);
776 $fct = $match[1];
777 }
778 // quelques cas spécifiques {par hasard}, {par date}
779 if ($par == 'hasard') {
780 $order = calculer_critere_par_hasard($idb, $boucles, $crit);
781 } elseif ($par == 'date' and !empty($boucle->show['date'])) {
782 $order = "'" . $boucle->id_table . "." . $boucle->show['date'] . "'";
783 } else {
784 // cas général {par champ}, {par table.champ}, ...
785 $order = calculer_critere_par_champ($idb, $boucles, $crit, $par);
786 }
787 }
788
789 // on ne sait pas traiter…
790 else {
791 return array('zbug_critere_inconnu', array('critere' => $crit->op . " $par"));
792 }
793
794 // En cas d'erreur de squelette retournée par une fonction
795 if (is_array($order)) {
796 return $order;
797 }
798 }
799
800 if (preg_match('/^\'([^"]*)\'$/', $order, $m)) {
801 $t = $m[1];
802 if (strpos($t, '.') and !in_array($t, $boucle->select)) {
803 $boucle->select[] = $t;
804 }
805 } else {
806 $sens = '';
807 }
808
809 if ($fct) {
810 if (preg_match("/^\s*'(.*)'\s*$/", $order, $r)) {
811 $order = "'$fct(" . $r[1] . ")'";
812 } else {
813 $order = "'$fct(' . $order . ')'";
814 }
815 }
816 $t = $order . $collecte . $sens;
817 if (preg_match("/^(.*)'\s*\.\s*'([^']*')$/", $t, $r)) {
818 $t = $r[1] . $r[2];
819 }
820
821 $boucle->order[] = $t;
822 }
823 }
824
825 /**
826 * Calculs pour le critère `{par hasard}`
827 *
828 * Ajoute le générateur d'aléatoire au SELECT de la boucle.
829 *
830 * @param string $idb Identifiant de la boucle
831 * @param array $boucles AST du squelette
832 * @param Critere $crit Paramètres du critère dans cette boucle
833 * @return string Clause pour le Order by
834 */
835 function calculer_critere_par_hasard($idb, &$boucles, $crit) {
836 $boucle = &$boucles[$idb];
837 // Si ce n'est fait, ajouter un champ 'hasard' dans le select
838 $parha = "rand() AS hasard";
839 if (!in_array($parha, $boucle->select)) {
840 $boucle->select[] = $parha;
841 }
842 return "'hasard'";
843 }
844
845 /**
846 * Calculs pour le critère `{par num champ}` qui extrait le numéro préfixant un texte
847 *
848 * Tri par numéro de texte (tel que "10. titre"). Le numéro calculé est ajouté au SELECT
849 * de la boucle. L'écriture `{par num #ENV{tri}}` est aussi prise en compte.
850 *
851 * @note
852 * Les textes sans numéro valent 0 et sont donc placés avant les titres ayant des numéros.
853 * Utiliser `{par sinum champ, num champ}` pour avoir le comportement inverse.
854 *
855 * @see calculer_critere_par_expression_sinum() pour le critère `{par sinum champ}`
856 * @uses calculer_critere_par_champ()
857 *
858 * @param string $idb Identifiant de la boucle
859 * @param array $boucles AST du squelette
860 * @param Critere $crit Paramètres du critère dans cette boucle
861 * @param array $tri Paramètre en cours du critère
862 * @param string $champ Texte suivant l'expression ('titre' dans {par num titre})
863 * @return string Clause pour le Order by
864 */
865 function calculer_critere_par_expression_num($idb, &$boucles, $crit, $tri, $champ) {
866 $_champ = calculer_critere_par_champ($idb, $boucles, $crit, $champ, true);
867 if (is_array($_champ)) {
868 return array('zbug_critere_inconnu', array('critere' => $crit->op . " num $champ"));
869 }
870 $boucle = &$boucles[$idb];
871 $texte = '0+' . $_champ;
872 $suite = calculer_liste($tri, array(), $boucles, $boucle->id_parent);
873 if ($suite !== "''") {
874 $texte = "\" . ((\$x = $suite) ? ('$texte' . \$x) : '0')" . " . \"";
875 }
876 $as = 'num' . ($boucle->order ? count($boucle->order) : "");
877 $boucle->select[] = $texte . " AS $as";
878 $order = "'$as'";
879 return $order;
880 }
881
882 /**
883 * Calculs pour le critère `{par sinum champ}` qui ordonne les champs avec numéros en premier
884 *
885 * Ajoute au SELECT la valeur 'sinum' qui vaut 0 si le champ a un numéro, 1 s'il n'en a pas.
886 * Ainsi `{par sinum titre, num titre, titre}` mettra les éléments sans numéro en fin de liste,
887 * contrairement à `{par num titre, titre}` seulement.
888 *
889 * @see calculer_critere_par_expression_num() pour le critère `{par num champ}`
890 * @uses calculer_critere_par_champ()
891 *
892 * @param string $idb Identifiant de la boucle
893 * @param array $boucles AST du squelette
894 * @param Critere $crit Paramètres du critère dans cette boucle
895 * @param array $tri Paramètre en cours du critère
896 * @param string $champ Texte suivant l'expression ('titre' dans {par sinum titre})
897 * @return string Clause pour le Order by
898 */
899 function calculer_critere_par_expression_sinum($idb, &$boucles, $crit, $tri, $champ) {
900 $_champ = calculer_critere_par_champ($idb, $boucles, $crit, $champ, true);
901 if (is_array($_champ)) {
902 return array('zbug_critere_inconnu', array('critere' => $crit->op . " sinum $champ"));
903 }
904 $boucle = &$boucles[$idb];
905 $texte = '0+' . $_champ;
906 $suite = calculer_liste($tri, array(), $boucles, $boucle->id_parent);
907 if ($suite !== "''") {
908 $texte = "\" . ((\$x = $suite) ? ('$texte' . \$x) : '0')" . " . \"";
909 }
910 $as = 'sinum' . ($boucle->order ? count($boucle->order) : "");
911 $boucle->select[] = 'CASE (' . $texte . ') WHEN 0 THEN 1 ELSE 0 END AS ' . $as;
912 $order = "'$as'";
913 return $order;
914 }
915
916
917 /**
918 * Calculs pour le critère `{par multi champ}` qui extrait la langue en cours dans les textes
919 * ayant des balises `<multi>` (polyglottes)
920 *
921 * Ajoute le calcul du texte multi extrait dans le SELECT de la boucle.
922 * Il ne peut y avoir qu'un seul critère de tri `multi` par boucle.
923 *
924 * @uses calculer_critere_par_champ()
925 * @param string $idb Identifiant de la boucle
926 * @param array $boucles AST du squelette
927 * @param Critere $crit Paramètres du critère dans cette boucle
928 * @param array $tri Paramètre en cours du critère
929 * @param string $champ Texte suivant l'expression ('titre' dans {par multi titre})
930 * @return string Clause pour le Order by
931 */
932 function calculer_critere_par_expression_multi($idb, &$boucles, $crit, $tri, $champ) {
933 $_champ = calculer_critere_par_champ($idb, $boucles, $crit, $champ, true);
934 if (is_array($_champ)) {
935 return array('zbug_critere_inconnu', array('critere' => $crit->op . " multi $champ"));
936 }
937 $boucle = &$boucles[$idb];
938 $boucle->select[] = "\".sql_multi('" . $_champ . "', \$GLOBALS['spip_lang']).\"";
939 $order = "'multi'";
940 return $order;
941 }
942
943 /**
944 * Retourne le champ de tri demandé en ajoutant éventuellement les jointures nécessaires à la boucle.
945 *
946 * - si le champ existe dans la table, on l'utilise
947 * - si c'est une exception de jointure, on l'utilise (et crée la jointure au besoin)
948 * - si c'est un champ dont la jointure est déjà présente on la réutilise
949 * - si c'est un champ dont la jointure n'est pas présente, on la crée.
950 *
951 * @param string $idb Identifiant de la boucle
952 * @param array $boucles AST du squelette
953 * @param Critere $crit Paramètres du critère dans cette boucle
954 * @param string $par Nom du tri à analyser ('champ' ou 'table.champ')
955 * @param bool $raw Retourne le champ pour le compilateur ("'alias.champ'") ou brut ('alias.champ')
956 * @return array|string
957 */
958 function calculer_critere_par_champ($idb, &$boucles, $crit, $par, $raw = false) {
959 $boucle = &$boucles[$idb];
960
961 // le champ existe dans la table, pas de souci (le plus commun)
962 if (isset($desc['field'][$par])) {
963 $par = $boucle->id_table . "." . $par;
964 }
965 // le champ est peut être une jointure
966 else {
967 $table = $table_alias = false; // toutes les tables de jointure possibles
968 $champ = $par;
969
970 // le champ demandé est une exception de jointure {par titre_mot}
971 if (isset($GLOBALS['exceptions_des_jointures'][$par])) {
972 list($table, $champ) = $GLOBALS['exceptions_des_jointures'][$par];
973 } // la table de jointure est explicitement indiquée {par truc.muche}
974 elseif (preg_match("/^([^,]*)\.(.*)$/", $par, $r)) {
975 list(, $table, $champ) = $r;
976 $table_alias = $table; // c'est peut-être un alias de table {par L1.titre}
977 $table = table_objet_sql($table);
978 }
979
980 // Si on connait la table d'arrivée, on la demande donc explicitement
981 // Sinon on cherche le champ dans les tables possibles de jointures
982 // Si la table est déjà dans le from, on la réutilise.
983 if ($infos = chercher_champ_dans_tables($champ, $boucle->from, $boucle->sql_serveur, $table)) {
984 $par = $infos['alias'] . "." . $champ;
985 } elseif (
986 $boucle->jointures_explicites
987 and $alias = trouver_jointure_champ($champ, $boucle, explode(' ', $boucle->jointures_explicites), false, $table)
988 ) {
989 $par = $alias . "." . $champ;
990 } elseif ($alias = trouver_jointure_champ($champ, $boucle, $boucle->jointures, false, $table)) {
991 $par = $alias . "." . $champ;
992 // en spécifiant directement l'alias {par L2.titre} (situation hasardeuse tout de même)
993 } elseif (
994 $table_alias
995 and isset($boucle->from[$table_alias])
996 and $infos = chercher_champ_dans_tables($champ, $boucle->from, $boucle->sql_serveur, $boucle->from[$table_alias])
997 ) {
998 $par = $infos['alias'] . "." . $champ;
999 } elseif ($table) {
1000 // On avait table + champ, mais on ne les a pas trouvés
1001 return array('zbug_critere_inconnu', array('critere' => $crit->op . " $par"));
1002 } else {
1003 // Sinon tant pis, ca doit etre un champ synthetise (cf points)
1004 }
1005 }
1006
1007 return $raw ? $par : "'$par'";
1008 }
1009
1010 /**
1011 * Retourne un champ de tri en créant une jointure
1012 * si la table n'est pas présente dans le from de la boucle.
1013 *
1014 * @deprecated
1015 * @param string $table Table du champ désiré
1016 * @param string $champ Champ désiré
1017 * @param Boucle $boucle Boucle en cours de compilation
1018 * @return string Champ pour le compilateur si trouvé, tel que "'alias.champ'", sinon vide.
1019 */
1020 function critere_par_joint($table, $champ, &$boucle) {
1021 $t = array_search($table, $boucle->from);
1022 if (!$t) {
1023 $t = trouver_jointure_champ($champ, $boucle);
1024 }
1025 return !$t ? '' : ("'" . $t . '.' . $champ . "'");
1026 }
1027
1028 /**
1029 * Compile le critère `{inverse}` qui inverse l'ordre utilisé par le précédent critère `{par}`
1030 *
1031 * Accèpte un paramètre pour déterminer le sens : `{inverse #X}` utilisera un tri croissant (ASC)
1032 * si la valeur retournée par `#X` est considérée vrai (`true`),
1033 * le sens contraire (DESC) sinon.
1034 *
1035 * @example
1036 * - `{par date}{inverse}`, équivalent à `{!par date}`
1037 * - `{par date}{inverse #ENV{sens}}` utilise la valeur d'environnement sens pour déterminer le sens.
1038 *
1039 * @critere
1040 * @see critere_par_dist() Le critère `{par}`
1041 * @link http://www.spip.net/5530
1042 * @uses critere_parinverse()
1043 *
1044 * @param string $idb Identifiant de la boucle
1045 * @param array $boucles AST du squelette
1046 * @param Critere $crit Paramètres du critère dans cette boucle
1047 */
1048 function critere_inverse_dist($idb, &$boucles, $crit) {
1049
1050 $boucle = &$boucles[$idb];
1051 // Classement par ordre inverse
1052 if ($crit->not) {
1053 critere_parinverse($idb, $boucles, $crit);
1054 } else {
1055 $order = "' DESC'";
1056 // Classement par ordre inverse fonction eventuelle de #ENV{...}
1057 if (isset($crit->param[0])) {
1058 $critere = calculer_liste($crit->param[0], array(), $boucles, $boucles[$idb]->id_parent);
1059 $order = "(($critere)?' DESC':'')";
1060 }
1061
1062 $n = count($boucle->order);
1063 if (!$n) {
1064 if (isset($boucle->default_order[0])) {
1065 $boucle->default_order[0] .= ' . " DESC"';
1066 } else {
1067 $boucle->default_order[] = ' DESC';
1068 }
1069 } else {
1070 $t = $boucle->order[$n - 1] . " . $order";
1071 if (preg_match("/^(.*)'\s*\.\s*'([^']*')$/", $t, $r)) {
1072 $t = $r[1] . $r[2];
1073 }
1074 $boucle->order[$n - 1] = $t;
1075 }
1076 }
1077 }
1078
1079 // http://code.spip.net/@critere_agenda_dist
1080 function critere_agenda_dist($idb, &$boucles, $crit) {
1081 $params = $crit->param;
1082
1083 if (count($params) < 1) {
1084 return array('zbug_critere_inconnu', array('critere' => $crit->op . " ?"));
1085 }
1086
1087 $boucle = &$boucles[$idb];
1088 $parent = $boucle->id_parent;
1089 $fields = $boucle->show['field'];
1090
1091 $date = array_shift($params);
1092 $type = array_shift($params);
1093
1094 // la valeur $type doit etre connue a la compilation
1095 // donc etre forcement reduite a un litteral unique dans le source
1096 $type = is_object($type[0]) ? $type[0]->texte : null;
1097
1098 // La valeur date doit designer un champ de la table SQL.
1099 // Si c'est un litteral unique dans le source, verifier a la compil,
1100 // sinon synthetiser le test de verif pour execution ulterieure
1101 // On prendra arbitrairement le premier champ si test negatif.
1102 if ((count($date) == 1) and ($date[0]->type == 'texte')) {
1103 $date = $date[0]->texte;
1104 if (!isset($fields[$date])) {
1105 return array('zbug_critere_inconnu', array('critere' => $crit->op . " " . $date));
1106 }
1107 } else {
1108 $a = calculer_liste($date, array(), $boucles, $parent);
1109 $noms = array_keys($fields);
1110 $defaut = $noms[0];
1111 $noms = join(" ", $noms);
1112 # bien laisser 2 espaces avant $nom pour que strpos<>0
1113 $cond = "(\$a=strval($a))AND\nstrpos(\" $noms \",\" \$a \")";
1114 $date = "'.(($cond)\n?\$a:\"$defaut\").'";
1115 }
1116 $annee = $params ? array_shift($params) : "";
1117 $annee = "\n" . 'sprintf("%04d", ($x = ' .
1118 calculer_liste($annee, array(), $boucles, $parent) .
1119 ') ? $x : date("Y"))';
1120
1121 $mois = $params ? array_shift($params) : "";
1122 $mois = "\n" . 'sprintf("%02d", ($x = ' .
1123 calculer_liste($mois, array(), $boucles, $parent) .
1124 ') ? $x : date("m"))';
1125
1126 $jour = $params ? array_shift($params) : "";
1127 $jour = "\n" . 'sprintf("%02d", ($x = ' .
1128 calculer_liste($jour, array(), $boucles, $parent) .
1129 ') ? $x : date("d"))';
1130
1131 $annee2 = $params ? array_shift($params) : "";
1132 $annee2 = "\n" . 'sprintf("%04d", ($x = ' .
1133 calculer_liste($annee2, array(), $boucles, $parent) .
1134 ') ? $x : date("Y"))';
1135
1136 $mois2 = $params ? array_shift($params) : "";
1137 $mois2 = "\n" . 'sprintf("%02d", ($x = ' .
1138 calculer_liste($mois2, array(), $boucles, $parent) .
1139 ') ? $x : date("m"))';
1140
1141 $jour2 = $params ? array_shift($params) : "";
1142 $jour2 = "\n" . 'sprintf("%02d", ($x = ' .
1143 calculer_liste($jour2, array(), $boucles, $parent) .
1144 ') ? $x : date("d"))';
1145
1146 $date = $boucle->id_table . ".$date";
1147
1148 $quote_end = ",'" . $boucle->sql_serveur . "','text'";
1149 if ($type == 'jour') {
1150 $boucle->where[] = array(
1151 "'='",
1152 "'DATE_FORMAT($date, \'%Y%m%d\')'",
1153 ("sql_quote($annee . $mois . $jour$quote_end)")
1154 );
1155 } elseif ($type == 'mois') {
1156 $boucle->where[] = array(
1157 "'='",
1158 "'DATE_FORMAT($date, \'%Y%m\')'",
1159 ("sql_quote($annee . $mois$quote_end)")
1160 );
1161 } elseif ($type == 'semaine') {
1162 $boucle->where[] = array(
1163 "'AND'",
1164 array(
1165 "'>='",
1166 "'DATE_FORMAT($date, \'%Y%m%d\')'",
1167 ("date_debut_semaine($annee, $mois, $jour)")
1168 ),
1169 array(
1170 "'<='",
1171 "'DATE_FORMAT($date, \'%Y%m%d\')'",
1172 ("date_fin_semaine($annee, $mois, $jour)")
1173 )
1174 );
1175 } elseif (count($crit->param) > 2) {
1176 $boucle->where[] = array(
1177 "'AND'",
1178 array(
1179 "'>='",
1180 "'DATE_FORMAT($date, \'%Y%m%d\')'",
1181 ("sql_quote($annee . $mois . $jour$quote_end)")
1182 ),
1183 array("'<='", "'DATE_FORMAT($date, \'%Y%m%d\')'", ("sql_quote($annee2 . $mois2 . $jour2$quote_end)"))
1184 );
1185 }
1186 // sinon on prend tout
1187 }
1188
1189
1190 /**
1191 * Compile les critères {i,j} et {i/j}
1192 *
1193 * Le critère {i,j} limite l'affiche de la boucle en commançant l'itération
1194 * au i-ème élément, et pour j nombre d'éléments.
1195 * Le critère {n-i,j} limite en commençant au n moins i-ème élément de boucle
1196 * Le critère {i,n-j} limite en terminant au n moins j-ème élément de boucle.
1197 *
1198 * Le critère {i/j} affiche une part d'éléments de la boucle.
1199 * Commence à i*n/j élément et boucle n/j éléments. {2/4} affiche le second
1200 * quart des éléments d'une boucle.
1201 *
1202 * Traduit si possible (absence de n dans {i,j}) la demande en une
1203 * expression LIMIT du gestionnaire SQL
1204 *
1205 * @param string $idb Identifiant de la boucle
1206 * @param array $boucles AST du squelette
1207 * @param Critere $crit Paramètres du critère dans cette boucle
1208 * @return void
1209 **/
1210 function calculer_critere_parties($idb, &$boucles, $crit) {
1211 $boucle = &$boucles[$idb];
1212 $a1 = $crit->param[0];
1213 $a2 = $crit->param[1];
1214 $op = $crit->op;
1215
1216 list($a11, $a12) = calculer_critere_parties_aux($idb, $boucles, $a1);
1217 list($a21, $a22) = calculer_critere_parties_aux($idb, $boucles, $a2);
1218
1219 if (($op == ',') && (is_numeric($a11) && (is_numeric($a21)))) {
1220 $boucle->limit = $a11 . ',' . $a21;
1221 } else {
1222 // 3 dans {1/3}, {2,3} ou {1,n-3}
1223 $boucle->total_parties = ($a21 != 'n') ? $a21 : $a22;
1224 // 2 dans {2/3}, {2,5}, {n-2,1}
1225 $partie = ($a11 != 'n') ? $a11 : $a12;
1226 $mode = (($op == '/') ? '/' :
1227 (($a11 == 'n') ? '-' : '+') . (($a21 == 'n') ? '-' : '+'));
1228 // cas simple {0,#ENV{truc}} compilons le en LIMIT :
1229 if ($a11 !== 'n' and $a21 !== 'n' and $mode == "++" and $op == ',') {
1230 $boucle->limit =
1231 (is_numeric($a11) ? "'$a11'" : $a11)
1232 . ".','."
1233 . (is_numeric($a21) ? "'$a21'" : $a21);
1234 } else {
1235 calculer_parties($boucles, $idb, $partie, $mode);
1236 }
1237 }
1238 }
1239
1240 /**
1241 * Compile certains critères {i,j} et {i/j}
1242 *
1243 * Calcule une expression déterminant $debut_boucle et $fin_boucle (le
1244 * début et la fin des éléments de la boucle qui doivent être affichés)
1245 * et les déclare dans la propriété «mode_partie» de la boucle, qui se
1246 * charge également de déplacer le pointeur de boucle sur le premier
1247 * élément à afficher.
1248 *
1249 * Place dans la propriété partie un test vérifiant que l'élément de
1250 * boucle en cours de lecture appartient bien à la plage autorisée.
1251 * Trop tôt, passe à l'élément suivant, trop tard, sort de l'itération de boucle.
1252 *
1253 * @param array $boucles AST du squelette
1254 * @param string $id_boucle Identifiant de la boucle
1255 * @param string $debut Valeur ou code pour trouver le début (i dans {i,j})
1256 * @param string $mode
1257 * Mode (++, p+, +- ...) : 2 signes début & fin
1258 * - le signe - indique
1259 * -- qu'il faut soustraire debut du total {n-3,x}. 3 étant $debut
1260 * -- qu'il faut raccourcir la fin {x,n-3} de 3 elements. 3 étant $total_parties
1261 * - le signe p indique une pagination
1262 * @return void
1263 **/
1264 function calculer_parties(&$boucles, $id_boucle, $debut, $mode) {
1265 $total_parties = $boucles[$id_boucle]->total_parties;
1266
1267 preg_match(",([+-/p])([+-/])?,", $mode, $regs);
1268 list(, $op1, $op2) = array_pad($regs, 3, null);
1269 $nombre_boucle = "\$Numrows['$id_boucle']['total']";
1270 // {1/3}
1271 if ($op1 == '/') {
1272 $pmoins1 = is_numeric($debut) ? ($debut - 1) : "($debut-1)";
1273 $totpos = is_numeric($total_parties) ? ($total_parties) :
1274 "($total_parties ? $total_parties : 1)";
1275 $fin = "ceil(($nombre_boucle * $debut )/$totpos) - 1";
1276 $debut = !$pmoins1 ? 0 : "ceil(($nombre_boucle * $pmoins1)/$totpos);";
1277 } else {
1278 // cas {n-1,x}
1279 if ($op1 == '-') {
1280 $debut = "$nombre_boucle - $debut;";
1281 }
1282
1283 // cas {x,n-1}
1284 if ($op2 == '-') {
1285 $fin = '$debut_boucle + ' . $nombre_boucle . ' - '
1286 . (is_numeric($total_parties) ? ($total_parties + 1) :
1287 ($total_parties . ' - 1'));
1288 } else {
1289 // {x,1} ou {pagination}
1290 $fin = '$debut_boucle'
1291 . (is_numeric($total_parties) ?
1292 (($total_parties == 1) ? "" : (' + ' . ($total_parties - 1))) :
1293 ('+' . $total_parties . ' - 1'));
1294 }
1295
1296 // {pagination}, gerer le debut_xx=-1 pour tout voir
1297 if ($op1 == 'p') {
1298 $debut .= ";\n \$debut_boucle = ((\$tout=(\$debut_boucle == -1))?0:(\$debut_boucle))";
1299 $debut .= ";\n \$debut_boucle = max(0,min(\$debut_boucle,floor(($nombre_boucle-1)/($total_parties))*($total_parties)))";
1300 $fin = "(\$tout ? $nombre_boucle : $fin)";
1301 }
1302 }
1303
1304 // Notes :
1305 // $debut_boucle et $fin_boucle sont les indices SQL du premier
1306 // et du dernier demandes dans la boucle : 0 pour le premier,
1307 // n-1 pour le dernier ; donc total_boucle = 1 + debut - fin
1308 // Utiliser min pour rabattre $fin_boucle sur total_boucle.
1309
1310 $boucles[$id_boucle]->mode_partie = "\n\t"
1311 . '$debut_boucle = ' . $debut . ";\n "
1312 . "\$debut_boucle = intval(\$debut_boucle);\n "
1313 . '$fin_boucle = min(' . $fin . ", \$Numrows['$id_boucle']['total'] - 1);\n "
1314 . '$Numrows[\'' . $id_boucle . "']['grand_total'] = \$Numrows['$id_boucle']['total'];\n "
1315 . '$Numrows[\'' . $id_boucle . '\']["total"] = max(0,$fin_boucle - $debut_boucle + 1);'
1316 . "\n\tif (\$debut_boucle>0"
1317 . " AND \$debut_boucle < \$Numrows['$id_boucle']['grand_total']"
1318 . " AND \$iter->seek(\$debut_boucle,'continue'))"
1319 . "\n\t\t\$Numrows['$id_boucle']['compteur_boucle'] = \$debut_boucle;\n\t";
1320
1321 $boucles[$id_boucle]->partie = "
1322 if (\$Numrows['$id_boucle']['compteur_boucle'] <= \$debut_boucle) continue;
1323 if (\$Numrows['$id_boucle']['compteur_boucle']-1 > \$fin_boucle) break;";
1324 }
1325
1326 /**
1327 * Analyse un des éléments des critères {a,b} ou {a/b}
1328 *
1329 * Pour l'élément demandé (a ou b) retrouve la valeur de l'élément,
1330 * et de combien il est soustrait si c'est le cas comme dans {a-3,b}
1331 *
1332 * @param string $idb Identifiant de la boucle
1333 * @param array $boucles AST du squelette
1334 * @param array $param Paramètre à analyser (soit a, soit b dans {a,b} ou {a/b})
1335 * @return array Valeur de l'élément (peut être une expression PHP), Nombre soustrait
1336 **/
1337 function calculer_critere_parties_aux($idb, &$boucles, $param) {
1338 if ($param[0]->type != 'texte') {
1339 $a1 = calculer_liste(array($param[0]), array('id_mere' => $idb), $boucles, $boucles[$idb]->id_parent);
1340 if (isset($param[1]->texte)) {
1341 preg_match(',^ *(-([0-9]+))? *$,', $param[1]->texte, $m);
1342
1343 return array("intval($a1)", ((isset($m[2]) and $m[2]) ? $m[2] : 0));
1344 } else {
1345 return array("intval($a1)", 0);
1346 }
1347 } else {
1348 preg_match(',^ *(([0-9]+)|n) *(- *([0-9]+)? *)?$,', $param[0]->texte, $m);
1349 $a1 = $m[1];
1350 if (empty($m[3])) {
1351 return array($a1, 0);
1352 } elseif (!empty($m[4])) {
1353 return array($a1, $m[4]);
1354 } else {
1355 return array($a1, calculer_liste(array($param[1]), array(), $boucles, $boucles[$idb]->id_parent));
1356 }
1357 }
1358 }
1359
1360
1361 /**
1362 * Compile les critères d'une boucle
1363 *
1364 * Cette fonction d'aiguillage cherche des fonctions spécifiques déclarées
1365 * pour chaque critère demandé, dans l'ordre ci-dessous :
1366 *
1367 * - critere_{serveur}_{table}_{critere}, sinon avec _dist
1368 * - critere_{serveur}_{critere}, sinon avec _dist
1369 * - critere_{table}_{critere}, sinon avec _dist
1370 * - critere_{critere}, sinon avec _dist
1371 * - calculer_critere_defaut, sinon avec _dist
1372 *
1373 * Émet une erreur de squelette si un critère retourne une erreur.
1374 *
1375 * @param string $idb
1376 * Identifiant de la boucle
1377 * @param array $boucles
1378 * AST du squelette
1379 * @return string|array
1380 * string : Chaine vide sans erreur
1381 * array : Erreur sur un des critères
1382 **/
1383 function calculer_criteres($idb, &$boucles) {
1384 $msg = '';
1385 $boucle = $boucles[$idb];
1386 $table = strtoupper($boucle->type_requete);
1387 $serveur = strtolower($boucle->sql_serveur);
1388
1389 $defaut = charger_fonction('DEFAUT', 'calculer_critere');
1390 // s'il y avait une erreur de syntaxe, propager cette info
1391 if (!is_array($boucle->criteres)) {
1392 return array();
1393 }
1394
1395 foreach ($boucle->criteres as $crit) {
1396 $critere = $crit->op;
1397 // critere personnalise ?
1398 if (
1399 (!$serveur or
1400 ((!function_exists($f = "critere_" . $serveur . "_" . $table . "_" . $critere))
1401 and (!function_exists($f = $f . "_dist"))
1402 and (!function_exists($f = "critere_" . $serveur . "_" . $critere))
1403 and (!function_exists($f = $f . "_dist"))
1404 )
1405 )
1406 and (!function_exists($f = "critere_" . $table . "_" . $critere))
1407 and (!function_exists($f = $f . "_dist"))
1408 and (!function_exists($f = "critere_" . $critere))
1409 and (!function_exists($f = $f . "_dist"))
1410 ) {
1411 // fonction critere standard
1412 $f = $defaut;
1413 }
1414 // compile le critere
1415 $res = $f($idb, $boucles, $crit);
1416
1417 // Gestion centralisee des erreurs pour pouvoir propager
1418 if (is_array($res)) {
1419 $msg = $res;
1420 erreur_squelette($msg, $boucle);
1421 }
1422 }
1423
1424 return $msg;
1425 }
1426
1427 /**
1428 * Désemberlificote les guillements et échappe (ou fera échapper) le contenu...
1429 *
1430 * Madeleine de Proust, revision MIT-1958 sqq, revision CERN-1989
1431 * hum, c'est kwoi cette fonxion ? on va dire qu'elle desemberlificote les guillemets...
1432 *
1433 * http://code.spip.net/@kwote
1434 *
1435 * @param string $lisp Code compilé
1436 * @param string $serveur Connecteur de bdd utilisé
1437 * @param string $type Type d'échappement (char, int...)
1438 * @return string Code compilé rééchappé
1439 */
1440 function kwote($lisp, $serveur = '', $type = '') {
1441 if (preg_match(_CODE_QUOTE, $lisp, $r)) {
1442 return $r[1] . "\"" . sql_quote(str_replace(array("\\'", "\\\\"), array("'", "\\"), $r[2]), $serveur, $type) . "\"";
1443 } else {
1444 return "sql_quote($lisp, '$serveur', '" . str_replace("'", "\\'", $type) . "')";
1445 }
1446 }
1447
1448
1449 /**
1450 * Compile un critère possédant l'opérateur IN : {xx IN yy}
1451 *
1452 * Permet de restreindre un champ sur une liste de valeurs tel que
1453 * {id_article IN 3,4} {id_article IN #LISTE{3,4}}
1454 *
1455 * Si on a une liste de valeurs dans #ENV{x}, utiliser la double etoile
1456 * pour faire par exemple {id_article IN #ENV**{liste_articles}}
1457 *
1458 * @param string $idb Identifiant de la boucle
1459 * @param array $boucles AST du squelette
1460 * @param Critere $crit Paramètres du critère dans cette boucle
1461 * @return void
1462 **/
1463 function critere_IN_dist($idb, &$boucles, $crit) {
1464 $r = calculer_critere_infixe($idb, $boucles, $crit);
1465 if (!$r) {
1466 return (array('zbug_critere_inconnu', array('critere' => $crit->op . " ?")));
1467 }
1468 list($arg, $op, $val, $col, $where_complement) = $r;
1469
1470 $in = critere_IN_cas($idb, $boucles, $crit->not ? 'NOT' : ($crit->exclus ? 'exclus' : ''), $arg, $op, $val, $col);
1471
1472 // inserer la condition; exemple: {id_mot ?IN (66, 62, 64)}
1473 $where = $in;
1474 if ($crit->cond) {
1475 $pred = calculer_argument_precedent($idb, $col, $boucles);
1476 $where = array("'?'", $pred, $where, "''");
1477 if ($where_complement) // condition annexe du type "AND (objet='article')"
1478 {
1479 $where_complement = array("'?'", $pred, $where_complement, "''");
1480 }
1481 }
1482 if ($crit->exclus) {
1483 if (!preg_match(",^L[0-9]+[.],", $arg)) {
1484 $where = array("'NOT'", $where);
1485 } else
1486 // un not sur un critere de jointure se traduit comme un NOT IN avec une sous requete
1487 // c'est une sous requete identique a la requete principale sous la forme (SELF,$select,$where) avec $select et $where qui surchargent
1488 {
1489 $where = array(
1490 "'NOT'",
1491 array(
1492 "'IN'",
1493 "'" . $boucles[$idb]->id_table . "." . $boucles[$idb]->primary . "'",
1494 array("'SELF'", "'" . $boucles[$idb]->id_table . "." . $boucles[$idb]->primary . "'", $where)
1495 )
1496 );
1497 }
1498 }
1499
1500 $boucles[$idb]->where[] = $where;
1501 if ($where_complement) // condition annexe du type "AND (objet='article')"
1502 {
1503 $boucles[$idb]->where[] = $where_complement;
1504 }
1505 }
1506
1507 // http://code.spip.net/@critere_IN_cas
1508 function critere_IN_cas($idb, &$boucles, $crit2, $arg, $op, $val, $col) {
1509 static $num = array();
1510 $descr = $boucles[$idb]->descr;
1511 $cpt = &$num[$descr['nom']][$descr['gram']][$idb];
1512
1513 $var = '$in' . $cpt++;
1514 $x = "\n\t$var = array();";
1515 foreach ($val as $k => $v) {
1516 if (preg_match(",^(\n//.*\n)?'(.*)'$,", $v, $r)) {
1517 // optimiser le traitement des constantes
1518 if (is_numeric($r[2])) {
1519 $x .= "\n\t$var" . "[]= $r[2];";
1520 } else {
1521 $x .= "\n\t$var" . "[]= " . sql_quote($r[2]) . ";";
1522 }
1523 } else {
1524 // Pour permettre de passer des tableaux de valeurs
1525 // on repere l'utilisation brute de #ENV**{X},
1526 // c'est-a-dire sa traduction en ($PILE[0][X]).
1527 // et on deballe mais en rajoutant l'anti XSS
1528 $x .= "\n\tif (!(is_array(\$a = ($v))))\n\t\t$var" . "[]= \$a;\n\telse $var = array_merge($var, \$a);";
1529 }
1530 }
1531
1532 $boucles[$idb]->in .= $x;
1533
1534 // inserer le tri par defaut selon les ordres du IN ...
1535 // avec une ecriture de type FIELD qui degrade les performances (du meme ordre qu'un regexp)
1536 // et que l'on limite donc strictement aux cas necessaires :
1537 // si ce n'est pas un !IN, et si il n'y a pas d'autre order dans la boucle
1538 if (!$crit2) {
1539 $boucles[$idb]->default_order[] = "((!sql_quote($var) OR sql_quote($var)===\"''\") ? 0 : ('FIELD($arg,' . sql_quote($var) . ')'))";
1540 }
1541
1542 return "sql_in('$arg',sql_quote($var)" . ($crit2 == 'NOT' ? ",'NOT'" : "") . ")";
1543 }
1544
1545 /**
1546 * Compile le critère {where}
1547 *
1548 * Ajoute une contrainte sql WHERE, tout simplement pour faire le pont
1549 * entre php et squelettes, en utilisant la syntaxe attendue par
1550 * la propriété $where d'une Boucle.
1551 *
1552 * @param string $idb Identifiant de la boucle
1553 * @param array $boucles AST du squelette
1554 * @param Critere $crit Paramètres du critère dans cette boucle
1555 * @return void
1556 */
1557 function critere_where_dist($idb, &$boucles, $crit) {
1558 $boucle = &$boucles[$idb];
1559 if (isset($crit->param[0])) {
1560 $_where = calculer_liste($crit->param[0], array(), $boucles, $boucle->id_parent);
1561 } else {
1562 $_where = 'spip_sanitize_from_request(@$Pile[0]["where"],"where","vide")';
1563 }
1564
1565 if ($crit->cond) {
1566 $_where = "((\$zzw = $_where) ? \$zzw : '')";
1567 }
1568
1569 if ($crit->not) {
1570 $_where = "array('NOT',$_where)";
1571 }
1572
1573 $boucle->where[] = $_where;
1574 }
1575
1576
1577 /**
1578 * Compile le critère `{tri}` permettant le tri dynamique d'un champ
1579 *
1580 * Le critère `{tri}` gère un champ de tri qui peut être modifié dynamiquement par la balise `#TRI`.
1581 * Il s'utilise donc conjointement avec la balise `#TRI` dans la même boucle
1582 * pour génerér les liens qui permettent de changer le critère de tri et le sens du tri
1583 *
1584 * @syntaxe `{tri [champ_par_defaut][,sens_par_defaut][,nom_variable]}`
1585 *
1586 * - champ_par_defaut : un champ de la table sql
1587 * - sens_par_defaut : -1 ou inverse pour décroissant, 1 ou direct pour croissant
1588 * peut être un tableau pour préciser des sens par défaut associés à chaque champ
1589 * exemple : `array('titre' => 1, 'date' => -1)` pour trier par défaut
1590 * les titre croissants et les dates décroissantes
1591 * dans ce cas, quand un champ est utilisé pour le tri et n'est pas présent dans le tableau
1592 * c'est la première valeur qui est utilisée
1593 * - nom_variable : nom de la variable utilisée (par defaut `tri_{nomboucle}`)
1594 *
1595 * {tri titre}
1596 * {tri titre,inverse}
1597 * {tri titre,-1}
1598 * {tri titre,-1,truc}
1599 *
1600 * Exemple d'utilisation :
1601 *
1602 * <B_articles>
1603 * <p>#TRI{titre,'Trier par titre'} | #TRI{date,'Trier par date'}</p>
1604 * <ul>
1605 * <BOUCLE_articles(ARTICLES){tri titre}>
1606 * <li>#TITRE - [(#DATE|affdate_jourcourt)]</li>
1607 * </BOUCLE_articles>
1608 * </ul>
1609 * </B_articles>
1610 *
1611 * @note
1612 * Contraitement à `{par ...}`, `{tri}` ne peut prendre qu'un seul champ,
1613 * mais il peut être complété avec `{par ...}` pour indiquer des criteres secondaires
1614 *
1615 * Exemble :
1616 * `{tri num titre}{par titre}` permet de faire un tri sur le rang (modifiable dynamiquement)
1617 * avec un second critère sur le titre en cas d'égalité des rangs
1618 *
1619 * @link http://www.spip.net/5429
1620 * @see critere_par_dist() Le critère `{par ...}`
1621 * @see balise_TRI_dist() La balise `#TRI`
1622 *
1623 * @param string $idb Identifiant de la boucle
1624 * @param array $boucles AST du squelette
1625 * @param Critere $crit Paramètres du critère dans cette boucle
1626 * @return void
1627 */
1628 function critere_tri_dist($idb, &$boucles, $crit) {
1629 $boucle = &$boucles[$idb];
1630
1631 // definition du champ par defaut
1632 $_champ_defaut = !isset($crit->param[0][0]) ? "''"
1633 : calculer_liste(array($crit->param[0][0]), array(), $boucles, $boucle->id_parent);
1634 $_sens_defaut = !isset($crit->param[1][0]) ? "1"
1635 : calculer_liste(array($crit->param[1][0]), array(), $boucles, $boucle->id_parent);
1636 $_variable = !isset($crit->param[2][0]) ? "'$idb'"
1637 : calculer_liste(array($crit->param[2][0]), array(), $boucles, $boucle->id_parent);
1638
1639 $_tri = "((\$t=(isset(\$Pile[0]['tri'.$_variable]))?\$Pile[0]['tri'.$_variable]:((strncmp($_variable,'session',7)==0 AND session_get('tri'.$_variable))?session_get('tri'.$_variable):$_champ_defaut))?tri_protege_champ(\$t):'')";
1640
1641 $_sens_defaut = "(is_array(\$s=$_sens_defaut)?(isset(\$s[\$st=$_tri])?\$s[\$st]:reset(\$s)):\$s)";
1642 $_sens = "((intval(\$t=(isset(\$Pile[0]['sens'.$_variable]))?\$Pile[0]['sens'.$_variable]:((strncmp($_variable,'session',7)==0 AND session_get('sens'.$_variable))?session_get('sens'.$_variable):$_sens_defaut))==-1 OR \$t=='inverse')?-1:1)";
1643
1644 $boucle->modificateur['tri_champ'] = $_tri;
1645 $boucle->modificateur['tri_sens'] = $_sens;
1646 $boucle->modificateur['tri_nom'] = $_variable;
1647 // faut il inserer un test sur l'existence de $tri parmi les champs de la table ?
1648 // evite des erreurs sql, mais peut empecher des tri sur jointure ...
1649 $boucle->hash .= "
1650 \$senstri = '';
1651 \$tri = $_tri;
1652 if (\$tri){
1653 \$senstri = $_sens;
1654 \$senstri = (\$senstri<0)?' DESC':'';
1655 };
1656 ";
1657 $boucle->select[] = "\".tri_champ_select(\$tri).\"";
1658 $boucle->order[] = "tri_champ_order(\$tri,\$command['from']).\$senstri";
1659 }
1660
1661 # Criteres de comparaison
1662
1663 /**
1664 * Compile un critère non déclaré explicitement
1665 *
1666 * Compile les critères non déclarés, ainsi que les parties de boucles
1667 * avec les critères {0,1} ou {1/2}
1668 *
1669 * @param string $idb Identifiant de la boucle
1670 * @param array $boucles AST du squelette
1671 * @param Critere $crit Paramètres du critère dans cette boucle
1672 * @return void
1673 **/
1674 function calculer_critere_DEFAUT_dist($idb, &$boucles, $crit) {
1675 // double cas particulier {0,1} et {1/2} repere a l'analyse lexicale
1676 if (($crit->op == ",") or ($crit->op == '/')) {
1677 return calculer_critere_parties($idb, $boucles, $crit);
1678 }
1679
1680 $r = calculer_critere_infixe($idb, $boucles, $crit);
1681 if (!$r) {
1682 # // on produit une erreur seulement si le critere n'a pas de '?'
1683 # if (!$crit->cond) {
1684 return (array('zbug_critere_inconnu', array('critere' => $crit->op)));
1685 # }
1686 } else {
1687 calculer_critere_DEFAUT_args($idb, $boucles, $crit, $r);
1688 }
1689 }
1690
1691
1692 /**
1693 * Compile un critère non déclaré explicitement, dont on reçoit une analyse
1694 *
1695 * Ajoute en fonction des arguments trouvés par calculer_critere_infixe()
1696 * les conditions WHERE à appliquer sur la boucle.
1697 *
1698 * @see calculer_critere_infixe()
1699 *
1700 * @param string $idb Identifiant de la boucle
1701 * @param array $boucles AST du squelette
1702 * @param Critere $crit Paramètres du critère dans cette boucle
1703 * @param array $args Description du critère
1704 * Cf. retour de calculer_critere_infixe()
1705 * @return void
1706 **/
1707 function calculer_critere_DEFAUT_args($idb, &$boucles, $crit, $args) {
1708 list($arg, $op, $val, $col, $where_complement) = $args;
1709
1710 $where = array("'$op'", "'$arg'", $val[0]);
1711
1712 // inserer la negation (cf !...)
1713
1714 if ($crit->not) {
1715 $where = array("'NOT'", $where);
1716 }
1717 if ($crit->exclus) {
1718 if (!preg_match(",^L[0-9]+[.],", $arg)) {
1719 $where = array("'NOT'", $where);
1720 } else
1721 // un not sur un critere de jointure se traduit comme un NOT IN avec une sous requete
1722 // c'est une sous requete identique a la requete principale sous la forme (SELF,$select,$where) avec $select et $where qui surchargent
1723 {
1724 $where = array(
1725 "'NOT'",
1726 array(
1727 "'IN'",
1728 "'" . $boucles[$idb]->id_table . "." . $boucles[$idb]->primary . "'",
1729 array("'SELF'", "'" . $boucles[$idb]->id_table . "." . $boucles[$idb]->primary . "'", $where)
1730 )
1731 );
1732 }
1733 }
1734
1735 // inserer la condition (cf {lang?})
1736 // traiter a part la date, elle est mise d'office par SPIP,
1737 if ($crit->cond) {
1738 $pred = calculer_argument_precedent($idb, $col, $boucles);
1739 if ($col == "date" or $col == "date_redac") {
1740 if ($pred == "\$Pile[0]['" . $col . "']") {
1741 $pred = "(\$Pile[0]['{$col}_default']?'':$pred)";
1742 }
1743 }
1744
1745 if ($op == '=' and !$crit->not) {
1746 $where = array(
1747 "'?'",
1748 "(is_array($pred))",
1749 critere_IN_cas($idb, $boucles, 'COND', $arg, $op, array($pred), $col),
1750 $where
1751 );
1752 }
1753 $where = array("'?'", "!(is_array($pred)?count($pred):strlen($pred))", "''", $where);
1754 if ($where_complement) // condition annexe du type "AND (objet='article')"
1755 {
1756 $where_complement = array("'?'", "!(is_array($pred)?count($pred):strlen($pred))", "''", $where_complement);
1757 }
1758 }
1759
1760 $boucles[$idb]->where[] = $where;
1761 if ($where_complement) // condition annexe du type "AND (objet='article')"
1762 {
1763 $boucles[$idb]->where[] = $where_complement;
1764 }
1765 }
1766
1767
1768 /**
1769 * Décrit un critère non déclaré explicitement
1770 *
1771 * Décrit un critère non déclaré comme {id_article} {id_article>3} en
1772 * retournant un tableau de l'analyse si la colonne (ou l'alias) existe vraiment.
1773 *
1774 * Ajoute au passage pour chaque colonne utilisée (alias et colonne véritable)
1775 * un modificateur['criteres'][colonne].
1776 *
1777 * S'occupe de rechercher des exceptions, tel que
1778 * - les id_parent, id_enfant, id_secteur,
1779 * - des colonnes avec des exceptions déclarées,
1780 * - des critères de date (jour_relatif, ...),
1781 * - des critères sur tables jointes explicites (mots.titre),
1782 * - des critères sur tables de jointure non explicite (id_mot sur une boucle articles...)
1783 *
1784 *
1785 * @param string $idb Identifiant de la boucle
1786 * @param array $boucles AST du squelette
1787 * @param Critere $crit Paramètres du critère dans cette boucle
1788 * @return array|string
1789 * Liste si on trouve le champ :
1790 * - string $arg
1791 * Opérande avant l'opérateur : souvent la colonne d'application du critère, parfois un calcul
1792 * plus complexe dans le cas des dates.
1793 * - string $op
1794 * L'opérateur utilisé, tel que '='
1795 * - string[] $val
1796 * Liste de codes PHP obtenant les valeurs des comparaisons (ex: id_article sur la boucle parente)
1797 * Souvent (toujours ?) un tableau d'un seul élément.
1798 * - $col_alias
1799 * - $where_complement
1800 *
1801 * Chaîne vide si on ne trouve pas le champ...
1802 **/
1803 function calculer_critere_infixe($idb, &$boucles, $crit) {
1804
1805 $boucle = &$boucles[$idb];
1806 $type = $boucle->type_requete;
1807 $table = $boucle->id_table;
1808 $desc = $boucle->show;
1809 $col_vraie = null;
1810
1811 list($fct, $col, $op, $val, $args_sql) =
1812 calculer_critere_infixe_ops($idb, $boucles, $crit);
1813
1814 $col_alias = $col;
1815 $where_complement = false;
1816
1817 // Cas particulier : id_enfant => utiliser la colonne id_objet
1818 if ($col == 'id_enfant') {
1819 $col = $boucle->primary;
1820 }
1821
1822 // Cas particulier : id_parent => verifier les exceptions de tables
1823 if ((in_array($col, array('id_parent', 'id_secteur')) and isset($GLOBALS['exceptions_des_tables'][$table][$col]))
1824 or (isset($GLOBALS['exceptions_des_tables'][$table][$col]) and is_string($GLOBALS['exceptions_des_tables'][$table][$col]))
1825 ) {
1826 $col = $GLOBALS['exceptions_des_tables'][$table][$col];
1827 } // et possibilite de gerer un critere secteur sur des tables de plugins (ie forums)
1828 else {
1829 if (($col == 'id_secteur') and ($critere_secteur = charger_fonction("critere_secteur_$type", "public", true))) {
1830 $table = $critere_secteur($idb, $boucles, $val, $crit);
1831 }
1832
1833 // cas id_article=xx qui se mappe en id_objet=xx AND objet=article
1834 // sauf si exception declaree : sauter cette etape
1835 else {
1836 if (
1837 !isset($GLOBALS['exceptions_des_jointures'][table_objet_sql($table)][$col])
1838 and !isset($GLOBALS['exceptions_des_jointures'][$col])
1839 and count(trouver_champs_decomposes($col, $desc)) > 1
1840 ) {
1841 $e = decompose_champ_id_objet($col);
1842 $col = array_shift($e);
1843 $where_complement = primary_doublee($e, $table);
1844 } // Cas particulier : expressions de date
1845 else {
1846 if ($c = calculer_critere_infixe_date($idb, $boucles, $col)) {
1847 list($col, $col_vraie) = $c;
1848 $table = '';
1849 } // table explicitée {mots.titre}
1850 else {
1851 if (preg_match('/^(.*)\.(.*)$/', $col, $r)) {
1852 list(, $table, $col) = $r;
1853 $col_alias = $col;
1854
1855 $trouver_table = charger_fonction('trouver_table', 'base');
1856 if ($desc = $trouver_table($table, $boucle->sql_serveur)
1857 and isset($desc['field'][$col])
1858 and $cle = array_search($desc['table'], $boucle->from)
1859 ) {
1860 $table = $cle;
1861 } else {
1862 $table = trouver_jointure_champ($col, $boucle, array($table), ($crit->cond or $op != '='));
1863 }
1864 #$table = calculer_critere_externe_init($boucle, array($table), $col, $desc, ($crit->cond OR $op!='='), true);
1865 if (!$table) {
1866 return '';
1867 }
1868 }
1869 // si le champ n'est pas trouvé dans la table,
1870 // on cherche si une jointure peut l'obtenir
1871 elseif (@!array_key_exists($col, $desc['field'])) {
1872 // Champ joker * des iterateurs DATA qui accepte tout
1873 if (@array_key_exists('*', $desc['field'])) {
1874 $desc['field'][$col_vraie ? $col_vraie : $col] = ''; // on veut pas de cast INT par defaut car le type peut etre n'importe quoi dans les boucles DATA
1875 }
1876 else {
1877 $r = calculer_critere_infixe_externe($boucle, $crit, $op, $desc, $col, $col_alias, $table);
1878 if (!$r) {
1879 return '';
1880 }
1881 list($col, $col_alias, $table, $where_complement, $desc) = $r;
1882 }
1883 }
1884 }
1885 }
1886 }
1887 }
1888
1889 $col_vraie = ($col_vraie ? $col_vraie : $col);
1890 // Dans tous les cas,
1891 // virer les guillemets eventuels autour d'un int (qui sont refuses par certains SQL)
1892 // et passer dans sql_quote avec le type si connu
1893 // et int sinon si la valeur est numerique
1894 // sinon introduire le vrai type du champ si connu dans le sql_quote (ou int NOT NULL sinon)
1895 // Ne pas utiliser intval, PHP tronquant les Bigint de SQL
1896 if ($op == '=' or in_array($op, $GLOBALS['table_criteres_infixes'])) {
1897 $type_cast_quote = (isset($desc['field'][$col_vraie]) ? $desc['field'][$col_vraie] : 'int NOT NULL');
1898 // defaire le quote des int et les passer dans sql_quote avec le bon type de champ si on le connait, int sinon
1899 // prendre en compte le debug ou la valeur arrive avec un commentaire PHP en debut
1900 if (preg_match(",^\\A(\s*//.*?$\s*)?\"'(-?\d+)'\"\\z,ms", $val[0], $r)) {
1901 $val[0] = $r[1] . '"' . sql_quote($r[2], $boucle->sql_serveur, $type_cast_quote) . '"';
1902 }
1903 // sinon expliciter les
1904 // sql_quote(truc) en sql_quote(truc,'',type)
1905 // sql_quote(truc,serveur) en sql_quote(truc,serveur,type)
1906 // sql_quote(truc,serveur,'') en sql_quote(truc,serveur,type)
1907 // sans toucher aux
1908 // sql_quote(truc,'','varchar(10) DEFAULT \'oui\' COLLATE NOCASE')
1909 // sql_quote(truc,'','varchar')
1910 elseif (preg_match('/\Asql_quote[(](.*?)(,[^)]*?)?(,[^)]*(?:\(\d+\)[^)]*)?)?[)]\s*\z/ms', $val[0], $r)
1911 // si pas deja un type
1912 and (!isset($r[3]) or !$r[3] or !trim($r[3],", '"))
1913 ) {
1914 $r = $r[1]
1915 . ((isset($r[2]) and $r[2]) ? $r[2] : ",''")
1916 . ",'" . addslashes($type_cast_quote) . "'";
1917 $val[0] = "sql_quote($r)";
1918 }
1919 elseif(strpos($val[0], '@@defaultcast@@') !== false
1920 and preg_match("/'@@defaultcast@@'\s*\)\s*\z/ms", $val[0], $r)) {
1921 $val[0] = substr($val[0], 0, -strlen($r[0])) . "'" . addslashes($type_cast_quote) . "')";
1922 }
1923 }
1924
1925 if(strpos($val[0], '@@defaultcast@@') !== false
1926 and preg_match("/'@@defaultcast@@'\s*\)\s*\z/ms", $val[0], $r)) {
1927 $val[0] = substr($val[0], 0, -strlen($r[0])) . "'char')";
1928 }
1929
1930 // Indicateur pour permettre aux fonctionx boucle_X de modifier
1931 // leurs requetes par defaut, notamment le champ statut
1932 // Ne pas confondre champs de la table principale et des jointures
1933 if ($table === $boucle->id_table) {
1934 $boucles[$idb]->modificateur['criteres'][$col_vraie] = true;
1935 if ($col_alias != $col_vraie) {
1936 $boucles[$idb]->modificateur['criteres'][$col_alias] = true;
1937 }
1938 }
1939
1940 // ajout pour le cas special d'une condition sur le champ statut:
1941 // il faut alors interdire a la fonction de boucle
1942 // de mettre ses propres criteres de statut
1943 // http://www.spip.net/@statut (a documenter)
1944 // garde pour compatibilite avec code des plugins anterieurs, mais redondant avec la ligne precedente
1945 if ($col == 'statut') {
1946 $boucles[$idb]->statut = true;
1947 }
1948
1949 // inserer le nom de la table SQL devant le nom du champ
1950 if ($table) {
1951 if ($col[0] == "`") {
1952 $arg = "$table." . substr($col, 1, -1);
1953 } else {
1954 $arg = "$table.$col";
1955 }
1956 } else {
1957 $arg = $col;
1958 }
1959
1960 // inserer la fonction SQL
1961 if ($fct) {
1962 $arg = "$fct($arg$args_sql)";
1963 }
1964
1965 return array($arg, $op, $val, $col_alias, $where_complement);
1966 }
1967
1968
1969 /**
1970 * Décrit un critère non déclaré explicitement, sur un champ externe à la table
1971 *
1972 * Décrit un critère non déclaré comme {id_article} {id_article>3} qui correspond
1973 * à un champ non présent dans la table, et donc à retrouver par jointure si possible.
1974 *
1975 * @param Boucle $boucle Description de la boucle
1976 * @param Critere $crit Paramètres du critère dans cette boucle
1977 * @param string $op L'opérateur utilisé, tel que '='
1978 * @param array $desc Description de la table
1979 * @param string $col Nom de la colonne à trouver (la véritable)
1980 * @param string $col_alias Alias de la colonne éventuel utilisé dans le critère ex: id_enfant
1981 * @param string $table Nom de la table SQL de la boucle
1982 * @return array|string
1983 * Liste si jointure possible :
1984 * - string $col
1985 * - string $col_alias
1986 * - string $table
1987 * - array $where
1988 * - array $desc
1989 *
1990 * Chaîne vide si on ne trouve pas le champ par jointure...
1991 **/
1992 function calculer_critere_infixe_externe($boucle, $crit, $op, $desc, $col, $col_alias, $table) {
1993
1994 $where = '';
1995
1996 $calculer_critere_externe = 'calculer_critere_externe_init';
1997 // gestion par les plugins des jointures tordues
1998 // pas automatiques mais necessaires
1999 $table_sql = table_objet_sql($table);
2000 if (isset($GLOBALS['exceptions_des_jointures'][$table_sql])
2001 and is_array($GLOBALS['exceptions_des_jointures'][$table_sql])
2002 and
2003 (
2004 isset($GLOBALS['exceptions_des_jointures'][$table_sql][$col])
2005 or
2006 isset($GLOBALS['exceptions_des_jointures'][$table_sql][''])
2007 )
2008 ) {
2009 $t = $GLOBALS['exceptions_des_jointures'][$table_sql];
2010 $index = isset($t[$col])
2011 ? $t[$col] : (isset($t['']) ? $t[''] : array());
2012
2013 if (count($index) == 3) {
2014 list($t, $col, $calculer_critere_externe) = $index;
2015 } elseif (count($index) == 2) {
2016 list($t, $col) = $t[$col];
2017 } elseif (count($index) == 1) {
2018 list($calculer_critere_externe) = $index;
2019 $t = $table;
2020 } else {
2021 $t = '';
2022 } // jointure non declaree. La trouver.
2023 } elseif (isset($GLOBALS['exceptions_des_jointures'][$col])) {
2024 list($t, $col) = $GLOBALS['exceptions_des_jointures'][$col];
2025 } else {
2026 $t = '';
2027 } // jointure non declaree. La trouver.
2028
2029 // ici on construit le from pour fournir $col en piochant dans les jointures
2030
2031 // si des jointures explicites sont fournies, on cherche d'abord dans celles ci
2032 // permet de forcer une table de lien quand il y a ambiguite
2033 // <BOUCLE_(DOCUMENTS documents_liens){id_mot}>
2034 // alors que <BOUCLE_(DOCUMENTS){id_mot}> produit la meme chose que <BOUCLE_(DOCUMENTS mots_liens){id_mot}>
2035 $table = "";
2036 if ($boucle->jointures_explicites) {
2037 $jointures_explicites = explode(' ', $boucle->jointures_explicites);
2038 $table = $calculer_critere_externe($boucle, $jointures_explicites, $col, $desc, ($crit->cond or $op != '='), $t);
2039 }
2040
2041 // et sinon on cherche parmi toutes les jointures declarees
2042 if (!$table) {
2043 $table = $calculer_critere_externe($boucle, $boucle->jointures, $col, $desc, ($crit->cond or $op != '='), $t);
2044 }
2045
2046 if (!$table) {
2047 return '';
2048 }
2049
2050 // il ne reste plus qu'a trouver le champ dans les from
2051 list($nom, $desc, $cle) = trouver_champ_exterieur($col, $boucle->from, $boucle);
2052
2053 if (count($cle) > 1 or reset($cle) !== $col) {
2054 $col_alias = $col; // id_article devient juste le nom d'origine
2055 if (count($cle) > 1 and reset($cle) == 'id_objet') {
2056 $e = decompose_champ_id_objet($col);
2057 $col = array_shift($e);
2058 $where = primary_doublee($e, $table);
2059 } else {
2060 $col = reset($cle);
2061 }
2062 }
2063
2064 return array($col, $col_alias, $table, $where, $desc);
2065 }
2066
2067
2068 /**
2069 * Calcule une condition WHERE entre un nom du champ et une valeur
2070 *
2071 * Ne pas appliquer sql_quote lors de la compilation,
2072 * car on ne connait pas le serveur SQL
2073 *
2074 * @todo Ce nom de fonction n'est pas très clair ?
2075 *
2076 * @param array $decompose Liste nom du champ, code PHP pour obtenir la valeur
2077 * @param string $table Nom de la table
2078 * @return string[]
2079 * Liste de 3 éléments pour une description where du compilateur :
2080 * - operateur (=),
2081 * - table.champ,
2082 * - valeur
2083 **/
2084 function primary_doublee($decompose, $table) {
2085 $e1 = reset($decompose);
2086 $e2 = "sql_quote('" . end($decompose) . "')";
2087
2088 return array("'='", "'$table." . $e1 . "'", $e2);
2089 }
2090
2091 /**
2092 * Champ hors table, ça ne peut être qu'une jointure.
2093 *
2094 * On cherche la table du champ et on regarde si elle est déjà jointe
2095 * Si oui et qu'on y cherche un champ nouveau, pas de jointure supplementaire
2096 * Exemple: criteres {titre_mot=...}{type_mot=...}
2097 * Dans les 2 autres cas ==> jointure
2098 * (Exemple: criteres {type_mot=...}{type_mot=...} donne 2 jointures
2099 * pour selectioner ce qui a exactement ces 2 mots-cles.
2100 *
2101 * @param Boucle $boucle
2102 * Description de la boucle
2103 * @param array $joints
2104 * Liste de jointures possibles (ex: $boucle->jointures ou $boucle->jointures_explicites)
2105 * @param string $col
2106 * Colonne cible de la jointure
2107 * @param array $desc
2108 * Description de la table
2109 * @param bool $cond
2110 * Flag pour savoir si le critère est conditionnel ou non
2111 * @param bool|string $checkarrivee
2112 * string : nom de la table jointe où on veut trouver le champ.
2113 * n'a normalement pas d'appel sans $checkarrivee.
2114 * @return string
2115 * Alias de la table de jointure (Lx)
2116 * Vide sinon.
2117 */
2118 function calculer_critere_externe_init(&$boucle, $joints, $col, $desc, $cond, $checkarrivee = false) {
2119 // si on demande un truc du genre spip_mots
2120 // avec aussi spip_mots_liens dans les jointures dispo
2121 // et qu'on est la
2122 // il faut privilegier la jointure directe en 2 etapes spip_mots_liens, spip_mots
2123 if ($checkarrivee
2124 and is_string($checkarrivee)
2125 and $a = table_objet($checkarrivee)
2126 and in_array($a . '_liens', $joints)
2127 ) {
2128 if ($res = calculer_lien_externe_init($boucle, $joints, $col, $desc, $cond, $checkarrivee)) {
2129 return $res;
2130 }
2131 }
2132 foreach ($joints as $joint) {
2133 if ($arrivee = trouver_champ_exterieur($col, array($joint), $boucle, $checkarrivee)) {
2134 // alias de table dans le from
2135 $t = array_search($arrivee[0], $boucle->from);
2136 // recuperer la cle id_xx eventuellement decomposee en (id_objet,objet)
2137 $cols = $arrivee[2];
2138 // mais on ignore la 3eme cle si presente qui correspond alors au point de depart
2139 if (count($cols) > 2) {
2140 array_pop($cols);
2141 }
2142 if ($t) {
2143 // la table est déjà dans le FROM, on vérifie si le champ est utilisé.
2144 $joindre = false;
2145 foreach ($cols as $col) {
2146 $c = '/\b' . $t . ".$col" . '\b/';
2147 if (trouver_champ($c, $boucle->where)) {
2148 $joindre = true;
2149 } else {
2150 // mais ca peut etre dans le FIELD pour le Having
2151 $c = "/FIELD.$t" . ".$col,/";
2152 if (trouver_champ($c, $boucle->select)) {
2153 $joindre = true;
2154 }
2155 }
2156 }
2157 if (!$joindre) {
2158 return $t;
2159 }
2160 }
2161 array_pop($arrivee);
2162 if ($res = calculer_jointure($boucle, array($boucle->id_table, $desc), $arrivee, $cols, $cond, 1)) {
2163 return $res;
2164 }
2165 }
2166 }
2167
2168 return '';
2169
2170 }
2171
2172 /**
2173 * Générer directement une jointure via une table de lien spip_xxx_liens
2174 * pour un critère {id_xxx}
2175 *
2176 * @todo $checkarrivee doit être obligatoire ici ?
2177 *
2178 * @param Boucle $boucle
2179 * Description de la boucle
2180 * @param array $joints
2181 * Liste de jointures possibles (ex: $boucle->jointures ou $boucle->jointures_explicites)
2182 * @param string $col
2183 * Colonne cible de la jointure
2184 * @param array $desc
2185 * Description de la table
2186 * @param bool $cond
2187 * Flag pour savoir si le critère est conditionnel ou non
2188 * @param bool|string $checkarrivee
2189 * string : nom de la table jointe où on veut trouver le champ.
2190 * n'a normalement pas d'appel sans $checkarrivee.
2191 * @return string
2192 * Alias de la table de jointure (Lx)
2193 */
2194 function calculer_lien_externe_init(&$boucle, $joints, $col, $desc, $cond, $checkarrivee = false) {
2195 $primary_arrivee = id_table_objet($checkarrivee);
2196
2197 // [FIXME] $checkarrivee peut-il arriver avec false ????
2198 $intermediaire = trouver_champ_exterieur($primary_arrivee, $joints, $boucle, $checkarrivee . "_liens");
2199 $arrivee = trouver_champ_exterieur($col, $joints, $boucle, $checkarrivee);
2200
2201 if (!$intermediaire or !$arrivee) {
2202 return '';
2203 }
2204 array_pop($intermediaire); // enlever la cle en 3eme argument
2205 array_pop($arrivee); // enlever la cle en 3eme argument
2206
2207 $res = fabrique_jointures($boucle,
2208 array(
2209 array(
2210 $boucle->id_table,
2211 $intermediaire,
2212 array(id_table_objet($desc['table_objet']), 'id_objet', 'objet', $desc['type'])
2213 ),
2214 array(reset($intermediaire), $arrivee, $primary_arrivee)
2215 ), $cond, $desc, $boucle->id_table, array($col));
2216
2217 return $res;
2218 }
2219
2220
2221 /**
2222 * Recherche la présence d'un champ dans une valeur de tableau
2223 *
2224 * @param string $champ
2225 * Expression régulière pour trouver un champ donné.
2226 * Exemple : /\barticles.titre\b/
2227 * @param array $where
2228 * Tableau de valeurs dans lesquels chercher le champ.
2229 * @return bool
2230 * true si le champ est trouvé quelque part dans $where
2231 * false sinon.
2232 **/
2233 function trouver_champ($champ, $where) {
2234 if (!is_array($where)) {
2235 return preg_match($champ, $where);
2236 } else {
2237 foreach ($where as $clause) {
2238 if (trouver_champ($champ, $clause)) {
2239 return true;
2240 }
2241 }
2242
2243 return false;
2244 }
2245 }
2246
2247
2248 /**
2249 * Détermine l'operateur et les opérandes d'un critère non déclaré
2250 *
2251 * Lorsque l'opérateur n'est pas explicite comme sur {id_article>0} c'est
2252 * l'opérateur '=' qui est utilisé.
2253 *
2254 * Traite les cas particuliers id_parent, id_enfant, date, lang
2255 *
2256 * @param string $idb Identifiant de la boucle
2257 * @param array $boucles AST du squelette
2258 * @param Critere $crit Paramètres du critère dans cette boucle
2259 * @return array
2260 * Liste :
2261 * - string $fct Nom d'une fonction SQL sur le champ ou vide (ex: SUM)
2262 * - string $col Nom de la colonne SQL utilisée
2263 * - string $op Opérateur
2264 * - string[] $val
2265 * Liste de codes PHP obtenant les valeurs des comparaisons (ex: id_article sur la boucle parente)
2266 * Souvent un tableau d'un seul élément.
2267 * - string $args_sql Suite des arguments du critère. ?
2268 **/
2269 function calculer_critere_infixe_ops($idb, &$boucles, $crit) {
2270 // cas d'une valeur comparee a elle-meme ou son referent
2271 if (count($crit->param) == 0) {
2272 $op = '=';
2273 $col = $val = $crit->op;
2274 if (preg_match('/^(.*)\.(.*)$/', $col, $r)) {
2275 $val = $r[2];
2276 }
2277 // Cas special {lang} : aller chercher $GLOBALS['spip_lang']
2278 if ($val == 'lang') {
2279 $val = array(kwote('$GLOBALS[\'spip_lang\']'));
2280 } else {
2281 $defaut = null;
2282 if ($val == 'id_parent') {
2283 // Si id_parent, comparer l'id_parent avec l'id_objet
2284 // de la boucle superieure.... faudrait verifier qu'il existe
2285 // pour eviter l'erreur SQL
2286 $val = $boucles[$idb]->primary;
2287 // mais si pas de boucle superieure, prendre id_parent dans l'env
2288 $defaut = "@\$Pile[0]['id_parent']";
2289 } elseif ($val == 'id_enfant') {
2290 // Si id_enfant, comparer l'id_objet avec l'id_parent
2291 // de la boucle superieure
2292 $val = 'id_parent';
2293 } elseif ($crit->cond and ($col == "date" or $col == "date_redac")) {
2294 // un critere conditionnel sur date est traite a part
2295 // car la date est mise d'office par SPIP,
2296 $defaut = "(\$Pile[0]['{$col}_default']?'':\$Pile[0]['" . $col . "'])";
2297 }
2298
2299 $val = calculer_argument_precedent($idb, $val, $boucles, $defaut);
2300 $val = array(kwote($val));
2301 }
2302 } else {
2303 // comparaison explicite
2304 // le phraseur impose que le premier param soit du texte
2305 $params = $crit->param;
2306 $op = $crit->op;
2307 if ($op == '==') {
2308 $op = 'REGEXP';
2309 }
2310 $col = array_shift($params);
2311 $col = $col[0]->texte;
2312
2313 $val = array();
2314 $desc = array('id_mere' => $idb);
2315 $parent = $boucles[$idb]->id_parent;
2316
2317 // Dans le cas {x=='#DATE'} etc, defaire le travail du phraseur,
2318 // celui ne sachant pas ce qu'est un critere infixe
2319 // et a fortiori son 2e operande qu'entoure " ou '
2320 if (count($params) == 1
2321 and count($params[0]) == 3
2322 and $params[0][0]->type == 'texte'
2323 and $params[0][2]->type == 'texte'
2324 and ($p = $params[0][0]->texte) == $params[0][2]->texte
2325 and (($p == "'") or ($p == '"'))
2326 and $params[0][1]->type == 'champ'
2327 ) {
2328 $val[] = "$p\\$p#" . $params[0][1]->nom_champ . "\\$p$p";
2329 } else {
2330 foreach ((($op != 'IN') ? $params : calculer_vieux_in($params)) as $p) {
2331 $a = calculer_liste($p, $desc, $boucles, $parent);
2332 if (strcasecmp($op, 'IN') == 0) {
2333 $val[] = $a;
2334 } else {
2335 $val[] = kwote($a, $boucles[$idb]->sql_serveur, '@@defaultcast@@');
2336 } // toujours quoter en char ici
2337 }
2338 }
2339 }
2340
2341 $fct = $args_sql = '';
2342 // fonction SQL ?
2343 // chercher FONCTION(champ) tel que CONCAT(titre,descriptif)
2344 if (preg_match('/^(.*)' . SQL_ARGS . '$/', $col, $m)) {
2345 $fct = $m[1];
2346 preg_match('/^\(([^,]*)(.*)\)$/', $m[2], $a);
2347 $col = $a[1];
2348 if (preg_match('/^(\S*)(\s+AS\s+.*)$/i', $col, $m)) {
2349 $col = $m[1];
2350 $args_sql = $m[2];
2351 }
2352 $args_sql .= $a[2];
2353 }
2354
2355 return array($fct, $col, $op, $val, $args_sql);
2356 }
2357
2358 // compatibilite ancienne version
2359
2360 // http://code.spip.net/@calculer_vieux_in
2361 function calculer_vieux_in($params) {
2362 $deb = $params[0][0];
2363 $k = count($params) - 1;
2364 $last = $params[$k];
2365 $j = count($last) - 1;
2366 $last = $last[$j];
2367 $n = isset($last->texte) ? strlen($last->texte) : 0;
2368
2369 if (!((isset($deb->texte[0]) and $deb->texte[0] == '(')
2370 && (isset($last->texte[$n - 1]) and $last->texte[$n - 1] == ')'))
2371 ) {
2372 return $params;
2373 }
2374 $params[0][0]->texte = substr($deb->texte, 1);
2375 // attention, on peut avoir k=0,j=0 ==> recalculer
2376 $last = $params[$k][$j];
2377 $n = strlen($last->texte);
2378 $params[$k][$j]->texte = substr($last->texte, 0, $n - 1);
2379 $newp = array();
2380 foreach ($params as $v) {
2381 if ($v[0]->type != 'texte') {
2382 $newp[] = $v;
2383 } else {
2384 foreach (explode(',', $v[0]->texte) as $x) {
2385 $t = new Texte;
2386 $t->texte = $x;
2387 $newp[] = array($t);
2388 }
2389 }
2390 }
2391
2392 return $newp;
2393 }
2394
2395 /**
2396 * Calcule les cas particuliers de critères de date
2397 *
2398 * Lorsque la colonne correspond à un critère de date, tel que
2399 * jour, jour_relatif, jour_x, age, age_relatif, age_x...
2400 *
2401 * @param string $idb Identifiant de la boucle
2402 * @param array $boucles AST du squelette
2403 * @param string $col Nom du champ demandé
2404 * @return string|array
2405 * chaine vide si ne correspond pas à une date,
2406 * sinon liste
2407 * - expression SQL de calcul de la date,
2408 * - nom de la colonne de date (si le calcul n'est pas relatif)
2409 **/
2410 function calculer_critere_infixe_date($idb, &$boucles, $col) {
2411 if (!preg_match(",^((age|jour|mois|annee)_relatif|date|mois|annee|jour|heure|age)(_[a-z_]+)?$,", $col, $regs)) {
2412 return '';
2413 }
2414
2415 $boucle = $boucles[$idb];
2416 $table = $boucle->show;
2417
2418 // si c'est une colonne de la table, ne rien faire
2419 if (isset($table['field'][$col])) {
2420 return '';
2421 }
2422
2423 if (!$table['date'] && !isset($GLOBALS['table_date'][$table['id_table']])) {
2424 return '';
2425 }
2426 $pred = $date_orig = isset($GLOBALS['table_date'][$table['id_table']]) ? $GLOBALS['table_date'][$table['id_table']] : $table['date'];
2427
2428 $col = $regs[1];
2429 if (isset($regs[3]) and $suite = $regs[3]) {
2430 # Recherche de l'existence du champ date_xxxx,
2431 # si oui choisir ce champ, sinon choisir xxxx
2432
2433 if (isset($table['field']["date$suite"])) {
2434 $date_orig = 'date' . $suite;
2435 } else {
2436 $date_orig = substr($suite, 1);
2437 }
2438 $pred = $date_orig;
2439 } else {
2440 if (isset($regs[2]) and $rel = $regs[2]) {
2441 $pred = 'date';
2442 }
2443 }
2444
2445 $date_compare = "\"' . normaliser_date(" .
2446 calculer_argument_precedent($idb, $pred, $boucles) .
2447 ") . '\"";
2448
2449 $col_vraie = $date_orig;
2450 $date_orig = $boucle->id_table . '.' . $date_orig;
2451
2452 switch ($col) {
2453 case 'date':
2454 $col = $date_orig;
2455 break;
2456 case 'jour':
2457 $col = "DAYOFMONTH($date_orig)";
2458 break;
2459 case 'mois':
2460 $col = "MONTH($date_orig)";
2461 break;
2462 case 'annee':
2463 $col = "YEAR($date_orig)";
2464 break;
2465 case 'heure':
2466 $col = "DATE_FORMAT($date_orig, \\'%H:%i\\')";
2467 break;
2468 case 'age':
2469 $col = calculer_param_date("NOW()", $date_orig);
2470 $col_vraie = "";// comparer a un int (par defaut)
2471 break;
2472 case 'age_relatif':
2473 $col = calculer_param_date($date_compare, $date_orig);
2474 $col_vraie = "";// comparer a un int (par defaut)
2475 break;
2476 case 'jour_relatif':
2477 $col = "(TO_DAYS(" . $date_compare . ")-TO_DAYS(" . $date_orig . "))";
2478 $col_vraie = "";// comparer a un int (par defaut)
2479 break;
2480 case 'mois_relatif':
2481 $col = "MONTH(" . $date_compare . ")-MONTH(" .
2482 $date_orig . ")+12*(YEAR(" . $date_compare .
2483 ")-YEAR(" . $date_orig . "))";
2484 $col_vraie = "";// comparer a un int (par defaut)
2485 break;
2486 case 'annee_relatif':
2487 $col = "YEAR(" . $date_compare . ")-YEAR(" .
2488 $date_orig . ")";
2489 $col_vraie = "";// comparer a un int (par defaut)
2490 break;
2491 }
2492
2493 return array($col, $col_vraie);
2494 }
2495
2496 /**
2497 * Calcule l'expression SQL permettant de trouver un nombre de jours écoulés.
2498 *
2499 * Le calcul SQL retournera un nombre de jours écoulés entre la date comparée
2500 * et la colonne SQL indiquée
2501 *
2502 * @param string $date_compare
2503 * Code PHP permettant d'obtenir le timestamp référent.
2504 * C'est à partir de lui que l'on compte les jours
2505 * @param string $date_orig
2506 * Nom de la colonne SQL qui possède la date
2507 * @return string
2508 * Expression SQL calculant le nombre de jours écoulé entre une valeur
2509 * de colonne SQL et une date.
2510 **/
2511 function calculer_param_date($date_compare, $date_orig) {
2512 if (preg_match(",'\" *\.(.*)\. *\"',", $date_compare, $r)) {
2513 $init = "'\" . (\$x = $r[1]) . \"'";
2514 $date_compare = '\'$x\'';
2515 } else {
2516 $init = $date_compare;
2517 }
2518
2519 return
2520 // optimisation : mais prevoir le support SQLite avant
2521 "TIMESTAMPDIFF(HOUR,$date_orig,$init)/24";
2522 }
2523
2524 /**
2525 * Compile le critère {source} d'une boucle DATA
2526 *
2527 * Permet de déclarer le mode d'obtention des données dans une boucle
2528 * DATA (premier argument) et les données (la suite).
2529 *
2530 * @example
2531 * (DATA){source mode, "xxxxxx", arg, arg, arg}
2532 * (DATA){source tableau, #LISTE{un,deux,trois}}
2533 *
2534 * @param string $idb Identifiant de la boucle
2535 * @param array $boucles AST du squelette
2536 * @param Critere $crit Paramètres du critère dans cette boucle
2537 */
2538 function critere_DATA_source_dist($idb, &$boucles, $crit) {
2539 $boucle = &$boucles[$idb];
2540
2541 $args = array();
2542 foreach ($crit->param as &$param) {
2543 array_push($args,
2544 calculer_liste($param, array(), $boucles, $boucles[$idb]->id_parent));
2545 }
2546
2547 $boucle->hash .= '
2548 $command[\'sourcemode\'] = ' . array_shift($args) . ";\n";
2549
2550 $boucle->hash .= '
2551 $command[\'source\'] = array(' . join(', ', $args) . ");\n";
2552 }
2553
2554
2555 /**
2556 * Compile le critère {datasource} d'une boucle DATA
2557 *
2558 * Permet de déclarer le mode d'obtention des données dans une boucle DATA
2559 *
2560 * @deprecated Utiliser directement le critère {source}
2561 *
2562 * @param string $idb Identifiant de la boucle
2563 * @param array $boucles AST du squelette
2564 * @param Critere $crit Paramètres du critère dans cette boucle
2565 */
2566 function critere_DATA_datasource_dist($idb, &$boucles, $crit) {
2567 $boucle = &$boucles[$idb];
2568 $boucle->hash .= '
2569 $command[\'source\'] = array(' . calculer_liste($crit->param[0], array(), $boucles, $boucles[$idb]->id_parent) . ');
2570 $command[\'sourcemode\'] = ' . calculer_liste($crit->param[1], array(), $boucles, $boucles[$idb]->id_parent) . ';';
2571 }
2572
2573
2574 /**
2575 * Compile le critère {datacache} d'une boucle DATA
2576 *
2577 * Permet de transmettre une durée de cache (time to live) utilisée
2578 * pour certaines sources d'obtention des données (par exemple RSS),
2579 * indiquant alors au bout de combien de temps la donnée est à réobtenir.
2580 *
2581 * La durée par défaut est 1 journée.
2582 *
2583 * @param string $idb Identifiant de la boucle
2584 * @param array $boucles AST du squelette
2585 * @param Critere $crit Paramètres du critère dans cette boucle
2586 */
2587 function critere_DATA_datacache_dist($idb, &$boucles, $crit) {
2588 $boucle = &$boucles[$idb];
2589 $boucle->hash .= '
2590 $command[\'datacache\'] = ' . calculer_liste($crit->param[0], array(), $boucles, $boucles[$idb]->id_parent) . ';';
2591 }
2592
2593
2594 /**
2595 * Compile le critère {args} d'une boucle PHP
2596 *
2597 * Permet de passer des arguments à un iterateur non-spip
2598 * (PHP:xxxIterator){args argument1, argument2, argument3}
2599 *
2600 * @param string $idb Identifiant de la boucle
2601 * @param array $boucles AST du squelette
2602 * @param Critere $crit Paramètres du critère dans cette boucle
2603 */
2604 function critere_php_args_dist($idb, &$boucles, $crit) {
2605 $boucle = &$boucles[$idb];
2606 $boucle->hash .= '$command[\'args\']=array();';
2607 foreach ($crit->param as $param) {
2608 $boucle->hash .= '
2609 $command[\'args\'][] = ' . calculer_liste($param, array(), $boucles, $boucles[$idb]->id_parent) . ';';
2610 }
2611 }
2612
2613 /**
2614 * Compile le critère {liste} d'une boucle DATA
2615 *
2616 * Passe une liste de données à l'itérateur DATA
2617 *
2618 * @example
2619 * (DATA){liste X1, X2, X3}
2620 * équivalent à (DATA){source tableau,#LISTE{X1, X2, X3}}
2621 *
2622 * @param string $idb Identifiant de la boucle
2623 * @param array $boucles AST du squelette
2624 * @param Critere $crit Paramètres du critère dans cette boucle
2625 */
2626 function critere_DATA_liste_dist($idb, &$boucles, $crit) {
2627 $boucle = &$boucles[$idb];
2628 $boucle->hash .= "\n\t" . '$command[\'liste\'] = array();' . "\n";
2629 foreach ($crit->param as $param) {
2630 $boucle->hash .= "\t" . '$command[\'liste\'][] = ' . calculer_liste($param, array(), $boucles,
2631 $boucles[$idb]->id_parent) . ";\n";
2632 }
2633 }
2634
2635 /**
2636 * Compile le critère {enum} d'une boucle DATA
2637 *
2638 * Passe les valeurs de début et de fin d'une énumération, qui seront
2639 * vues comme une liste d'autant d'éléments à parcourir pour aller du
2640 * début à la fin.
2641 *
2642 * Cela utilisera la fonction range() de PHP.
2643 *
2644 * @example
2645 * (DATA){enum Xdebut, Xfin}
2646 * (DATA){enum a,z}
2647 * (DATA){enum z,a}
2648 * (DATA){enum 1.0,9.2}
2649 *
2650 * @link http://php.net/manual/fr/function.range.php
2651 *
2652 * @param string $idb Identifiant de la boucle
2653 * @param array $boucles AST du squelette
2654 * @param Critere $crit Paramètres du critère dans cette boucle
2655 */
2656 function critere_DATA_enum_dist($idb, &$boucles, $crit) {
2657 $boucle = &$boucles[$idb];
2658 $boucle->hash .= "\n\t" . '$command[\'enum\'] = array();' . "\n";
2659 foreach ($crit->param as $param) {
2660 $boucle->hash .= "\t" . '$command[\'enum\'][] = ' . calculer_liste($param, array(), $boucles,
2661 $boucles[$idb]->id_parent) . ";\n";
2662 }
2663 }
2664
2665 /**
2666 * Compile le critère {datapath} d'une boucle DATA
2667 *
2668 * Extrait un chemin d'un tableau de données
2669 *
2670 * (DATA){datapath query.results}
2671 *
2672 * @param string $idb Identifiant de la boucle
2673 * @param array $boucles AST du squelette
2674 * @param Critere $crit Paramètres du critère dans cette boucle
2675 */
2676 function critere_DATA_datapath_dist($idb, &$boucles, $crit) {
2677 $boucle = &$boucles[$idb];
2678 foreach ($crit->param as $param) {
2679 $boucle->hash .= '
2680 $command[\'datapath\'][] = ' . calculer_liste($param, array(), $boucles, $boucles[$idb]->id_parent) . ';';
2681 }
2682 }
2683
2684
2685 /**
2686 * Compile le critère {si}
2687 *
2688 * Le critère {si condition} est applicable à toutes les boucles et conditionne
2689 * l'exécution de la boucle au résultat de la condition. La partie alternative
2690 * de la boucle est alors affichée si une condition n'est pas remplie (comme
2691 * lorsque la boucle ne ramène pas de résultat).
2692 * La différence étant que si la boucle devait réaliser une requête SQL
2693 * (par exemple une boucle ARTICLES), celle ci n'est pas réalisée si la
2694 * condition n'est pas remplie.
2695 *
2696 * Les valeurs de la condition sont forcément extérieures à cette boucle
2697 * (sinon il faudrait l'exécuter pour connaître le résultat, qui doit tester
2698 * si on exécute la boucle !)
2699 *
2700 * Si plusieurs critères {si} sont présents, ils sont cumulés :
2701 * si une seule des conditions n'est pas vérifiée, la boucle n'est pas exécutée.
2702 *
2703 * @example
2704 * {si #ENV{exec}|=={article}}
2705 * {si (#_contenu:GRAND_TOTAL|>{10})}
2706 * {si #AUTORISER{voir,articles}}
2707 *
2708 * @param string $idb Identifiant de la boucle
2709 * @param array $boucles AST du squelette
2710 * @param Critere $crit Paramètres du critère dans cette boucle
2711 */
2712 function critere_si_dist($idb, &$boucles, $crit) {
2713 $boucle = &$boucles[$idb];
2714 // il faut initialiser 1 fois le tableau a chaque appel de la boucle
2715 // (par exemple lorsque notre boucle est appelee dans une autre boucle)
2716 // mais ne pas l'initialiser n fois si il y a n criteres {si } dans la boucle !
2717 $boucle->hash .= "\n\tif (!isset(\$si_init)) { \$command['si'] = array(); \$si_init = true; }\n";
2718 if ($crit->param) {
2719 foreach ($crit->param as $param) {
2720 $boucle->hash .= "\t\$command['si'][] = "
2721 . calculer_liste($param, array(), $boucles, $boucles[$idb]->id_parent) . ";\n";
2722 }
2723 // interdire {si 0} aussi !
2724 } else {
2725 $boucle->hash .= '$command[\'si\'][] = 0;';
2726 }
2727 }
2728
2729 /**
2730 * Compile le critère {tableau} d'une boucle POUR
2731 *
2732 * {tableau #XX} pour compatibilite ascendante boucle POUR
2733 * ... préférer la notation (DATA){source tableau,#XX}
2734 *
2735 * @deprecated Utiliser une boucle (DATA){source tableau,#XX}
2736 *
2737 * @param string $idb Identifiant de la boucle
2738 * @param array $boucles AST du squelette
2739 * @param Critere $crit Paramètres du critère dans cette boucle
2740 */
2741 function critere_POUR_tableau_dist($idb, &$boucles, $crit) {
2742 $boucle = &$boucles[$idb];
2743 $boucle->hash .= '
2744 $command[\'source\'] = array(' . calculer_liste($crit->param[0], array(), $boucles, $boucles[$idb]->id_parent) . ');
2745 $command[\'sourcemode\'] = \'table\';';
2746 }
2747
2748
2749 /**
2750 * Compile le critère {noeud}
2751 *
2752 * Trouver tous les objets qui ont des enfants (les noeuds de l'arbre)
2753 * {noeud}
2754 * {!noeud} retourne les feuilles
2755 *
2756 * @global array $exceptions_des_tables
2757 *
2758 * @param string $idb Identifiant de la boucle
2759 * @param array $boucles AST du squelette
2760 * @param Critere $crit Paramètres du critère dans cette boucle
2761 */
2762 function critere_noeud_dist($idb, &$boucles, $crit) {
2763
2764 $not = $crit->not;
2765 $boucle = &$boucles[$idb];
2766 $primary = $boucle->primary;
2767
2768 if (!$primary or strpos($primary, ',')) {
2769 erreur_squelette(_T('zbug_doublon_sur_table_sans_cle_primaire'), $boucle);
2770
2771 return;
2772 }
2773 $table = $boucle->type_requete;
2774 $table_sql = table_objet_sql(objet_type($table));
2775
2776 $id_parent = isset($GLOBALS['exceptions_des_tables'][$boucle->id_table]['id_parent']) ?
2777 $GLOBALS['exceptions_des_tables'][$boucle->id_table]['id_parent'] :
2778 'id_parent';
2779
2780 $in = "IN";
2781 $where = array("'IN'", "'$boucle->id_table." . "$primary'", "'('.sql_get_select('$id_parent', '$table_sql').')'");
2782 if ($not) {
2783 $where = array("'NOT'", $where);
2784 }
2785
2786 $boucle->where[] = $where;
2787 }
2788
2789 /**
2790 * Compile le critère {feuille}
2791 *
2792 * Trouver tous les objets qui n'ont pas d'enfants (les feuilles de l'arbre)
2793 * {feuille}
2794 * {!feuille} retourne les noeuds
2795 *
2796 * @global array $exceptions_des_tables
2797 * @param string $idb Identifiant de la boucle
2798 * @param array $boucles AST du squelette
2799 * @param Critere $crit Paramètres du critère dans cette boucle
2800 */
2801 function critere_feuille_dist($idb, &$boucles, $crit) {
2802 $not = $crit->not;
2803 $crit->not = $not ? false : true;
2804 critere_noeud_dist($idb, $boucles, $crit);
2805 $crit->not = $not;
2806 }