[SPIP] ~maj v3.0.14-->v3.0.17
[ptitvelo/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-2014 *
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\Compilateur\Criteres
17 **/
18
19 if (!defined('_ECRIRE_INC_VERSION')) return;
20
21 /**
22 * Une Regexp repérant une chaine produite par le compilateur,
23 * souvent utilisée pour faire de la concaténation lors de la compilation
24 * plutôt qu'à l'exécution, i.e. pour remplacer 'x'.'y' par 'xy'
25 **/
26 define('_CODE_QUOTE', ",^(\n//[^\n]*\n)? *'(.*)' *$,");
27
28
29
30 /**
31 * Compile le critère {racine}
32 *
33 * Ce critère sélectionne les éléments à la racine d'une hiérarchie,
34 * c'est à dire ayant id_parent=0
35 *
36 * @link http://www.spip.net/@racine
37 *
38 * @param string $idb
39 * Identifiant de la boucle
40 * @param array $boucles
41 * AST du squelette
42 * @param array $crit
43 * Paramètres du critère dans cette boucle
44 * @return
45 * AST complété de la gestion du critère
46 **/
47 function critere_racine_dist($idb, &$boucles, $crit){
48 global $exceptions_des_tables;
49 $not = $crit->not;
50 $boucle = &$boucles[$idb];
51 $id_parent = isset($exceptions_des_tables[$boucle->id_table]['id_parent']) ?
52 $exceptions_des_tables[$boucle->id_table]['id_parent'] :
53 'id_parent';
54
55 $c = array("'='", "'$boucle->id_table."."$id_parent'", 0);
56 $boucle->where[] = ($crit->not ? array("'NOT'", $c) : $c);
57 }
58
59
60 /**
61 * Compile le critère {exclus}
62 *
63 * Exclut du résultat l’élément dans lequel on se trouve déjà
64 *
65 * @link http://www.spip.net/@exclus
66 *
67 * @param string $idb
68 * Identifiant de la boucle
69 * @param array $boucles
70 * AST du squelette
71 * @param array $crit
72 * Paramètres du critère dans cette boucle
73 * @return
74 * AST complété de la gestion du critère
75 **/
76 function critere_exclus_dist($idb, &$boucles, $crit){
77 $not = $crit->not;
78 $boucle = &$boucles[$idb];
79 $id = $boucle->primary;
80
81 if ($not OR !$id)
82 return (array('zbug_critere_inconnu', array('critere' => $not.$crit->op)));
83 $arg = kwote(calculer_argument_precedent($idb, $id, $boucles));
84 $boucle->where[] = array("'!='", "'$boucle->id_table."."$id'", $arg);
85 }
86
87
88 /**
89 * Compile le critère {doublons} ou {unique}
90 *
91 * Ce critères enlève de la boucle les éléments déjà sauvegardés
92 * dans un précédent critère {doublon} sur une boucle de même table.
93 *
94 * Il est possible de spécifier un nom au doublon tel que {doublons sommaire}
95 *
96 * @link http://www.spip.net/@doublons
97 *
98 * @param string $idb
99 * Identifiant de la boucle
100 * @param array $boucles
101 * AST du squelette
102 * @param array $crit
103 * Paramètres du critère dans cette boucle
104 * @return
105 * AST complété de la gestion du critère
106 **/
107 function critere_doublons_dist($idb, &$boucles, $crit){
108 $boucle = &$boucles[$idb];
109 $primary = $boucle->primary;
110
111 // la table nécessite une clé primaire, non composée
112 if (!$primary OR strpos($primary, ',')){
113 return (array('zbug_doublon_sur_table_sans_cle_primaire'));
114 }
115
116 $not = ($crit->not ? '' : 'NOT');
117
118 // le doublon s'applique sur un type de boucle (article)
119 $nom = "'" . $boucle->type_requete. "'";
120
121 // compléter le nom avec un nom précisé {doublons nom}
122 // on obtient $nom = "'article' . 'nom'"
123 if (isset($crit->param[0])) {
124 $nom .= "." . calculer_liste($crit->param[0], array(), $boucles, $boucles[$idb]->id_parent);
125 }
126
127 // code qui déclarera l'index du stockage de nos doublons (pour éviter une notice PHP)
128 $init_comment = "\n\n\t// Initialise le(s) critère(s) doublons\n";
129 $init_code = "\tif (!isset(\$doublons[\$d = $nom])) { \$doublons[\$d] = ''; }\n";
130
131 // on crée un sql_in avec la clé primaire de la table
132 // et la collection des doublons déjà emmagasinés dans le tableau
133 // $doublons et son index, ici $nom
134
135 // debut du code "sql_in('articles.id_article', "
136 $debut_in = "sql_in('".$boucle->id_table.'.'.$primary."', ";
137 // lecture des données du doublon "$doublons[$doublon_index[] = "
138 // Attention : boucle->doublons désigne une variable qu'on affecte
139 $debut_doub = '$doublons[' . (!$not ? '' : ($boucle->doublons."[]= "));
140
141 // le debut complet du code des doublons
142 $debut_doub = $debut_in . $debut_doub;
143
144 // nom du doublon "('article' . 'nom')]"
145 $fin_doub = "($nom)]";
146
147 // si on trouve un autre critère doublon,
148 // on fusionne pour avoir un seul IN, et on s'en va !
149 foreach ($boucle->where as $k => $w) {
150 if (strpos($w[0], $debut_doub)===0) {
151 // fusionner le sql_in (du where)
152 $boucle->where[$k][0] = $debut_doub . $fin_doub.' . '.substr($w[0], strlen($debut_in));
153 // fusionner l'initialisation (du hash) pour faire plus joli
154 $x = strpos($boucle->hash, $init_comment);
155 $len = strlen($init_comment);
156 $boucle->hash =
157 substr($boucle->hash, 0, $x + $len) . $init_code . substr($boucle->hash, $x + $len);
158 return;
159 }
160 }
161
162 // mettre l'ensemble dans un tableau pour que ce ne soit pas vu comme une constante
163 $boucle->where[] = array($debut_doub . $fin_doub.", '".$not."')");
164
165 // déclarer le doublon s'il n'existe pas encore
166 $boucle->hash .= $init_comment . $init_code;
167
168
169 # la ligne suivante avait l'intention d'eviter une collecte deja faite
170 # mais elle fait planter une boucle a 2 critere doublons:
171 # {!doublons A}{doublons B}
172 # (de http://article.gmane.org/gmane.comp.web.spip.devel/31034)
173 # if ($crit->not) $boucle->doublons = "";
174 }
175
176
177 /**
178 * Compile le critère {lang_select}
179 *
180 * Permet de restreindre ou non une boucle en affichant uniquement
181 * les éléments dans la langue en cours. Certaines boucles
182 * tel que articles et rubriques restreignent par défaut sur la langue
183 * en cours.
184 *
185 * Sans définir de valeur au critère, celui-ci utilise 'oui' comme
186 * valeur par défaut.
187 *
188 * @param string $idb
189 * Identifiant de la boucle
190 * @param array $boucles
191 * AST du squelette
192 * @param array $crit
193 * Paramètres du critère dans cette boucle
194 * @return
195 * AST complété de la gestion du critère
196 **/
197 function critere_lang_select_dist($idb, &$boucles, $crit){
198 if (!isset($crit->param[1][0]) OR !($param = $crit->param[1][0]->texte)) $param = 'oui';
199 if ($crit->not) $param = ($param=='oui') ? 'non' : 'oui';
200 $boucle = &$boucles[$idb];
201 $boucle->lang_select = $param;
202 }
203
204 // {debut_xxx}
205 // http://www.spip.net/@debut_
206 // http://doc.spip.org/@critere_debut_dist
207 function critere_debut_dist($idb, &$boucles, $crit){
208 list($un, $deux) = $crit->param;
209 $un = $un[0]->texte;
210 $deux = $deux[0]->texte;
211 if ($deux){
212 $boucles[$idb]->limit = 'intval($Pile[0]["debut'.
213 $un.
214 '"]) . ",'.
215 $deux.
216 '"';
217 } else calculer_critere_DEFAUT_dist($idb, $boucles, $crit);
218 }
219
220 // {pagination}
221 // {pagination 20}
222 // {pagination #ENV{pages,5}} etc
223 // {pagination 20 #ENV{truc,chose}} pour utiliser la variable debut_#ENV{truc,chose}
224 // http://www.spip.net/@pagination
225 // http://doc.spip.org/@critere_pagination_dist
226 function critere_pagination_dist($idb, &$boucles, $crit){
227
228 $boucle = &$boucles[$idb];
229 // definition de la taille de la page
230 $pas = !isset($crit->param[0][0]) ? "''"
231 : calculer_liste(array($crit->param[0][0]), array(), $boucles, $boucle->id_parent);
232
233 if (!preg_match(_CODE_QUOTE, $pas, $r)){
234 $pas = "((\$a = intval($pas)) ? \$a : 10)";
235 } else {
236 $r = intval($r[2]);
237 $pas = strval($r ? $r : 10);
238 }
239 $type = !isset($crit->param[0][1]) ? "'$idb'"
240 : calculer_liste(array($crit->param[0][1]), array(), $boucles, $boucle->id_parent);
241 $debut = ($type[0]!=="'") ? "'debut'.$type"
242 : ("'debut".substr($type, 1));
243
244 $boucle->modificateur['debut_nom'] = $type;
245 $partie =
246 // tester si le numero de page demande est de la forme '@yyy'
247 'isset($Pile[0]['.$debut.']) ? $Pile[0]['.$debut.'] : _request('.$debut.");\n"
248 ."\tif(substr(\$debut_boucle,0,1)=='@'){\n"
249 ."\t\t".'$debut_boucle = $Pile[0]['.$debut.'] = quete_debut_pagination(\''.$boucle->primary.'\',$Pile[0][\'@'.$boucle->primary.'\'] = substr($debut_boucle,1),'.$pas.',$iter);'."\n"
250 ."\t\t".'$iter->seek(0);'."\n"
251 ."\t}\n"
252 ."\t".'$debut_boucle = intval($debut_boucle)';
253
254 $boucle->hash .= '
255 $command[\'pagination\'] = array((isset($Pile[0]['.$debut.']) ? $Pile[0]['.$debut.'] : null), ' . $pas . ');';
256
257 $boucle->total_parties = $pas;
258 calculer_parties($boucles, $idb, $partie, 'p+');
259 // ajouter la cle primaire dans le select pour pouvoir gerer la pagination referencee par @id
260 // sauf si pas de primaire, ou si primaire composee
261 // dans ce cas, on ne sait pas gerer une pagination indirecte
262 $t = $boucle->id_table.'.'.$boucle->primary;
263 if ($boucle->primary
264 AND !preg_match('/[,\s]/', $boucle->primary)
265 AND !in_array($t, $boucle->select)
266 )
267 $boucle->select[] = $t;
268 }
269
270
271 // {recherche} ou {recherche susan}
272 // http://www.spip.net/@recherche
273 // http://doc.spip.org/@critere_recherche_dist
274 function critere_recherche_dist($idb, &$boucles, $crit){
275
276 $boucle = &$boucles[$idb];
277
278 if (!$boucle->primary OR strpos($boucle->primary, ',')){
279 erreur_squelette(_T('zbug_critere_sur_table_sans_cle_primaire',array('critere'=>'recherche')), $boucle);
280 return;
281 }
282
283 if (isset($crit->param[0]))
284 $quoi = calculer_liste($crit->param[0], array(), $boucles, $boucles[$idb]->id_parent);
285 else
286 $quoi = '(isset($Pile[0]["recherche"])?$Pile[0]["recherche"]:(isset($GLOBALS["recherche"])?$GLOBALS["recherche"]:""))';
287
288 $_modificateur = var_export($boucle->modificateur, true);
289 $boucle->hash .= '
290 // RECHERCHE'
291 .($crit->cond ? '
292 if (!strlen('.$quoi.')){
293 list($rech_select, $rech_where) = array("0 as points","");
294 } else' : '').'
295 {
296 $prepare_recherche = charger_fonction(\'prepare_recherche\', \'inc\');
297 list($rech_select, $rech_where) = $prepare_recherche('.$quoi.', "'.$boucle->id_table.'", "'.$crit->cond.'","'.$boucle->sql_serveur.'",'.$_modificateur.',"'.$boucle->primary.'");
298 }
299 ';
300
301
302 $t = $boucle->id_table.'.'.$boucle->primary;
303 if (!in_array($t, $boucles[$idb]->select))
304 $boucle->select[] = $t; # pour postgres, neuneu ici
305 // jointure uniquement sur le serveur principal
306 // (on ne peut joindre une table d'un serveur distant avec la table des resultats du serveur principal)
307 if (!$boucle->sql_serveur){
308 $boucle->join['resultats'] = array("'".$boucle->id_table."'", "'id'", "'".$boucle->primary."'");
309 $boucle->from['resultats'] = 'spip_resultats';
310 }
311 $boucle->select[] = '$rech_select';
312 //$boucle->where[]= "\$rech_where?'resultats.id=".$boucle->id_table.".".$boucle->primary."':''";
313
314 // et la recherche trouve
315 $boucle->where[] = '$rech_where?$rech_where:\'\'';
316 }
317
318 // {traduction}
319 // http://www.spip.net/@traduction
320 // (id_trad>0 AND id_trad=id_trad(precedent))
321 // OR id_article=id_article(precedent)
322 // http://doc.spip.org/@critere_traduction_dist
323 function critere_traduction_dist($idb, &$boucles, $crit){
324 $boucle = &$boucles[$idb];
325 $prim = $boucle->primary;
326 $table = $boucle->id_table;
327 $arg = kwote(calculer_argument_precedent($idb, 'id_trad', $boucles));
328 $dprim = kwote(calculer_argument_precedent($idb, $prim, $boucles));
329 $boucle->where[] =
330 array("'OR'",
331 array("'AND'",
332 array("'='", "'$table.id_trad'", 0),
333 array("'='", "'$table.$prim'", $dprim)
334 ),
335 array("'AND'",
336 array("'>'", "'$table.id_trad'", 0),
337 array("'='", "'$table.id_trad'", $arg)
338 )
339 );
340 }
341
342 // {origine_traduction}
343 // (id_trad>0 AND id_article=id_trad) OR (id_trad=0)
344 // http://www.spip.net/@origine_traduction
345 // http://doc.spip.org/@critere_origine_traduction_dist
346 function critere_origine_traduction_dist($idb, &$boucles, $crit){
347 $boucle = &$boucles[$idb];
348 $prim = $boucle->primary;
349 $table = $boucle->id_table;
350
351 $c =
352 array("'OR'",
353 array("'='", "'$table."."id_trad'", "'$table.$prim'"),
354 array("'='", "'$table.id_trad'", "'0'")
355 );
356 $boucle->where[] = ($crit->not ? array("'NOT'", $c) : $c);
357 }
358
359 // {meme_parent}
360 // http://www.spip.net/@meme_parent
361 // http://doc.spip.org/@critere_meme_parent_dist
362 function critere_meme_parent_dist($idb, &$boucles, $crit){
363 global $exceptions_des_tables;
364 $boucle = &$boucles[$idb];
365 $arg = kwote(calculer_argument_precedent($idb, 'id_parent', $boucles));
366 $id_parent = isset($exceptions_des_tables[$boucle->id_table]['id_parent']) ?
367 $exceptions_des_tables[$boucle->id_table]['id_parent'] :
368 'id_parent';
369 $mparent = $boucle->id_table.'.'.$id_parent;
370
371 if ($boucle->type_requete=='rubriques' OR isset($exceptions_des_tables[$boucle->id_table]['id_parent'])){
372 $boucle->where[] = array("'='", "'$mparent'", $arg);
373
374 }
375 // le cas FORUMS est gere dans le plugin forum, dans la fonction critere_FORUMS_meme_parent_dist()
376 else {
377 return (array('zbug_critere_inconnu', array('critere' => $crit->op.' '.$boucle->type_requete)));
378 }
379 }
380
381
382 /**
383 * Sélectionne dans une boucle les éléments appartenant à une branche d'une rubrique
384 *
385 * Calcule une branche d'une rubrique et conditionne la boucle avec.
386 * Cherche l'identifiant de la rubrique dans les boucles parentes ou par jointure
387 * et calcule la liste des identifiants de rubrique de toute la branche
388 *
389 * @link http://www.spip.net/@branche
390 *
391 * @param string $idb
392 * Identifiant de la boucle
393 * @param array $boucles
394 * AST du squelette
395 * @param array $crit
396 * Paramètres du critère dans cette boucle
397 * @return
398 * AST complété de la condition where au niveau de la boucle,
399 * restreignant celle ci aux rubriques de la branche
400 **/
401 function critere_branche_dist($idb, &$boucles, $crit){
402
403 $not = $crit->not;
404 $boucle = &$boucles[$idb];
405 $arg = calculer_argument_precedent($idb, 'id_rubrique', $boucles);
406
407 //Trouver une jointure
408 $champ = "id_rubrique";
409 $desc = $boucle->show;
410 //Seulement si necessaire
411 if (!array_key_exists($champ, $desc['field'])){
412 $cle = trouver_jointure_champ($champ, $boucle);
413 $trouver_table = charger_fonction("trouver_table", "base");
414 $desc = $trouver_table($boucle->from[$cle]);
415 if (count(trouver_champs_decomposes($champ, $desc))>1){
416 $decompose = decompose_champ_id_objet($champ);
417 $champ = array_shift($decompose);
418 $boucle->where[] = array("'='", _q($cle.".".reset($decompose)), '"'.sql_quote(end($decompose)).'"');
419 }
420 }
421 else $cle = $boucle->id_table;
422
423 $c = "sql_in('$cle".".$champ', calcul_branche_in($arg)"
424 .($not ? ", 'NOT'" : '').")";
425 $boucle->where[] = !$crit->cond ? $c :
426 ("($arg ? $c : ".($not ? "'0=1'" : "'1=1'").')');
427 }
428
429 // {logo} liste les objets qui ont un logo
430 // http://doc.spip.org/@critere_logo_dist
431 function critere_logo_dist($idb, &$boucles, $crit){
432
433 $not = $crit->not;
434 $boucle = &$boucles[$idb];
435
436 $c = "sql_in('".
437 $boucle->id_table.'.'.$boucle->primary
438 ."', lister_objets_avec_logos('".$boucle->primary."'), '')";
439
440 if ($crit->cond) $c = "($arg ? $c : 1)";
441
442 if ($not)
443 $boucle->where[] = array("'NOT'", $c);
444 else
445 $boucle->where[] = $c;
446 }
447
448 // c'est la commande SQL "GROUP BY"
449 // par exemple <boucle(articles){fusion lang}>
450 // http://doc.spip.org/@critere_fusion_dist
451 function critere_fusion_dist($idb, &$boucles, $crit){
452 if ($t = isset($crit->param[0])){
453 $t = $crit->param[0];
454 if ($t[0]->type=='texte'){
455 $t = $t[0]->texte;
456 if (preg_match("/^(.*)\.(.*)$/", $t, $r)){
457 $t = table_objet_sql($r[1]);
458 $t = array_search($t, $boucles[$idb]->from);
459 if ($t) $t .= '.'.$r[2];
460 }
461 } else {
462 $t = '".'
463 .calculer_critere_arg_dynamique($idb, $boucles, $t)
464 .'."';
465 }
466 }
467 if ($t){
468 $boucles[$idb]->group[] = $t;
469 if (!in_array($t, $boucles[$idb]->select))
470 $boucles[$idb]->select[] = $t;
471 } else
472 return (array('zbug_critere_inconnu', array('critere' => $crit->op.' ?')));
473 }
474
475 // c'est la commande SQL "COLLATE"
476 // qui peut etre appliquee sur les order by, group by, where like ...
477 // http://doc.spip.org/@critere_collecte_dist
478 function critere_collecte_dist($idb, &$boucles, $crit){
479 if (isset($crit->param[0])){
480 $_coll = calculer_liste($crit->param[0], array(), $boucles, $boucles[$idb]->id_parent);
481 $boucle = $boucles[$idb];
482 $boucle->modificateur['collate'] = "($_coll ?' COLLATE '.$_coll:'')";
483 $n = count($boucle->order);
484 if ($n && (strpos($boucle->order[$n-1], 'COLLATE')===false))
485 $boucle->order[$n-1] .= " . ".$boucle->modificateur['collate'];
486 } else
487 return (array('zbug_critere_inconnu', array('critere' => $crit->op." ".count($boucles[$idb]->order))));
488 }
489
490 // http://doc.spip.org/@calculer_critere_arg_dynamique
491 function calculer_critere_arg_dynamique($idb, &$boucles, $crit, $suffix = ''){
492 $boucle = $boucles[$idb];
493 $alt = "('".$boucle->id_table.'.\' . $x'.$suffix.')';
494 $var = '$champs_'.$idb;
495 $desc = (strpos($boucle->in, "static $var =")!==false);
496 if (!$desc){
497 $desc = $boucle->show['field'];
498 $desc = implode(',', array_map('_q', array_keys($desc)));
499 $boucles[$idb]->in .= "\n\tstatic $var = array(".$desc.");";
500 }
501 if ($desc) $alt = "(in_array(\$x, $var) ? $alt :(\$x$suffix))";
502 $arg = calculer_liste($crit, array(), $boucles, $boucle->id_parent);
503 return "((\$x = preg_replace(\"/\\W/\",'', $arg)) ? $alt : '')";
504 }
505
506 // Tri : {par xxxx}
507 // http://www.spip.net/@par
508 // http://doc.spip.org/@critere_par_dist
509 function critere_par_dist($idb, &$boucles, $crit){
510 return critere_parinverse($idb, $boucles, $crit);
511 }
512
513 // http://doc.spip.org/@critere_parinverse
514 function critere_parinverse($idb, &$boucles, $crit, $sens = ''){
515 global $exceptions_des_jointures;
516 $boucle = &$boucles[$idb];
517 if ($crit->not) $sens = $sens ? "" : " . ' DESC'";
518 $collecte = (isset($boucle->modificateur['collecte'])) ? " . ".$boucle->modificateur['collecte'] : "";
519
520 foreach ($crit->param as $tri){
521
522 $order = $fct = ""; // en cas de fonction SQL
523 // tris specifies dynamiquement
524 if ($tri[0]->type!='texte'){
525 // calculer le order dynamique qui verifie les champs
526 $order = calculer_critere_arg_dynamique($idb, $boucles, $tri, $sens);
527 // et si ce n'est fait, ajouter un champ 'hasard'
528 // pour supporter 'hasard' comme tri dynamique
529 $par = "rand()";
530 $parha = $par." AS hasard";
531 if (!in_array($parha, $boucle->select))
532 $boucle->select[] = $parha;
533 } else {
534 $par = array_shift($tri);
535 $par = $par->texte;
536 // par multi champ
537 if (preg_match(",^multi[\s]*(.*)$,", $par, $m)){
538 $champ = trim($m[1]);
539 // par multi L1.champ
540 if (strpos($champ, '.')) {
541 $cle = '';
542 // par multi champ (champ sur une autre table)
543 } elseif (!array_key_exists($champ, $boucle->show['field'])){
544 $cle = trouver_jointure_champ($champ, $boucle);
545 // par multi champ (champ dans la table en cours)
546 } else {
547 $cle = $boucle->id_table;
548 }
549 if ($cle) { $cle .= '.'; }
550 $texte = $cle.$champ;
551 $boucle->select[] = "\".sql_multi('".$texte."', \$GLOBALS['spip_lang']).\"";
552 $order = "'multi'";
553 // par num champ(, suite)
554 } else if (preg_match(",^num (.*)$,m", $par, $m)) {
555 $champ = trim($m[1]);
556 // par num L1.champ
557 if (strpos($champ, '.')) {
558 $cle = '';
559 // par num champ (champ sur une autre table)
560 } elseif (!array_key_exists($champ, $boucle->show['field'])){
561 $cle = trouver_jointure_champ($champ, $boucle);
562 // par num champ (champ dans la table en cours)
563 } else {
564 $cle = $boucle->id_table;
565 }
566 if ($cle) { $cle .= '.'; }
567 $texte = '0+'. $cle . $champ;
568 $suite = calculer_liste($tri, array(), $boucles, $boucle->id_parent);
569 if ($suite!=="''")
570 $texte = "\" . ((\$x = $suite) ? ('$texte' . \$x) : '0')"." . \"";
571 $as = 'num'.($boucle->order ? count($boucle->order) : "");
572 $boucle->select[] = $texte." AS $as";
573 $order = "'$as'";
574 } else {
575 if (!preg_match(",^".CHAMP_SQL_PLUS_FONC.'$,is', $par, $match)){
576 return (array('zbug_critere_inconnu', array('critere' => $crit->op." $par")));
577 } else {
578 if (count($match)>2){
579 $par = substr($match[2], 1, -1);
580 $fct = $match[1];
581 }
582 // par hasard
583 if ($par=='hasard'){
584 $par = "rand()";
585 $boucle->select[] = $par." AS alea";
586 $order = "'alea'";
587 }
588 // par titre_mot ou type_mot voire d'autres
589 else if (isset($exceptions_des_jointures[$par])){
590 list($table, $champ) = $exceptions_des_jointures[$par];
591 $order = critere_par_joint($table, $champ, $boucle, $idb);
592 if (!$order)
593 return (array('zbug_critere_inconnu', array('critere' => $crit->op." $par")));
594 }
595 else if ($par=='date'
596 AND $desc = $boucle->show
597 AND $desc['date']
598 ){
599 $m = $desc['date'];
600 $order = "'".$boucle->id_table.".".$m."'";
601 }
602 // par champ. Verifier qu'ils sont presents.
603 elseif (preg_match("/^([^,]*)\.(.*)$/", $par, $r)) {
604 // cas du tri sur champ de jointure explicite
605 $t = array_search($r[1], $boucle->from);
606 if (!$t){
607 $t = trouver_jointure_champ($r[2], $boucle, array($r[1]));
608 }
609 if (!$t){
610 return (array('zbug_critere_inconnu', array('critere' => $crit->op." $par")));
611 } else $order = "'".$t.'.'.$r[2]."'";
612 } else {
613 $desc = $boucle->show;
614 if ($desc['field'][$par])
615 $par = $boucle->id_table.".".$par;
616 // sinon tant pis, ca doit etre un champ synthetise (cf points)
617 $order = "'$par'";
618 }
619 }
620 }
621 }
622 if (preg_match('/^\'([^"]*)\'$/', $order, $m)){
623 $t = $m[1];
624 if (strpos($t, '.') AND !in_array($t, $boucle->select)){
625 $boucle->select[] = $t;
626 }
627 } else $sens = '';
628
629 if ($fct){
630 if (preg_match("/^\s*'(.*)'\s*$/", $order, $r))
631 $order = "'$fct(".$r[1].")'";
632 else $order = "'$fct(' . $order . ')'";
633 }
634 $t = $order.$collecte.$sens;
635 if (preg_match("/^(.*)'\s*\.\s*'([^']*')$/", $t, $r))
636 $t = $r[1].$r[2];
637 $boucle->order[] = $t;
638 }
639 }
640
641 // http://doc.spip.org/@critere_par_joint
642 function critere_par_joint($table, $champ, &$boucle, $idb){
643 $t = array_search($table, $boucle->from);
644 if (!$t) $t = trouver_jointure_champ($champ, $boucle);
645 return !$t ? '' : ("'".$t.'.'.$champ."'");
646 }
647
648 // {inverse}
649 // http://www.spip.net/@inverse
650
651 // http://doc.spip.org/@critere_inverse_dist
652 function critere_inverse_dist($idb, &$boucles, $crit){
653
654 $boucle = &$boucles[$idb];
655 // Classement par ordre inverse
656 if ($crit->not)
657 critere_parinverse($idb, $boucles, $crit);
658 else
659 {
660 $order = "' DESC'";
661 // Classement par ordre inverse fonction eventuelle de #ENV{...}
662 if (isset($crit->param[0])){
663 $critere = calculer_liste($crit->param[0], array(), $boucles, $boucles[$idb]->id_parent);
664 $order = "(($critere)?' DESC':'')";
665 }
666
667 $n = count($boucle->order);
668 if (!$n){
669 if (isset($boucle->default_order[0]))
670 $boucle->default_order[0] .= ' . " DESC"';
671 else
672 $boucle->default_order[] = ' DESC';
673 } else {
674 $t = $boucle->order[$n-1]." . $order";
675 if (preg_match("/^(.*)'\s*\.\s*'([^']*')$/", $t, $r))
676 $t = $r[1].$r[2];
677 $boucle->order[$n-1] = $t;
678 }
679 }
680 }
681
682 // http://doc.spip.org/@critere_agenda_dist
683 function critere_agenda_dist($idb, &$boucles, $crit){
684 $params = $crit->param;
685
686 if (count($params)<1)
687 return (array('zbug_critere_inconnu', array('critere' => $crit->op." ?")));
688
689 $parent = $boucles[$idb]->id_parent;
690
691 // les valeurs $date et $type doivent etre connus a la compilation
692 // autrement dit ne pas etre des champs
693
694 $date = array_shift($params);
695 $date = $date[0]->texte;
696
697 $type = array_shift($params);
698 $type = $type[0]->texte;
699
700 $annee = $params ? array_shift($params) : "";
701 $annee = "\n".'sprintf("%04d", ($x = '.
702 calculer_liste($annee, array(), $boucles, $parent).
703 ') ? $x : date("Y"))';
704
705 $mois = $params ? array_shift($params) : "";
706 $mois = "\n".'sprintf("%02d", ($x = '.
707 calculer_liste($mois, array(), $boucles, $parent).
708 ') ? $x : date("m"))';
709
710 $jour = $params ? array_shift($params) : "";
711 $jour = "\n".'sprintf("%02d", ($x = '.
712 calculer_liste($jour, array(), $boucles, $parent).
713 ') ? $x : date("d"))';
714
715 $annee2 = $params ? array_shift($params) : "";
716 $annee2 = "\n".'sprintf("%04d", ($x = '.
717 calculer_liste($annee2, array(), $boucles, $parent).
718 ') ? $x : date("Y"))';
719
720 $mois2 = $params ? array_shift($params) : "";
721 $mois2 = "\n".'sprintf("%02d", ($x = '.
722 calculer_liste($mois2, array(), $boucles, $parent).
723 ') ? $x : date("m"))';
724
725 $jour2 = $params ? array_shift($params) : "";
726 $jour2 = "\n".'sprintf("%02d", ($x = '.
727 calculer_liste($jour2, array(), $boucles, $parent).
728 ') ? $x : date("d"))';
729
730 $boucle = &$boucles[$idb];
731 $date = $boucle->id_table.".$date";
732
733 $quote_end = ",'".$boucle->sql_serveur."','text'";
734 if ($type=='jour')
735 $boucle->where[] = array("'='", "'DATE_FORMAT($date, \'%Y%m%d\')'",
736 ("sql_quote($annee . $mois . $jour$quote_end)"));
737 elseif ($type=='mois')
738 $boucle->where[] = array("'='", "'DATE_FORMAT($date, \'%Y%m\')'",
739 ("sql_quote($annee . $mois$quote_end)"));
740 elseif ($type=='semaine')
741 $boucle->where[] = array("'AND'",
742 array("'>='",
743 "'DATE_FORMAT($date, \'%Y%m%d\')'",
744 ("date_debut_semaine($annee, $mois, $jour)")),
745 array("'<='",
746 "'DATE_FORMAT($date, \'%Y%m%d\')'",
747 ("date_fin_semaine($annee, $mois, $jour)")));
748 elseif (count($crit->param)>2)
749 $boucle->where[] = array("'AND'",
750 array("'>='",
751 "'DATE_FORMAT($date, \'%Y%m%d\')'",
752 ("sql_quote($annee . $mois . $jour$quote_end)")),
753 array("'<='", "'DATE_FORMAT($date, \'%Y%m%d\')'", ("sql_quote($annee2 . $mois2 . $jour2$quote_end)")));
754 // sinon on prend tout
755 }
756
757 // http://doc.spip.org/@calculer_critere_parties
758 function calculer_critere_parties($idb, &$boucles, $crit){
759 $boucle = &$boucles[$idb];
760 $a1 = $crit->param[0];
761 $a2 = $crit->param[1];
762 $op = $crit->op;
763
764 list($a11, $a12) = calculer_critere_parties_aux($idb, $boucles, $a1);
765 list($a21, $a22) = calculer_critere_parties_aux($idb, $boucles, $a2);
766
767 if (($op==',') && (is_numeric($a11) && (is_numeric($a21)))){
768 $boucle->limit = $a11.','.$a21;
769 }
770 else {
771 $boucle->total_parties = ($a21!='n') ? $a21 : $a22;
772 $partie = ($a11!='n') ? $a11 : $a12;
773 $mode = (($op=='/') ? '/' :
774 (($a11=='n') ? '-' : '+').(($a21=='n') ? '-' : '+'));
775 // cas simple {0,#ENV{truc}} compilons le en LIMIT :
776 if ($a11!=='n' AND $a21!=='n' AND $mode=="++" AND $op==','){
777 $boucle->limit =
778 (is_numeric($a11)?"'$a11'":$a11)
779 .".','."
780 .(is_numeric($a21)?"'$a21'":$a21);
781 }
782 else
783 calculer_parties($boucles, $idb, $partie, $mode);
784 }
785 }
786
787 //
788 // Code specifique aux criteres {pagination}, {1,n} {n/m} etc
789 //
790
791 function calculer_parties(&$boucles, $id_boucle, $debut, $mode){
792 $total_parties = $boucles[$id_boucle]->total_parties;
793
794 preg_match(",([+-/p])([+-/])?,", $mode, $regs);
795 list(, $op1, $op2) = array_pad($regs, 3, null);
796 $nombre_boucle = "\$Numrows['$id_boucle']['total']";
797 // {1/3}
798 if ($op1=='/'){
799 $pmoins1 = is_numeric($debut) ? ($debut-1) : "($debut-1)";
800 $totpos = is_numeric($total_parties) ? ($total_parties) :
801 "($total_parties ? $total_parties : 1)";
802 $fin = "ceil(($nombre_boucle * $debut )/$totpos) - 1";
803 $debut = !$pmoins1 ? 0 : "ceil(($nombre_boucle * $pmoins1)/$totpos);";
804 } else {
805 // cas {n-1,x}
806 if ($op1=='-') $debut = "$nombre_boucle - $debut;";
807
808 // cas {x,n-1}
809 if ($op2=='-'){
810 $fin = '$debut_boucle + '.$nombre_boucle.' - '
811 .(is_numeric($total_parties) ? ($total_parties+1) :
812 ($total_parties.' - 1'));
813 } else {
814 // {x,1} ou {pagination}
815 $fin = '$debut_boucle'
816 .(is_numeric($total_parties) ?
817 (($total_parties==1) ? "" : (' + '.($total_parties-1))) :
818 ('+'.$total_parties.' - 1'));
819 }
820
821 // {pagination}, gerer le debut_xx=-1 pour tout voir
822 if ($op1=='p'){
823 $debut .= ";\n \$debut_boucle = ((\$tout=(\$debut_boucle == -1))?0:(\$debut_boucle))";
824 $debut .= ";\n \$debut_boucle = max(0,min(\$debut_boucle,floor(($nombre_boucle-1)/($total_parties))*($total_parties)))";
825 $fin = "(\$tout ? $nombre_boucle : $fin)";
826 }
827 }
828
829 // Notes :
830 // $debut_boucle et $fin_boucle sont les indices SQL du premier
831 // et du dernier demandes dans la boucle : 0 pour le premier,
832 // n-1 pour le dernier ; donc total_boucle = 1 + debut - fin
833 // Utiliser min pour rabattre $fin_boucle sur total_boucle.
834
835 $boucles[$id_boucle]->mode_partie = "\n\t"
836 .'$debut_boucle = '.$debut.";\n "
837 .'$fin_boucle = min('.$fin.", \$Numrows['$id_boucle']['total'] - 1);\n "
838 .'$Numrows[\''.$id_boucle."']['grand_total'] = \$Numrows['$id_boucle']['total'];\n "
839 .'$Numrows[\''.$id_boucle.'\']["total"] = max(0,$fin_boucle - $debut_boucle + 1);'
840 ."\n\tif (\$debut_boucle>0 AND \$debut_boucle < \$Numrows['$id_boucle']['grand_total'] AND \$iter->seek(\$debut_boucle,'continue'))\n\t\t\$Numrows['$id_boucle']['compteur_boucle'] = \$debut_boucle;\n\t";
841
842 $boucles[$id_boucle]->partie = "
843 if (\$Numrows['$id_boucle']['compteur_boucle'] <= \$debut_boucle) continue;
844 if (\$Numrows['$id_boucle']['compteur_boucle']-1 > \$fin_boucle) break;";
845 }
846
847 // http://doc.spip.org/@calculer_critere_parties_aux
848 function calculer_critere_parties_aux($idb, &$boucles, $param){
849 if ($param[0]->type!='texte'){
850 $a1 = calculer_liste(array($param[0]), array('id_mere' => $idb), $boucles, $boucles[$idb]->id_parent);
851 preg_match(',^ *(-([0-9]+))? *$,', $param[1]->texte, $m);
852 return array("intval($a1)", ($m[2] ? $m[2] : 0));
853 } else {
854 preg_match(',^ *(([0-9]+)|n) *(- *([0-9]+)? *)?$,', $param[0]->texte, $m);
855 $a1 = $m[1];
856 if (!@$m[3])
857 return array($a1, 0);
858 elseif ($m[4])
859 return array($a1, $m[4]);
860 else return array($a1,
861 calculer_liste(array($param[1]), array(), $boucles[$idb]->id_parent, $boucles));
862 }
863 }
864
865
866 /**
867 * Compile les critères d'une boucle
868 *
869 * Cette fonction d'aiguillage cherche des fonctions spécifiques déclarées
870 * pour chaque critère demandé, dans l'ordre ci-dessous :
871 *
872 * - critere_{serveur}_{table}_{critere}, sinon avec _dist
873 * - critere_{serveur}_{critere}, sinon avec _dist
874 * - critere_{table}_{critere}, sinon avec _dist
875 * - critere_{critere}, sinon avec _dist
876 * - critere_defaut, sinon avec _dist
877 *
878 * Émet une erreur de squelette si un critère retourne une erreur.
879 *
880 * @param string $idb
881 * Identifiant de la boucle
882 * @param array $boucles
883 * AST du squelette
884 * @return string|array
885 * string : Chaine vide sans erreur
886 * array : Erreur sur un des critères
887 **/
888 function calculer_criteres($idb, &$boucles){
889 $msg = '';
890 $boucle = $boucles[$idb];
891 $table = strtoupper($boucle->type_requete);
892 $serveur = strtolower($boucle->sql_serveur);
893
894 $defaut = charger_fonction('DEFAUT', 'calculer_critere');
895 // s'il y avait une erreur de syntaxe, propager cette info
896 if (!is_array($boucle->criteres)) return array();
897
898 foreach ($boucle->criteres as $crit){
899 $critere = $crit->op;
900 // critere personnalise ?
901 if (
902 (!$serveur OR
903 ((!function_exists($f = "critere_".$serveur."_".$table."_".$critere))
904 AND (!function_exists($f = $f."_dist"))
905 AND (!function_exists($f = "critere_".$serveur."_".$critere))
906 AND (!function_exists($f = $f."_dist"))
907 )
908 )
909 AND (!function_exists($f = "critere_".$table."_".$critere))
910 AND (!function_exists($f = $f."_dist"))
911 AND (!function_exists($f = "critere_".$critere))
912 AND (!function_exists($f = $f."_dist"))
913 ){
914 // fonction critere standard
915 $f = $defaut;
916 }
917 // compile le critere
918 $res = $f($idb, $boucles, $crit);
919
920 // Gestion centralisee des erreurs pour pouvoir propager
921 if (is_array($res)){
922 $msg = $res;
923 erreur_squelette($msg, $boucle);
924 }
925 }
926 return $msg;
927 }
928
929 /**
930 * Madeleine de Proust, revision MIT-1958 sqq, revision CERN-1989
931 * hum, c'est kwoi cette fonxion ? on va dire qu'elle desemberlificote les guillemets...
932 *
933 * http://doc.spip.org/@kwote
934 *
935 * @param string $lisp
936 * @param string $serveur
937 * @param string $type
938 * @return string
939 */
940 function kwote($lisp, $serveur='', $type=''){
941 if (preg_match(_CODE_QUOTE, $lisp, $r))
942 return $r[1]."\"".sql_quote(str_replace(array("\\'", "\\\\"), array("'", "\\"), $r[2]),$serveur,$type)."\"";
943 else
944 return "sql_quote($lisp)";
945 }
946
947 // Si on a une liste de valeurs dans #ENV{x}, utiliser la double etoile
948 // pour faire par exemple {id_article IN #ENV**{liste_articles}}
949 // http://doc.spip.org/@critere_IN_dist
950 function critere_IN_dist($idb, &$boucles, $crit){
951 $r = calculer_critere_infixe($idb, $boucles, $crit);
952 if (!$r){
953 return (array('zbug_critere_inconnu', array('critere' => $crit->op." ?")));
954 }
955 list($arg, $op, $val, $col, $where_complement) = $r;
956
957 $in = critere_IN_cas($idb, $boucles, $crit->not ? 'NOT' : ($crit->exclus ? 'exclus' : ''), $arg, $op, $val, $col);
958
959 // inserer la condition; exemple: {id_mot ?IN (66, 62, 64)}
960 $where = $in;
961 if ($crit->cond){
962 $pred = calculer_argument_precedent($idb, $col, $boucles);
963 $where = array("'?'", $pred, $where, "''");
964 if ($where_complement) // condition annexe du type "AND (objet='article')"
965 $where_complement = array("'?'", $pred, $where_complement, "''");
966 }
967 if ($crit->exclus)
968 if (!preg_match(",^L[0-9]+[.],", $arg))
969 $where = array("'NOT'", $where);
970 else
971 // un not sur un critere de jointure se traduit comme un NOT IN avec une sous requete
972 // c'est une sous requete identique a la requete principale sous la forme (SELF,$select,$where) avec $select et $where qui surchargent
973 $where = array("'NOT'", array("'IN'", "'".$boucles[$idb]->id_table.".".$boucles[$idb]->primary."'", array("'SELF'", "'".$boucles[$idb]->id_table.".".$boucles[$idb]->primary."'", $where)));
974
975 $boucles[$idb]->where[] = $where;
976 if ($where_complement) // condition annexe du type "AND (objet='article')"
977 $boucles[$idb]->where[] = $where_complement;
978 }
979
980 // http://doc.spip.org/@critere_IN_cas
981 function critere_IN_cas($idb, &$boucles, $crit2, $arg, $op, $val, $col){
982 static $num = array();
983 $descr = $boucles[$idb]->descr;
984 $cpt = &$num[$descr['nom']][$descr['gram']][$idb];
985
986 $var = '$in'.$cpt++;
987 $x = "\n\t$var = array();";
988 foreach ($val as $k => $v){
989 if (preg_match(",^(\n//.*\n)?'(.*)'$,", $v, $r)){
990 // optimiser le traitement des constantes
991 if (is_numeric($r[2]))
992 $x .= "\n\t$var"."[]= $r[2];";
993 else
994 $x .= "\n\t$var"."[]= ".sql_quote($r[2]).";";
995 } else {
996 // Pour permettre de passer des tableaux de valeurs
997 // on repere l'utilisation brute de #ENV**{X},
998 // c'est-a-dire sa traduction en ($PILE[0][X]).
999 // et on deballe mais en rajoutant l'anti XSS
1000 $x .= "\n\tif (!(is_array(\$a = ($v))))\n\t\t$var"."[]= \$a;\n\telse $var = array_merge($var, \$a);";
1001 }
1002 }
1003
1004 $boucles[$idb]->in .= $x;
1005
1006 // inserer le tri par defaut selon les ordres du IN ...
1007 // avec une ecriture de type FIELD qui degrade les performances (du meme ordre qu'un rexgexp)
1008 // et que l'on limite donc strictement aux cas necessaires :
1009 // si ce n'est pas un !IN, et si il n'y a pas d'autre order dans la boucle
1010 if (!$crit2){
1011 $boucles[$idb]->default_order[] = "((!sql_quote($var) OR sql_quote($var)===\"''\") ? 0 : ('FIELD($arg,' . sql_quote($var) . ')'))";
1012 }
1013
1014 return "sql_in('$arg',sql_quote($var)".($crit2=='NOT' ? ",'NOT'" : "").")";
1015 }
1016
1017 /**
1018 * {where}
1019 * tout simplement, pour faire le pont entre php et squelettes
1020 *
1021 * @param <type> $idb
1022 * @param <type> $boucles
1023 * @param <type> $crit
1024 */
1025 function critere_where_dist($idb, &$boucles, $crit){
1026 $boucle = &$boucles[$idb];
1027 if (isset($crit->param[0]))
1028 $_where = calculer_liste($crit->param[0], array(), $boucles, $boucle->id_parent);
1029 else
1030 $_where = '@$Pile[0]["where"]';
1031
1032 if ($crit->cond)
1033 $_where = "(($_where) ? ($_where) : '')";
1034
1035 if ($crit->not)
1036 $_where = "array('NOT',$_where)";
1037
1038 $boucle->where[] = $_where;
1039 }
1040
1041
1042 /**
1043 * Un critere pour gerer un champ de tri qui peut etre modifie dynamiquement
1044 * par la balise #TRI
1045 *
1046 * {tri [champ_par_defaut][,sens_par_defaut][,nom_variable]}
1047 * champ_par_defaut : un champ de la table sql
1048 * sens_par_defaut : -1 ou inverse pour decroissant, 1 ou direct pour croissant
1049 * peut etre un tableau pour preciser des sens par defaut associes a chaque champ
1050 * exemple : array('titre'=>1,'date'=>-1) pour trier par defaut
1051 * les titre croissant et les dates decroissantes
1052 * dans ce cas, quand un champ est utilise pour le tri et n'est pas present dans le tableau
1053 * c'est la premiere valeur qui est utilisee
1054 * nom_variable : nom de la variable utilisee (par defaut tri_nomboucle)
1055 *
1056 * {tri titre}
1057 * {tri titre,inverse}
1058 * {tri titre,-1}
1059 * {tri titre,-1,truc}
1060 *
1061 * le critere {tri} s'utilise conjointement avec la balise #TRI dans la meme boucle
1062 * pour generer les liens qui permettent de changer le critere de tri et le sens du tri
1063 *
1064 * Exemple d'utilisation
1065 *
1066 * <B_articles>
1067 * <p>#TRI{titre,'Trier par titre'} | #TRI{date,'Trier par date'}</p>
1068 * <ul>
1069 * <BOUCLE_articles(ARTICLES){tri titre}>
1070 * <li>#TITRE - [(#DATE|affdate_jourcourt)]</li>
1071 * </BOUCLE_articles>
1072 * </ul>
1073 * </B_articles>
1074 *
1075 * NB :
1076 * contraitement a {par ...} {tri} ne peut prendre qu'un seul champ,
1077 * mais il peut etre complete avec {par ...} pour indiquer des criteres secondaires
1078 *
1079 * ex :
1080 * {tri num titre}{par titre} permet de faire un tri sur le rang (modifiable dynamiquement)
1081 * avec un second critere sur le titre en cas d'egalite des rang
1082 *
1083 * @param unknown_type $idb
1084 * @param unknown_type $boucles
1085 * @param unknown_type $crit
1086 */
1087 function critere_tri_dist($idb, &$boucles, $crit){
1088 $boucle = &$boucles[$idb];
1089
1090 // definition du champ par defaut
1091 $_champ_defaut = !isset($crit->param[0][0]) ? "''"
1092 : calculer_liste(array($crit->param[0][0]), array(), $boucles, $boucle->id_parent);
1093 $_sens_defaut = !isset($crit->param[1][0]) ? "1"
1094 : calculer_liste(array($crit->param[1][0]), array(), $boucles, $boucle->id_parent);
1095 $_variable = !isset($crit->param[2][0]) ? "'$idb'"
1096 : calculer_liste(array($crit->param[2][0]), array(), $boucles, $boucle->id_parent);
1097
1098 $_tri = "((\$t=(isset(\$Pile[0]['tri'.$_variable]))?\$Pile[0]['tri'.$_variable]:$_champ_defaut)?tri_protege_champ(\$t):'')";
1099
1100 $_sens_defaut = "(is_array(\$s=$_sens_defaut)?(isset(\$s[\$st=$_tri])?\$s[\$st]:reset(\$s)):\$s)";
1101 $_sens = "((intval(\$t=(isset(\$Pile[0]['sens'.$_variable]))?\$Pile[0]['sens'.$_variable]:$_sens_defaut)==-1 OR \$t=='inverse')?-1:1)";
1102
1103 $boucle->modificateur['tri_champ'] = $_tri;
1104 $boucle->modificateur['tri_sens'] = $_sens;
1105 $boucle->modificateur['tri_nom'] = $_variable;
1106 // faut il inserer un test sur l'existence de $tri parmi les champs de la table ?
1107 // evite des erreurs sql, mais peut empecher des tri sur jointure ...
1108 $boucle->hash .= "
1109 \$senstri = '';
1110 \$tri = $_tri;
1111 if (\$tri){
1112 \$senstri = $_sens;
1113 \$senstri = (\$senstri<0)?' DESC':'';
1114 };
1115 ";
1116 $boucle->select[] = "\".tri_champ_select(\$tri).\"";
1117 $boucle->order[] = "tri_champ_order(\$tri,\$command['from']).\$senstri";
1118 }
1119
1120 # Criteres de comparaison
1121
1122 // http://doc.spip.org/@calculer_critere_DEFAUT
1123 function calculer_critere_DEFAUT_dist($idb, &$boucles, $crit){
1124 // double cas particulier {0,1} et {1/2} repere a l'analyse lexicale
1125 if (($crit->op==",") OR ($crit->op=='/'))
1126 return calculer_critere_parties($idb, $boucles, $crit);
1127
1128 $r = calculer_critere_infixe($idb, $boucles, $crit);
1129 if (!$r){
1130 # // on produit une erreur seulement si le critere n'a pas de '?'
1131 # if (!$crit->cond) {
1132 return (array('zbug_critere_inconnu', array('critere' => $crit->op)));
1133 # }
1134 } else calculer_critere_DEFAUT_args($idb, $boucles, $crit, $r);
1135 }
1136
1137 function calculer_critere_DEFAUT_args($idb, &$boucles, $crit, $args){
1138 list($arg, $op, $val, $col, $where_complement) = $args;
1139
1140 $where = array("'$op'", "'$arg'", $val[0]);
1141
1142 // inserer la negation (cf !...)
1143
1144 if ($crit->not) $where = array("'NOT'", $where);
1145 if ($crit->exclus)
1146 if (!preg_match(",^L[0-9]+[.],", $arg))
1147 $where = array("'NOT'", $where);
1148 else
1149 // un not sur un critere de jointure se traduit comme un NOT IN avec une sous requete
1150 // c'est une sous requete identique a la requete principale sous la forme (SELF,$select,$where) avec $select et $where qui surchargent
1151 $where = array("'NOT'", array("'IN'", "'".$boucles[$idb]->id_table.".".$boucles[$idb]->primary."'", array("'SELF'", "'".$boucles[$idb]->id_table.".".$boucles[$idb]->primary."'", $where)));
1152
1153 // inserer la condition (cf {lang?})
1154 // traiter a part la date, elle est mise d'office par SPIP,
1155 if ($crit->cond){
1156 $pred = calculer_argument_precedent($idb, $col, $boucles);
1157 if ($col=="date" OR $col=="date_redac"){
1158 if ($pred=="\$Pile[0]['".$col."']"){
1159 $pred = "(\$Pile[0]['{$col}_default']?'':$pred)";
1160 }
1161 }
1162
1163 if ($op=='=' AND !$crit->not)
1164 $where = array("'?'", "(is_array($pred))",
1165 critere_IN_cas($idb, $boucles, 'COND', $arg, $op, array($pred), $col),
1166 $where);
1167 $where = array("'?'", "!(is_array($pred)?count($pred):strlen($pred))", "''", $where);
1168 if ($where_complement) // condition annexe du type "AND (objet='article')"
1169 $where_complement = array("'?'", "!(is_array($pred)?count($pred):strlen($pred))", "''", $where_complement);
1170 }
1171
1172 $boucles[$idb]->where[] = $where;
1173 if ($where_complement) // condition annexe du type "AND (objet='article')"
1174 $boucles[$idb]->where[] = $where_complement;
1175 }
1176
1177 // http://doc.spip.org/@calculer_critere_infixe
1178 function calculer_critere_infixe($idb, &$boucles, $crit){
1179
1180 global $table_criteres_infixes;
1181 global $exceptions_des_jointures, $exceptions_des_tables;
1182
1183 $boucle = &$boucles[$idb];
1184 $type = $boucle->type_requete;
1185 $table = $boucle->id_table;
1186 $desc = $boucle->show;
1187 $col_vraie = null;
1188
1189 list($fct, $col, $op, $val, $args_sql) =
1190 calculer_critere_infixe_ops($idb, $boucles, $crit);
1191
1192 $col_alias = $col;
1193 $where_complement = false;
1194
1195 // Cas particulier : id_enfant => utiliser la colonne id_objet
1196 if ($col=='id_enfant')
1197 $col = $boucle->primary;
1198
1199 // Cas particulier : id_parent => verifier les exceptions de tables
1200 if (in_array($col,array('id_parent','id_secteur'))
1201 AND isset($exceptions_des_tables[$table][$col]))
1202 $col = $exceptions_des_tables[$table][$col];
1203
1204 // et possibilite de gerer un critere secteur sur des tables de plugins (ie forums)
1205 else if (($col=='id_secteur') AND ($critere_secteur = charger_fonction("critere_secteur_$type", "public", true))){
1206 $table = $critere_secteur($idb, $boucles, $val, $crit);
1207 }
1208
1209 // cas id_article=xx qui se mappe en id_objet=xx AND objet=article
1210 // sauf si exception declaree : sauter cette etape
1211 else if (
1212 !isset($exceptions_des_jointures[table_objet_sql($table)][$col])
1213 AND !isset($exceptions_des_jointures[$col])
1214 AND count(trouver_champs_decomposes($col, $desc))>1
1215 ){
1216 $e = decompose_champ_id_objet($col);
1217 $col = array_shift($e);
1218 $where_complement = primary_doublee($e, $table);
1219 }
1220 // Cas particulier : expressions de date
1221 else if ($c = calculer_critere_infixe_date($idb, $boucles, $col)){
1222 list($col,$col_vraie) = $c;
1223 $table = '';
1224 }
1225 else if (preg_match('/^(.*)\.(.*)$/', $col, $r)){
1226 list(, $table, $col) = $r;
1227 $col_alias = $col;
1228
1229 $trouver_table = charger_fonction('trouver_table','base');
1230 if ($desc = $trouver_table($table, $boucle->sql_serveur)
1231 AND isset($desc['field'][$col])
1232 AND $cle = array_search($desc['table'],$boucle->from))
1233 $table = $cle;
1234 else {
1235 $table = trouver_jointure_champ($col, $boucle, array($table), ($crit->cond OR $op!='='));
1236 }
1237 #$table = calculer_critere_externe_init($boucle, array($table), $col, $desc, ($crit->cond OR $op!='='), true);
1238 if (!$table) return '';
1239 }
1240 elseif (@!array_key_exists($col, $desc['field'])
1241 // Champ joker * des iterateurs DATA qui accepte tout
1242 AND @!array_key_exists('*', $desc['field'])
1243 ) {
1244 $r = calculer_critere_infixe_externe($boucle, $crit, $op, $desc, $col, $col_alias, $table);
1245 if (!$r) return '';
1246 list($col, $col_alias, $table, $where_complement, $desc) = $r;
1247 }
1248
1249 $col_vraie = ($col_vraie?$col_vraie:$col);
1250 // Dans tous les cas,
1251 // virer les guillemets eventuels autour d'un int (qui sont refuses par certains SQL) et passer dans sql_quote avec le type si connu
1252 // et int sinon si la valeur est numerique
1253 // sinon introduire le vrai type du champ si connu dans le sql_quote (ou int NOT NULL sinon)
1254 // Ne pas utiliser intval, PHP tronquant les Bigint de SQL
1255 if ($op=='=' OR in_array($op, $table_criteres_infixes)){
1256
1257 // defaire le quote des int et les passer dans sql_quote avec le bon type de champ si on le connait, int sinon
1258 // prendre en compte le debug ou la valeur arrive avec un commentaire PHP en debut
1259 if (preg_match(",^\\A(\s*//.*?$\s*)?\"'(-?\d+)'\"\\z,ms", $val[0], $r))
1260 $val[0] = $r[1].'"'.sql_quote($r[2],$boucle->sql_serveur,(isset($desc['field'][$col_vraie])?$desc['field'][$col_vraie]:'int NOT NULL')).'"';
1261
1262 // sinon expliciter les
1263 // sql_quote(truc) en sql_quote(truc,'',type)
1264 // sql_quote(truc,serveur) en sql_quote(truc,serveur,type)
1265 // sans toucher aux
1266 // sql_quote(truc,'','varchar(10) DEFAULT \'oui\' COLLATE NOCASE')
1267 // sql_quote(truc,'','varchar')
1268 elseif (preg_match('/\Asql_quote[(](.*?)(,[^)]*?)?(,[^)]*(?:\(\d+\)[^)]*)?)?[)]\s*\z/ms', $val[0], $r)
1269 // si pas deja un type
1270 AND (!isset($r[3]) OR !$r[3])) {
1271 $r = $r[1]
1272 .((isset($r[2]) AND $r[2]) ? $r[2] : ",''")
1273 .",'".(isset($desc['field'][$col_vraie])?addslashes($desc['field'][$col_vraie]):'int NOT NULL')."'";
1274 $val[0] = "sql_quote($r)";
1275 }
1276 }
1277 // Indicateur pour permettre aux fonctionx boucle_X de modifier
1278 // leurs requetes par defaut, notamment le champ statut
1279 // Ne pas confondre champs de la table principale et des jointures
1280 if ($table===$boucle->id_table){
1281 $boucles[$idb]->modificateur['criteres'][$col_vraie] = true;
1282 if ($col_alias!=$col_vraie)
1283 $boucles[$idb]->modificateur['criteres'][$col_alias] = true;
1284 }
1285
1286 // ajout pour le cas special d'une condition sur le champ statut:
1287 // il faut alors interdire a la fonction de boucle
1288 // de mettre ses propres criteres de statut
1289 // http://www.spip.net/@statut (a documenter)
1290 // garde pour compatibilite avec code des plugins anterieurs, mais redondant avec la ligne precedente
1291 if ($col=='statut') $boucles[$idb]->statut = true;
1292
1293 // inserer le nom de la table SQL devant le nom du champ
1294 if ($table){
1295 if ($col[0]=="`")
1296 $arg = "$table.".substr($col, 1, -1);
1297 else $arg = "$table.$col";
1298 } else $arg = $col;
1299
1300 // inserer la fonction SQL
1301 if ($fct) $arg = "$fct($arg$args_sql)";
1302
1303 return array($arg, $op, $val, $col_alias, $where_complement);
1304 }
1305
1306 function calculer_critere_infixe_externe($boucle, $crit, $op, $desc, $col, $col_alias, $table){
1307 global $exceptions_des_jointures;
1308 $where = '';
1309
1310 $calculer_critere_externe = 'calculer_critere_externe_init';
1311 // gestion par les plugins des jointures tordues
1312 // pas automatiques mais necessaires
1313 $table_sql = table_objet_sql($table);
1314 if (isset($exceptions_des_jointures[$table_sql])
1315 AND is_array($exceptions_des_jointures[$table_sql])
1316 AND
1317 (
1318 isset($exceptions_des_jointures[$table_sql][$col])
1319 OR
1320 isset($exceptions_des_jointures[$table_sql][''])
1321 )
1322 ){
1323 $t = $exceptions_des_jointures[$table_sql];
1324 $index = isset($t[$col])
1325 ? $t[$col] : (isset($t['']) ? $t[''] : array());
1326
1327 if (count($index)==3)
1328 list($t, $col, $calculer_critere_externe) = $index;
1329 elseif (count($index)==2) {
1330 list($t, $col) = $t[$col];
1331 }
1332 elseif (count($index)==1) {
1333 list($calculer_critere_externe) = $index;
1334 $t = $table;
1335 }
1336 else
1337 $t = ''; // jointure non declaree. La trouver.
1338 }
1339 elseif (isset($exceptions_des_jointures[$col]))
1340 list($t, $col) = $exceptions_des_jointures[$col];
1341 else
1342 $t = ''; // jointure non declaree. La trouver.
1343
1344 // ici on construit le from pour fournir $col en piochant dans les jointures
1345
1346 // si des jointures explicites sont fournies, on cherche d'abord dans celles ci
1347 // permet de forcer une table de lien quand il y a ambiguite
1348 // <BOUCLE_(DOCUMENTS documents_liens){id_mot}>
1349 // alors que <BOUCLE_(DOCUMENTS){id_mot}> produit la meme chose que <BOUCLE_(DOCUMENTS mots_liens){id_mot}>
1350 $table = "";
1351 if ($boucle->jointures_explicites){
1352 $jointures_explicites = explode(' ', $boucle->jointures_explicites);
1353 $table = $calculer_critere_externe($boucle, $jointures_explicites, $col, $desc, ($crit->cond OR $op!='='), $t);
1354 }
1355
1356 // et sinon on cherche parmi toutes les jointures declarees
1357 if (!$table) {
1358 $table = $calculer_critere_externe($boucle, $boucle->jointures, $col, $desc, ($crit->cond OR $op!='='), $t);
1359 }
1360
1361 if (!$table) return '';
1362
1363 // il ne reste plus qu'a trouver le champ dans les from
1364 list($nom, $desc) = trouver_champ_exterieur($col, $boucle->from, $boucle);
1365
1366 if (count(trouver_champs_decomposes($col, $desc))>1){
1367 $col_alias = $col; // id_article devient juste le nom d'origine
1368 $e = decompose_champ_id_objet($col);
1369 $col = array_shift($e);
1370 $where = primary_doublee($e, $table);
1371 }
1372
1373 return array($col, $col_alias, $table, $where, $desc);
1374 }
1375
1376 // Ne pas appliquer sql_quote lors de la compilation,
1377 // car on ne connait pas le serveur SQL, donc s'il faut \' ou ''
1378
1379 // http://doc.spip.org/@primary_doublee
1380 function primary_doublee($decompose, $table){
1381 $e1 = reset($decompose);
1382 $e2 = "sql_quote('".end($decompose)."')";
1383 return array("'='", "'$table.".$e1."'", $e2);
1384 }
1385
1386 /**
1387 * Champ hors table, ca ne peut etre qu'une jointure.
1388 * On cherche la table du champ et on regarde si elle est deja jointe
1389 * Si oui et qu'on y cherche un champ nouveau, pas de jointure supplementaire
1390 * Exemple: criteres {titre_mot=...}{type_mot=...}
1391 * Dans les 2 autres cas ==> jointure
1392 * (Exemple: criteres {type_mot=...}{type_mot=...} donne 2 jointures
1393 * pour selectioner ce qui a exactement ces 2 mots-cles.
1394 *
1395 * http://doc.spip.org/@calculer_critere_externe_init
1396 *
1397 * @param $boucle
1398 * @param $joints
1399 * @param $col
1400 * @param $desc
1401 * @param $cond
1402 * @param bool|string $checkarrivee
1403 * @return mixed|string
1404 */
1405 function calculer_critere_externe_init(&$boucle, $joints, $col, $desc, $cond, $checkarrivee = false){
1406 // si on demande un truc du genre spip_mots
1407 // avec aussi spip_mots_liens dans les jointures dispo
1408 // et qu'on est la
1409 // il faut privilegier la jointure directe en 2 etapes spip_mots_liens, spip_mots
1410 if ($checkarrivee
1411 AND is_string($checkarrivee)
1412 AND $a = table_objet($checkarrivee)
1413 AND in_array($a.'_liens', $joints)
1414 ){
1415 if ($res = calculer_lien_externe_init($boucle, $joints, $col, $desc, $cond, $checkarrivee)) {
1416 return $res;
1417 }
1418 }
1419 foreach ($joints as $joint){
1420 if ($arrivee = trouver_champ_exterieur($col, array($joint), $boucle, $checkarrivee)){
1421 $t = array_search($arrivee[0], $boucle->from);
1422 // transformer eventuellement id_xx en (id_objet,objet)
1423 $cols = trouver_champs_decomposes($col, $arrivee[1]);
1424 if ($t){
1425 $joindre = false;
1426 foreach ($cols as $col){
1427 $c = '/\b'.$t.".$col".'\b/';
1428 if (trouver_champ($c, $boucle->where)) $joindre = true;
1429 else {
1430 // mais ca peut etre dans le FIELD pour le Having
1431 $c = "/FIELD.$t".".$col,/";
1432 if (trouver_champ($c, $boucle->select)) $joindre = true;
1433 }
1434 }
1435 if (!$joindre) return $t;
1436 }
1437 if ($res = calculer_jointure($boucle, array($boucle->id_table, $desc), $arrivee, $cols, $cond, 1)) {
1438 return $res;
1439 }
1440 }
1441 }
1442 return '';
1443
1444 }
1445
1446 /**
1447 * Generer directement une jointure via une table de lien spip_xxx_liens
1448 * pour un critere {id_xxx}
1449 * @param $boucle
1450 * @param $joints
1451 * @param $col
1452 * @param $desc
1453 * @param $cond
1454 * @param bool $checkarrivee
1455 * @return string
1456 */
1457 function calculer_lien_externe_init(&$boucle, $joints, $col, $desc, $cond, $checkarrivee = false){
1458 $primary_arrivee = id_table_objet($checkarrivee);
1459
1460 $intermediaire = trouver_champ_exterieur($primary_arrivee, $joints, $boucle, $checkarrivee."_liens");
1461 $arrivee = trouver_champ_exterieur($col, $joints, $boucle, $checkarrivee);
1462
1463 if (!$intermediaire OR !$arrivee) return '';
1464
1465 $res = fabrique_jointures($boucle,
1466 array(
1467 array($boucle->id_table, $intermediaire, array(id_table_objet($desc['table_objet']), 'id_objet', 'objet', $desc['type'])),
1468 array(reset($intermediaire), $arrivee, $primary_arrivee)
1469 )
1470 , $cond, $desc, $boucle->id_table, array($col));
1471 return $res;
1472 }
1473
1474
1475 // http://doc.spip.org/@trouver_champ
1476 function trouver_champ($champ, $where){
1477 if (!is_array($where))
1478 return preg_match($champ, $where);
1479 else {
1480 foreach ($where as $clause){
1481 if (trouver_champ($champ, $clause)) return true;
1482 }
1483 return false;
1484 }
1485 }
1486
1487
1488 // determine l'operateur et les operandes
1489
1490 // http://doc.spip.org/@calculer_critere_infixe_ops
1491 function calculer_critere_infixe_ops($idb, &$boucles, $crit){
1492 // cas d'une valeur comparee a elle-meme ou son referent
1493 if (count($crit->param)==0){
1494 $op = '=';
1495 $col = $val = $crit->op;
1496 if (preg_match('/^(.*)\.(.*)$/', $col, $r)) $val = $r[2];
1497 // Cas special {lang} : aller chercher $GLOBALS['spip_lang']
1498 if ($val=='lang')
1499 $val = array(kwote('$GLOBALS[\'spip_lang\']'));
1500 else {
1501 $defaut = null;
1502 if ($val=='id_parent') {
1503 // Si id_parent, comparer l'id_parent avec l'id_objet
1504 // de la boucle superieure.... faudrait verifier qu'il existe
1505 // pour eviter l'erreur SQL
1506 $val = $boucles[$idb]->primary;
1507 // mais si pas de boucle superieure, prendre id_parent dans l'env
1508 $defaut = "\$Pile[0]['id_parent']";
1509 }
1510 elseif ($val=='id_enfant'){
1511 // Si id_enfant, comparer l'id_objet avec l'id_parent
1512 // de la boucle superieure
1513 $val = 'id_parent';
1514 }
1515 elseif ($crit->cond AND ($col=="date" OR $col=="date_redac")){
1516 // un critere conditionnel sur date est traite a part
1517 // car la date est mise d'office par SPIP,
1518 $defaut = "(\$Pile[0]['{$col}_default']?'':\$Pile[0]['".$col."'])";
1519 }
1520
1521 $val = calculer_argument_precedent($idb, $val, $boucles, $defaut);
1522 $val = array(kwote($val));
1523 }
1524 } else {
1525 // comparaison explicite
1526 // le phraseur impose que le premier param soit du texte
1527 $params = $crit->param;
1528 $op = $crit->op;
1529 if ($op=='==') $op = 'REGEXP';
1530 $col = array_shift($params);
1531 $col = $col[0]->texte;
1532
1533 $val = array();
1534 $desc = array('id_mere' => $idb);
1535 $parent = $boucles[$idb]->id_parent;
1536
1537 // Dans le cas {x=='#DATE'} etc, defaire le travail du phraseur,
1538 // celui ne sachant pas ce qu'est un critere infixe
1539 // et a fortiori son 2e operande qu'entoure " ou '
1540 if (count($params)==1
1541 AND count($params[0]==3)
1542 AND $params[0][0]->type=='texte'
1543 AND @$params[0][2]->type=='texte'
1544 AND ($p = $params[0][0]->texte)==$params[0][2]->texte
1545 AND (($p=="'") OR ($p=='"'))
1546 AND $params[0][1]->type=='champ'
1547 ){
1548 $val[] = "$p\\$p#".$params[0][1]->nom_champ."\\$p$p";
1549 } else
1550 foreach ((($op!='IN') ? $params : calculer_vieux_in($params)) as $p){
1551 $a = calculer_liste($p, $desc, $boucles, $parent);
1552 if (strcasecmp($op,'IN')==0) $val[] = $a;
1553 else $val[] = kwote($a, $boucles[$idb]->sql_serveur, 'char'); // toujours quoter en char ici
1554 }
1555 }
1556
1557 $fct = $args_sql = '';
1558 // fonction SQL ?
1559 if (preg_match('/^(.*)'.SQL_ARGS.'$/', $col, $m)){
1560 $fct = $m[1];
1561 preg_match('/^\(([^,]*)(.*)\)$/', $m[2], $a);
1562 $col = $a[1];
1563 if (preg_match('/^(\S*)(\s+AS\s+.*)$/i', $col, $m)){
1564 $col = $m[1];
1565 $args_sql = $m[2];
1566 }
1567 $args_sql .= $a[2];
1568 ;
1569 }
1570
1571 return array($fct, $col, $op, $val, $args_sql);
1572 }
1573
1574 // compatibilite ancienne version
1575
1576 // http://doc.spip.org/@calculer_vieux_in
1577 function calculer_vieux_in($params){
1578 $deb = $params[0][0];
1579 $k = count($params)-1;
1580 $last = $params[$k];
1581 $j = count($last)-1;
1582 $last = $last[$j];
1583 $n = isset($last->texte) ? strlen($last->texte) : 0;
1584
1585 if (!((isset($deb->texte[0]) AND $deb->texte[0]=='(')
1586 && (isset($last->texte[$n-1]) AND $last->texte[$n-1]==')')))
1587 return $params;
1588 $params[0][0]->texte = substr($deb->texte, 1);
1589 // attention, on peut avoir k=0,j=0 ==> recalculer
1590 $last = $params[$k][$j];
1591 $n = strlen($last->texte);
1592 $params[$k][$j]->texte = substr($last->texte, 0, $n-1);
1593 $newp = array();
1594 foreach ($params as $v){
1595 if ($v[0]->type!='texte')
1596 $newp[] = $v;
1597 else {
1598 foreach (explode(',', $v[0]->texte) as $x){
1599 $t = new Texte;
1600 $t->texte = $x;
1601 $newp[] = array($t);
1602 }
1603 }
1604 }
1605 return $newp;
1606 }
1607
1608 // http://doc.spip.org/@calculer_critere_infixe_date
1609 function calculer_critere_infixe_date($idb, &$boucles, $col){
1610 if (!preg_match(",^((age|jour|mois|annee)_relatif|date|mois|annee|jour|heure|age)(_[a-z]+)?$,", $col, $regs)) return '';
1611 $boucle = $boucles[$idb];
1612 $table = $boucle->show;
1613 // si c'est une colonne de la table, ne rien faire
1614 if(isset($table['field'][$col])) return '';
1615
1616 if (!$table['date'] && !isset($GLOBALS['table_date'][$table['id_table']])) return '';
1617 $pred = $date_orig = isset($GLOBALS['table_date'][$table['id_table']])? $GLOBALS['table_date'][$table['id_table']] : $table['date'];
1618 $col = $regs[1];
1619 if (isset($regs[3]) AND $suite = $regs[3]){
1620 # Recherche de l'existence du champ date_xxxx,
1621 # si oui choisir ce champ, sinon choisir xxxx
1622
1623 if (isset($table['field']["date$suite"]))
1624 $date_orig = 'date'.$suite;
1625 else
1626 $date_orig = substr($suite, 1);
1627 $pred = $date_orig;
1628 }
1629 else
1630 if (isset($regs[2]) AND $rel = $regs[2]) $pred = 'date';
1631
1632 $date_compare = "\"' . normaliser_date(".
1633 calculer_argument_precedent($idb, $pred, $boucles).
1634 ") . '\"";
1635
1636 $col_vraie = $date_orig;
1637 $date_orig = $boucle->id_table.'.'.$date_orig;
1638
1639 switch ($col) {
1640 case 'date':
1641 $col = $date_orig;
1642 break;
1643 case 'jour':
1644 $col = "DAYOFMONTH($date_orig)";
1645 break;
1646 case 'mois':
1647 $col = "MONTH($date_orig)";
1648 break;
1649 case 'annee':
1650 $col = "YEAR($date_orig)";
1651 break;
1652 case 'heure':
1653 $col = "DATE_FORMAT($date_orig, \\'%H:%i\\')";
1654 break;
1655 case 'age':
1656 $col = calculer_param_date("NOW()", $date_orig);
1657 $col_vraie = "";// comparer a un int (par defaut)
1658 break;
1659 case 'age_relatif':
1660 $col = calculer_param_date($date_compare, $date_orig);
1661 $col_vraie = "";// comparer a un int (par defaut)
1662 break;
1663 case 'jour_relatif':
1664 $col = "(TO_DAYS(".$date_compare.")-TO_DAYS(".$date_orig."))";
1665 $col_vraie = "";// comparer a un int (par defaut)
1666 break;
1667 case 'mois_relatif':
1668 $col = "MONTH(".$date_compare.")-MONTH(".
1669 $date_orig.")+12*(YEAR(".$date_compare.
1670 ")-YEAR(".$date_orig."))";
1671 $col_vraie = "";// comparer a un int (par defaut)
1672 break;
1673 case 'annee_relatif':
1674 $col = "YEAR(".$date_compare.")-YEAR(".
1675 $date_orig.")";
1676 $col_vraie = "";// comparer a un int (par defaut)
1677 break;
1678 }
1679 return array($col,$col_vraie);
1680 }
1681
1682 // http://doc.spip.org/@calculer_param_date
1683 function calculer_param_date($date_compare, $date_orig){
1684 if (preg_match(",'\" *\.(.*)\. *\"',", $date_compare, $r)){
1685 $init = "'\" . (\$x = $r[1]) . \"'";
1686 $date_compare = '\'$x\'';
1687 }
1688 else
1689 $init = $date_compare;
1690
1691 return
1692 "LEAST((UNIX_TIMESTAMP(".
1693 $init.
1694 ")-UNIX_TIMESTAMP(".
1695 $date_orig.
1696 "))/86400,\n\tTO_DAYS(".
1697 $date_compare.
1698 ")-TO_DAYS(".
1699 $date_orig.
1700 "),\n\tDAYOFMONTH(".
1701 $date_compare.
1702 ")-DAYOFMONTH(".
1703 $date_orig.
1704 ")+30.4368*(MONTH(".
1705 $date_compare.
1706 ")-MONTH(".
1707 $date_orig.
1708 "))+365.2422*(YEAR(".
1709 $date_compare.
1710 ")-YEAR(".
1711 $date_orig.
1712 ")))";
1713 }
1714
1715 /**
1716 * (DATA){source mode, "xxxxxx", arg, arg, arg}
1717 * @param string $idb
1718 * @param object $boucles
1719 * @param object $crit
1720 */
1721 function critere_DATA_source_dist($idb, &$boucles, $crit){
1722 $boucle = &$boucles[$idb];
1723
1724 $args = array();
1725 foreach ($crit->param as &$param)
1726 array_push($args,
1727 calculer_liste($param, array(), $boucles, $boucles[$idb]->id_parent));
1728
1729 $boucle->hash .= '
1730 $command[\'sourcemode\'] = '.array_shift($args).";\n";
1731
1732 $boucle->hash .= '
1733 $command[\'source\'] = array('.join(', ', $args).");\n";
1734 }
1735
1736
1737 /**
1738 * (DATA){datasource "xxxxxx", mode} <= deprecated
1739 * @param string $idb
1740 * @param object $boucles
1741 * @param object $crit
1742 */
1743 function critere_DATA_datasource_dist($idb, &$boucles, $crit){
1744 $boucle = &$boucles[$idb];
1745 $boucle->hash .= '
1746 $command[\'source\'] = array('.calculer_liste($crit->param[0], array(), $boucles, $boucles[$idb]->id_parent).');
1747 $command[\'sourcemode\'] = '.calculer_liste($crit->param[1], array(), $boucles, $boucles[$idb]->id_parent).';';
1748 }
1749
1750
1751 /**
1752 * (DATA){datacache}
1753 * @param string $idb
1754 * @param object $boucles
1755 * @param object $crit
1756 */
1757 function critere_DATA_datacache_dist($idb, &$boucles, $crit){
1758 $boucle = &$boucles[$idb];
1759 $boucle->hash .= '
1760 $command[\'datacache\'] = '.calculer_liste($crit->param[0], array(), $boucles, $boucles[$idb]->id_parent).';';
1761 }
1762
1763
1764 /**
1765 * Pour passer des arguments a un iterateur non-spip
1766 * (php:xxxIterator){args argument1, argument2, argument3}
1767 *
1768 * @param string $idb
1769 * @param object $boucles
1770 * @param object $crit
1771 */
1772 function critere_php_args_dist($idb, &$boucles, $crit){
1773 $boucle = &$boucles[$idb];
1774 $boucle->hash .= '$command[\'args\']=array();';
1775 foreach ($crit->param as $param){
1776 $boucle->hash .= '
1777 $command[\'args\'][] = '.calculer_liste($param, array(), $boucles, $boucles[$idb]->id_parent).';';
1778 }
1779 }
1780
1781 /**
1782 * Passer une liste de donnees a l'iterateur DATA
1783 * (DATA){liste X1, X2, X3}
1784 *
1785 * @param string $idb
1786 * @param object $boucles
1787 * @param object $crit
1788 */
1789 function critere_DATA_liste_dist($idb, &$boucles, $crit){
1790 $boucle = &$boucles[$idb];
1791 $boucle->hash .= "\n\t".'$command[\'liste\'] = array();'."\n";
1792 foreach ($crit->param as $param){
1793 $boucle->hash .= "\t".'$command[\'liste\'][] = '.calculer_liste($param, array(), $boucles, $boucles[$idb]->id_parent).";\n";
1794 }
1795 }
1796
1797 /**
1798 * Passer un enum min max a l'iterateur DATA
1799 * (DATA){enum Xmin, Xmax}
1800 *
1801 * @param string $idb
1802 * @param object $boucles
1803 * @param object $crit
1804 */
1805 function critere_DATA_enum_dist($idb, &$boucles, $crit){
1806 $boucle = &$boucles[$idb];
1807 $boucle->hash .= "\n\t".'$command[\'enum\'] = array();'."\n";
1808 foreach ($crit->param as $param){
1809 $boucle->hash .= "\t".'$command[\'enum\'][] = '.calculer_liste($param, array(), $boucles, $boucles[$idb]->id_parent).";\n";
1810 }
1811 }
1812
1813 /**
1814 * Extraire un chemin d'un tableau de donnees
1815 * (DATA){datapath query.results}
1816 *
1817 * @param string $idb
1818 * @param object $boucles
1819 * @param object $crit
1820 */
1821 function critere_DATA_datapath_dist($idb, &$boucles, $crit){
1822 $boucle = &$boucles[$idb];
1823 foreach ($crit->param as $param){
1824 $boucle->hash .= '
1825 $command[\'datapath\'][] = '.calculer_liste($param, array(), $boucles, $boucles[$idb]->id_parent).';';
1826 }
1827 }
1828
1829
1830 /**
1831 * le critere {si ...} applicable a toutes les boucles
1832 *
1833 * @param string $idb
1834 * @param object $boucles
1835 * @param object $crit
1836 */
1837 function critere_si_dist($idb, &$boucles, $crit){
1838 $boucle = &$boucles[$idb];
1839 // il faut initialiser 1 fois le tableau a chaque appel de la boucle
1840 // (par exemple lorsque notre boucle est appelee dans une autre boucle)
1841 // mais ne pas l'initialiser n fois si il y a n criteres {si } dans la boucle !
1842 $boucle->hash .= "\n\tif (!isset(\$si_init)) { \$command['si'] = array(); \$si_init = true; }\n";
1843 if ($crit->param){
1844 foreach ($crit->param as $param){
1845 $boucle->hash .= "\t\$command['si'][] = "
1846 . calculer_liste($param, array(), $boucles, $boucles[$idb]->id_parent) . ";\n";
1847 }
1848 // interdire {si 0} aussi !
1849 } else {
1850 $boucle->hash .= '$command[\'si\'][] = 0;';
1851 }
1852 }
1853
1854 /**
1855 * {tableau #XX} pour compatibilite ascendante boucle POUR
1856 * ... preferer la notation {datasource #XX,table}
1857 *
1858 * @param string $idb
1859 * @param object $boucles
1860 * @param object $crit
1861 */
1862 function critere_POUR_tableau_dist($idb, &$boucles, $crit){
1863 $boucle = &$boucles[$idb];
1864 $boucle->hash .= '
1865 $command[\'source\'] = array('.calculer_liste($crit->param[0], array(), $boucles, $boucles[$idb]->id_parent).');
1866 $command[\'sourcemode\'] = \'table\';';
1867 }
1868
1869
1870 /**
1871 * Trouver toutes les objets qui ont des enfants (les noeuds de l'arbre)
1872 * {noeud}
1873 * {!noeud} retourne les feuilles
1874 *
1875 * @global array $exceptions_des_tables
1876 * @param string $idb
1877 * @param array $boucles
1878 * @param Object $crit
1879 */
1880 function critere_noeud_dist($idb, &$boucles, $crit){
1881 global $exceptions_des_tables;
1882 $not = $crit->not;
1883 $boucle = &$boucles[$idb];
1884 $primary = $boucle->primary;
1885
1886 if (!$primary OR strpos($primary, ',')){
1887 erreur_squelette(_T('zbug_doublon_sur_table_sans_cle_primaire'), $boucle);
1888 return;
1889 }
1890 $table = $boucle->type_requete;
1891 $table_sql = table_objet_sql(objet_type($table));
1892
1893 $id_parent = isset($exceptions_des_tables[$boucle->id_table]['id_parent']) ?
1894 $exceptions_des_tables[$boucle->id_table]['id_parent'] :
1895 'id_parent';
1896
1897 $in = "IN";
1898 $where = array("'IN'", "'$boucle->id_table."."$primary'", "'('.sql_get_select('$id_parent', '$table_sql').')'");
1899 if ($not)
1900 $where = array("'NOT'", $where);
1901
1902 $boucle->where[] = $where;
1903 }
1904
1905 /**
1906 * Trouver toutes les objets qui n'ont pas d'enfants (les feuilles de l'arbre)
1907 * {feuille}
1908 * {!feuille} retourne les noeuds
1909 *
1910 * @global array $exceptions_des_tables
1911 * @param string $idb
1912 * @param array $boucles
1913 * @param Object $crit
1914 */
1915 function critere_feuille_dist($idb, &$boucles, $crit){
1916 $not = $crit->not;
1917 $crit->not = $not ? false : true;
1918 critere_noeud_dist($idb, $boucles, $crit);
1919 $crit->not = $not;
1920 }
1921
1922 ?>