3 /***************************************************************************\
4 * SPIP, Systeme de publication pour l'internet *
6 * Copyright (c) 2001-2014 *
7 * Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James *
9 * Ce programme est un logiciel libre distribue sous licence GNU/GPL. *
10 * Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne. *
11 \***************************************************************************/
14 * Définition des {criteres} d'une boucle
16 * @package SPIP\Compilateur\Criteres
19 if (!defined('_ECRIRE_INC_VERSION')) return;
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'
26 define('_CODE_QUOTE', ",^(\n//[^\n]*\n)? *'(.*)' *$,");
31 * Compile le critère {racine}
33 * Ce critère sélectionne les éléments à la racine d'une hiérarchie,
34 * c'est à dire ayant id_parent=0
36 * @link http://www.spip.net/@racine
39 * Identifiant de la boucle
40 * @param array $boucles
43 * Paramètres du critère dans cette boucle
45 * AST complété de la gestion du critère
47 function critere_racine_dist($idb, &$boucles, $crit){
48 global $exceptions_des_tables;
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'] :
55 $c = array("'='", "'$boucle->id_table."."$id_parent'", 0);
56 $boucle->where
[] = ($crit->not ?
array("'NOT'", $c) : $c);
61 * Compile le critère {exclus}
63 * Exclut du résultat l’élément dans lequel on se trouve déjà
65 * @link http://www.spip.net/@exclus
68 * Identifiant de la boucle
69 * @param array $boucles
72 * Paramètres du critère dans cette boucle
74 * AST complété de la gestion du critère
76 function critere_exclus_dist($idb, &$boucles, $crit){
78 $boucle = &$boucles[$idb];
79 $id = $boucle->primary
;
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);
89 * Compile le critère {doublons} ou {unique}
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.
94 * Il est possible de spécifier un nom au doublon tel que {doublons sommaire}
96 * @link http://www.spip.net/@doublons
99 * Identifiant de la boucle
100 * @param array $boucles
103 * Paramètres du critère dans cette boucle
105 * AST complété de la gestion du critère
107 function critere_doublons_dist($idb, &$boucles, $crit){
108 $boucle = &$boucles[$idb];
109 $primary = $boucle->primary
;
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'));
116 $not = ($crit->not ?
'' : 'NOT');
118 // le doublon s'applique sur un type de boucle (article)
119 $nom = "'" . $boucle->type_requete
. "'";
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
);
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";
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
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
."[]= "));
141 // le debut complet du code des doublons
142 $debut_doub = $debut_in . $debut_doub;
144 // nom du doublon "('article' . 'nom')]"
145 $fin_doub = "($nom)]";
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);
157 substr($boucle->hash
, 0, $x +
$len) . $init_code . substr($boucle->hash
, $x +
$len);
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."')");
165 // déclarer le doublon s'il n'existe pas encore
166 $boucle->hash
.= $init_comment . $init_code;
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 = "";
178 * Compile le critère {lang_select}
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
185 * Sans définir de valeur au critère, celui-ci utilise 'oui' comme
189 * Identifiant de la boucle
190 * @param array $boucles
193 * Paramètres du critère dans cette boucle
195 * AST complété de la gestion du critère
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;
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
;
210 $deux = $deux[0]->texte
;
212 $boucles[$idb]->limit
= 'intval($Pile[0]["debut'.
217 } else calculer_critere_DEFAUT_dist($idb, $boucles, $crit);
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){
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
);
233 if (!preg_match(_CODE_QUOTE
, $pas, $r)){
234 $pas = "((\$a = intval($pas)) ? \$a : 10)";
237 $pas = strval($r ?
$r : 10);
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));
244 $boucle->modificateur
['debut_nom'] = $type;
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"
252 ."\t".'$debut_boucle = intval($debut_boucle)';
255 $command[\'pagination\'] = array((isset($Pile[0]['.$debut.']) ? $Pile[0]['.$debut.'] : null), ' . $pas . ');';
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
;
264 AND !preg_match('/[,\s]/', $boucle->primary
)
265 AND !in_array($t, $boucle->select
)
267 $boucle->select
[] = $t;
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){
276 $boucle = &$boucles[$idb];
278 if (isset($crit->param
[0]))
279 $quoi = calculer_liste($crit->param
[0], array(), $boucles, $boucles[$idb]->id_parent
);
281 $quoi = '(isset($Pile[0]["recherche"])?$Pile[0]["recherche"]:(isset($GLOBALS["recherche"])?$GLOBALS["recherche"]:""))';
283 $_modificateur = var_export($boucle->modificateur
, true);
287 if (!strlen('.$quoi.')){
288 list($rech_select, $rech_where) = array("0 as points","");
291 $prepare_recherche = charger_fonction(\'prepare_recherche\', \'inc\');
292 list($rech_select, $rech_where) = $prepare_recherche('.$quoi.', "'.$boucle->id_table
.'", "'.$crit->cond
.'","'.$boucle->sql_serveur
.'",'.$_modificateur.',"'.$boucle->primary
.'");
296 $t = $boucle->id_table
.'.'.$boucle->primary
;
297 if (!in_array($t, $boucles[$idb]->select
))
298 $boucle->select
[] = $t; # pour postgres, neuneu ici
299 // jointure uniquement sur le serveur principal
300 // (on ne peut joindre une table d'un serveur distant avec la table des resultats du serveur principal)
301 if (!$boucle->sql_serveur
){
302 $boucle->join
['resultats'] = array("'".$boucle->id_table
."'", "'id'", "'".$boucle->primary
."'");
303 $boucle->from
['resultats'] = 'spip_resultats';
305 $boucle->select
[] = '$rech_select';
306 //$boucle->where[]= "\$rech_where?'resultats.id=".$boucle->id_table.".".$boucle->primary."':''";
308 // et la recherche trouve
309 $boucle->where
[] = '$rech_where?$rech_where:\'\'';
313 // http://www.spip.net/@traduction
314 // (id_trad>0 AND id_trad=id_trad(precedent))
315 // OR id_article=id_article(precedent)
316 // http://doc.spip.org/@critere_traduction_dist
317 function critere_traduction_dist($idb, &$boucles, $crit){
318 $boucle = &$boucles[$idb];
319 $prim = $boucle->primary
;
320 $table = $boucle->id_table
;
321 $arg = kwote(calculer_argument_precedent($idb, 'id_trad', $boucles));
322 $dprim = kwote(calculer_argument_precedent($idb, $prim, $boucles));
326 array("'='", "'$table.id_trad'", 0),
327 array("'='", "'$table.$prim'", $dprim)
330 array("'>'", "'$table.id_trad'", 0),
331 array("'='", "'$table.id_trad'", $arg)
336 // {origine_traduction}
337 // (id_trad>0 AND id_article=id_trad) OR (id_trad=0)
338 // http://www.spip.net/@origine_traduction
339 // http://doc.spip.org/@critere_origine_traduction_dist
340 function critere_origine_traduction_dist($idb, &$boucles, $crit){
341 $boucle = &$boucles[$idb];
342 $prim = $boucle->primary
;
343 $table = $boucle->id_table
;
347 array("'='", "'$table."."id_trad'", "'$table.$prim'"),
348 array("'='", "'$table.id_trad'", "'0'")
350 $boucle->where
[] = ($crit->not ?
array("'NOT'", $c) : $c);
354 // http://www.spip.net/@meme_parent
355 // http://doc.spip.org/@critere_meme_parent_dist
356 function critere_meme_parent_dist($idb, &$boucles, $crit){
357 global $exceptions_des_tables;
358 $boucle = &$boucles[$idb];
359 $arg = kwote(calculer_argument_precedent($idb, 'id_parent', $boucles));
360 $id_parent = isset($exceptions_des_tables[$boucle->id_table
]['id_parent']) ?
361 $exceptions_des_tables[$boucle->id_table
]['id_parent'] :
363 $mparent = $boucle->id_table
.'.'.$id_parent;
365 if ($boucle->type_requete
=='rubriques' OR isset($exceptions_des_tables[$boucle->id_table
]['id_parent'])){
366 $boucle->where
[] = array("'='", "'$mparent'", $arg);
369 // le cas FORUMS est gere dans le plugin forum, dans la fonction critere_FORUMS_meme_parent_dist()
371 return (array('zbug_critere_inconnu', array('critere' => $crit->op
.' '.$boucle->type_requete
)));
377 * Sélectionne dans une boucle les éléments appartenant à une branche d'une rubrique
379 * Calcule une branche d'une rubrique et conditionne la boucle avec.
380 * Cherche l'identifiant de la rubrique dans les boucles parentes ou par jointure
381 * et calcule la liste des identifiants de rubrique de toute la branche
383 * @link http://www.spip.net/@branche
386 * Identifiant de la boucle
387 * @param array $boucles
390 * Paramètres du critère dans cette boucle
392 * AST complété de la condition where au niveau de la boucle,
393 * restreignant celle ci aux rubriques de la branche
395 function critere_branche_dist($idb, &$boucles, $crit){
398 $boucle = &$boucles[$idb];
399 $arg = calculer_argument_precedent($idb, 'id_rubrique', $boucles);
401 //Trouver une jointure
402 $champ = "id_rubrique";
403 $desc = $boucle->show
;
404 //Seulement si necessaire
405 if (!array_key_exists($champ, $desc['field'])){
406 $cle = trouver_jointure_champ($champ, $boucle);
407 $trouver_table = charger_fonction("trouver_table", "base");
408 $desc = $trouver_table($boucle->from
[$cle]);
409 if (count(trouver_champs_decomposes($champ, $desc))>1){
410 $decompose = decompose_champ_id_objet($champ);
411 $champ = array_shift($decompose);
412 $boucle->where
[] = array("'='", _q($cle.".".reset($decompose)), '"'.sql_quote(end($decompose)).'"');
415 else $cle = $boucle->id_table
;
417 $c = "sql_in('$cle".".$champ', calcul_branche_in($arg)"
418 .($not ?
", 'NOT'" : '').")";
419 $boucle->where
[] = !$crit->cond ?
$c :
420 ("($arg ? $c : ".($not ?
"'0=1'" : "'1=1'").')');
423 // {logo} liste les objets qui ont un logo
424 // http://doc.spip.org/@critere_logo_dist
425 function critere_logo_dist($idb, &$boucles, $crit){
428 $boucle = &$boucles[$idb];
431 $boucle->id_table
.'.'.$boucle->primary
432 ."', lister_objets_avec_logos('".$boucle->primary
."'), '')";
434 if ($crit->cond
) $c = "($arg ? $c : 1)";
437 $boucle->where
[] = array("'NOT'", $c);
439 $boucle->where
[] = $c;
442 // c'est la commande SQL "GROUP BY"
443 // par exemple <boucle(articles){fusion lang}>
444 // http://doc.spip.org/@critere_fusion_dist
445 function critere_fusion_dist($idb, &$boucles, $crit){
446 if ($t = isset($crit->param
[0])){
447 $t = $crit->param
[0];
448 if ($t[0]->type
=='texte'){
450 if (preg_match("/^(.*)\.(.*)$/", $t, $r)){
451 $t = table_objet_sql($r[1]);
452 $t = array_search($t, $boucles[$idb]->from
);
453 if ($t) $t .= '.'.$r[2];
457 .calculer_critere_arg_dynamique($idb, $boucles, $t)
462 $boucles[$idb]->group
[] = $t;
463 if (!in_array($t, $boucles[$idb]->select
))
464 $boucles[$idb]->select
[] = $t;
466 return (array('zbug_critere_inconnu', array('critere' => $crit->op
.' ?')));
469 // c'est la commande SQL "COLLATE"
470 // qui peut etre appliquee sur les order by, group by, where like ...
471 // http://doc.spip.org/@critere_collecte_dist
472 function critere_collecte_dist($idb, &$boucles, $crit){
473 if (isset($crit->param
[0])){
474 $_coll = calculer_liste($crit->param
[0], array(), $boucles, $boucles[$idb]->id_parent
);
475 $boucle = $boucles[$idb];
476 $boucle->modificateur
['collate'] = "($_coll ?' COLLATE '.$_coll:'')";
477 $n = count($boucle->order
);
478 if ($n && (strpos($boucle->order
[$n-1], 'COLLATE')===false))
479 $boucle->order
[$n-1] .= " . ".$boucle->modificateur
['collate'];
481 return (array('zbug_critere_inconnu', array('critere' => $crit->op
." ".count($boucles[$idb]->order
))));
484 // http://doc.spip.org/@calculer_critere_arg_dynamique
485 function calculer_critere_arg_dynamique($idb, &$boucles, $crit, $suffix = ''){
486 $boucle = $boucles[$idb];
487 $alt = "('".$boucle->id_table
.'.\' . $x'.$suffix.')';
488 $var = '$champs_'.$idb;
489 $desc = (strpos($boucle->in
, "static $var =")!==false);
491 $desc = $boucle->show
['field'];
492 $desc = implode(',', array_map('_q', array_keys($desc)));
493 $boucles[$idb]->in
.= "\n\tstatic $var = array(".$desc.");";
495 if ($desc) $alt = "(in_array(\$x, $var) ? $alt :(\$x$suffix))";
496 $arg = calculer_liste($crit, array(), $boucles, $boucle->id_parent
);
497 return "((\$x = preg_replace(\"/\\W/\",'', $arg)) ? $alt : '')";
501 // http://www.spip.net/@par
502 // http://doc.spip.org/@critere_par_dist
503 function critere_par_dist($idb, &$boucles, $crit){
504 return critere_parinverse($idb, $boucles, $crit);
507 // http://doc.spip.org/@critere_parinverse
508 function critere_parinverse($idb, &$boucles, $crit, $sens = ''){
509 global $exceptions_des_jointures;
510 $boucle = &$boucles[$idb];
511 if ($crit->not
) $sens = $sens ?
"" : " . ' DESC'";
512 $collecte = (isset($boucle->modificateur
['collecte'])) ?
" . ".$boucle->modificateur
['collecte'] : "";
514 foreach ($crit->param
as $tri){
516 $order = $fct = ""; // en cas de fonction SQL
517 // tris specifies dynamiquement
518 if ($tri[0]->type
!='texte'){
519 // calculer le order dynamique qui verifie les champs
520 $order = calculer_critere_arg_dynamique($idb, $boucles, $tri, $sens);
521 // et si ce n'est fait, ajouter un champ 'hasard'
522 // pour supporter 'hasard' comme tri dynamique
524 $parha = $par." AS hasard";
525 if (!in_array($parha, $boucle->select
))
526 $boucle->select
[] = $parha;
528 $par = array_shift($tri);
531 if (preg_match(",^multi[\s]*(.*)$,", $par, $m)){
532 $champ = trim($m[1]);
533 // par multi L1.champ
534 if (strpos($champ, '.')) {
536 // par multi champ (champ sur une autre table)
537 } elseif (!array_key_exists($champ, $boucle->show
['field'])){
538 $cle = trouver_jointure_champ($champ, $boucle);
539 // par multi champ (champ dans la table en cours)
541 $cle = $boucle->id_table
;
543 if ($cle) { $cle .= '.'; }
544 $texte = $cle.$champ;
545 $boucle->select
[] = "\".sql_multi('".$texte."', \$GLOBALS['spip_lang']).\"";
547 // par num champ(, suite)
548 } else if (preg_match(",^num (.*)$,m", $par, $m)) {
549 $champ = trim($m[1]);
551 if (strpos($champ, '.')) {
553 // par num champ (champ sur une autre table)
554 } elseif (!array_key_exists($champ, $boucle->show
['field'])){
555 $cle = trouver_jointure_champ($champ, $boucle);
556 // par num champ (champ dans la table en cours)
558 $cle = $boucle->id_table
;
560 if ($cle) { $cle .= '.'; }
561 $texte = '0+'. $cle . $champ;
562 $suite = calculer_liste($tri, array(), $boucles, $boucle->id_parent
);
564 $texte = "\" . ((\$x = $suite) ? ('$texte' . \$x) : '0')"." . \"";
565 $as = 'num'.($boucle->order ?
count($boucle->order
) : "");
566 $boucle->select
[] = $texte." AS $as";
569 if (!preg_match(",^".CHAMP_SQL_PLUS_FONC
.'$,is', $par, $match)){
570 return (array('zbug_critere_inconnu', array('critere' => $crit->op
." $par")));
572 if (count($match)>2){
573 $par = substr($match[2], 1, -1);
579 $boucle->select
[] = $par." AS alea";
582 // par titre_mot ou type_mot voire d'autres
583 else if (isset($exceptions_des_jointures[$par])){
584 list($table, $champ) = $exceptions_des_jointures[$par];
585 $order = critere_par_joint($table, $champ, $boucle, $idb);
587 return (array('zbug_critere_inconnu', array('critere' => $crit->op
." $par")));
589 else if ($par=='date'
590 AND $desc = $boucle->show
594 $order = "'".$boucle->id_table
.".".$m."'";
596 // par champ. Verifier qu'ils sont presents.
597 elseif (preg_match("/^([^,]*)\.(.*)$/", $par, $r)) {
598 // cas du tri sur champ de jointure explicite
599 $t = array_search($r[1], $boucle->from
);
601 $t = trouver_jointure_champ($r[2], $boucle, array($r[1]));
604 return (array('zbug_critere_inconnu', array('critere' => $crit->op
." $par")));
605 } else $order = "'".$t.'.'.$r[2]."'";
607 $desc = $boucle->show
;
608 if ($desc['field'][$par])
609 $par = $boucle->id_table
.".".$par;
610 // sinon tant pis, ca doit etre un champ synthetise (cf points)
616 if (preg_match('/^\'([^"]*)\'$/', $order, $m)){
618 if (strpos($t, '.') AND !in_array($t, $boucle->select
)){
619 $boucle->select
[] = $t;
624 if (preg_match("/^\s*'(.*)'\s*$/", $order, $r))
625 $order = "'$fct(".$r[1].")'";
626 else $order = "'$fct(' . $order . ')'";
628 $t = $order.$collecte.$sens;
629 if (preg_match("/^(.*)'\s*\.\s*'([^']*')$/", $t, $r))
631 $boucle->order
[] = $t;
635 // http://doc.spip.org/@critere_par_joint
636 function critere_par_joint($table, $champ, &$boucle, $idb){
637 $t = array_search($table, $boucle->from
);
638 if (!$t) $t = trouver_jointure_champ($champ, $boucle);
639 return !$t ?
'' : ("'".$t.'.'.$champ."'");
643 // http://www.spip.net/@inverse
645 // http://doc.spip.org/@critere_inverse_dist
646 function critere_inverse_dist($idb, &$boucles, $crit){
648 $boucle = &$boucles[$idb];
649 // Classement par ordre inverse
651 critere_parinverse($idb, $boucles, $crit);
655 // Classement par ordre inverse fonction eventuelle de #ENV{...}
656 if (isset($crit->param
[0])){
657 $critere = calculer_liste($crit->param
[0], array(), $boucles, $boucles[$idb]->id_parent
);
658 $order = "(($critere)?' DESC':'')";
661 $n = count($boucle->order
);
663 if (isset($boucle->default_order
[0]))
664 $boucle->default_order
[0] .= ' . " DESC"';
666 $boucle->default_order
[] = ' DESC';
668 $t = $boucle->order
[$n-1]." . $order";
669 if (preg_match("/^(.*)'\s*\.\s*'([^']*')$/", $t, $r))
671 $boucle->order
[$n-1] = $t;
676 // http://doc.spip.org/@critere_agenda_dist
677 function critere_agenda_dist($idb, &$boucles, $crit){
678 $params = $crit->param
;
680 if (count($params)<1)
681 return (array('zbug_critere_inconnu', array('critere' => $crit->op
." ?")));
683 $parent = $boucles[$idb]->id_parent
;
685 // les valeurs $date et $type doivent etre connus a la compilation
686 // autrement dit ne pas etre des champs
688 $date = array_shift($params);
689 $date = $date[0]->texte
;
691 $type = array_shift($params);
692 $type = $type[0]->texte
;
694 $annee = $params ?
array_shift($params) : "";
695 $annee = "\n".'sprintf("%04d", ($x = '.
696 calculer_liste($annee, array(), $boucles, $parent).
697 ') ? $x : date("Y"))';
699 $mois = $params ?
array_shift($params) : "";
700 $mois = "\n".'sprintf("%02d", ($x = '.
701 calculer_liste($mois, array(), $boucles, $parent).
702 ') ? $x : date("m"))';
704 $jour = $params ?
array_shift($params) : "";
705 $jour = "\n".'sprintf("%02d", ($x = '.
706 calculer_liste($jour, array(), $boucles, $parent).
707 ') ? $x : date("d"))';
709 $annee2 = $params ?
array_shift($params) : "";
710 $annee2 = "\n".'sprintf("%04d", ($x = '.
711 calculer_liste($annee2, array(), $boucles, $parent).
712 ') ? $x : date("Y"))';
714 $mois2 = $params ?
array_shift($params) : "";
715 $mois2 = "\n".'sprintf("%02d", ($x = '.
716 calculer_liste($mois2, array(), $boucles, $parent).
717 ') ? $x : date("m"))';
719 $jour2 = $params ?
array_shift($params) : "";
720 $jour2 = "\n".'sprintf("%02d", ($x = '.
721 calculer_liste($jour2, array(), $boucles, $parent).
722 ') ? $x : date("d"))';
724 $boucle = &$boucles[$idb];
725 $date = $boucle->id_table
.".$date";
727 $quote_end = ",'".$boucle->sql_serveur
."','text'";
729 $boucle->where
[] = array("'='", "'DATE_FORMAT($date, \'%Y%m%d\')'",
730 ("sql_quote($annee . $mois . $jour$quote_end)"));
731 elseif ($type=='mois')
732 $boucle->where
[] = array("'='", "'DATE_FORMAT($date, \'%Y%m\')'",
733 ("sql_quote($annee . $mois$quote_end)"));
734 elseif ($type=='semaine')
735 $boucle->where
[] = array("'AND'",
737 "'DATE_FORMAT($date, \'%Y%m%d\')'",
738 ("date_debut_semaine($annee, $mois, $jour)")),
740 "'DATE_FORMAT($date, \'%Y%m%d\')'",
741 ("date_fin_semaine($annee, $mois, $jour)")));
742 elseif (count($crit->param
)>2)
743 $boucle->where
[] = array("'AND'",
745 "'DATE_FORMAT($date, \'%Y%m%d\')'",
746 ("sql_quote($annee . $mois . $jour$quote_end)")),
747 array("'<='", "'DATE_FORMAT($date, \'%Y%m%d\')'", ("sql_quote($annee2 . $mois2 . $jour2$quote_end)")));
748 // sinon on prend tout
751 // http://doc.spip.org/@calculer_critere_parties
752 function calculer_critere_parties($idb, &$boucles, $crit){
753 $boucle = &$boucles[$idb];
754 $a1 = $crit->param
[0];
755 $a2 = $crit->param
[1];
758 list($a11, $a12) = calculer_critere_parties_aux($idb, $boucles, $a1);
759 list($a21, $a22) = calculer_critere_parties_aux($idb, $boucles, $a2);
761 if (($op==',') && (is_numeric($a11) && (is_numeric($a21)))){
762 $boucle->limit
= $a11.','.$a21;
765 $boucle->total_parties
= ($a21!='n') ?
$a21 : $a22;
766 $partie = ($a11!='n') ?
$a11 : $a12;
767 $mode = (($op=='/') ?
'/' :
768 (($a11=='n') ?
'-' : '+').(($a21=='n') ?
'-' : '+'));
769 // cas simple {0,#ENV{truc}} compilons le en LIMIT :
770 if ($a11!=='n' AND $a21!=='n' AND $mode=="++" AND $op==','){
772 (is_numeric($a11)?
"'$a11'":$a11)
774 .(is_numeric($a21)?
"'$a21'":$a21);
777 calculer_parties($boucles, $idb, $partie, $mode);
782 // Code specifique aux criteres {pagination}, {1,n} {n/m} etc
785 function calculer_parties(&$boucles, $id_boucle, $debut, $mode){
786 $total_parties = $boucles[$id_boucle]->total_parties
;
788 preg_match(",([+-/p])([+-/])?,", $mode, $regs);
789 list(, $op1, $op2) = array_pad($regs, 3, null);
790 $nombre_boucle = "\$Numrows['$id_boucle']['total']";
793 $pmoins1 = is_numeric($debut) ?
($debut-1) : "($debut-1)";
794 $totpos = is_numeric($total_parties) ?
($total_parties) :
795 "($total_parties ? $total_parties : 1)";
796 $fin = "ceil(($nombre_boucle * $debut )/$totpos) - 1";
797 $debut = !$pmoins1 ?
0 : "ceil(($nombre_boucle * $pmoins1)/$totpos);";
800 if ($op1=='-') $debut = "$nombre_boucle - $debut;";
804 $fin = '$debut_boucle + '.$nombre_boucle.' - '
805 .(is_numeric($total_parties) ?
($total_parties+
1) :
806 ($total_parties.' - 1'));
808 // {x,1} ou {pagination}
809 $fin = '$debut_boucle'
810 .(is_numeric($total_parties) ?
811 (($total_parties==1) ?
"" : (' + '.($total_parties-1))) :
812 ('+'.$total_parties.' - 1'));
815 // {pagination}, gerer le debut_xx=-1 pour tout voir
817 $debut .= ";\n \$debut_boucle = ((\$tout=(\$debut_boucle == -1))?0:(\$debut_boucle))";
818 $debut .= ";\n \$debut_boucle = max(0,min(\$debut_boucle,floor(($nombre_boucle-1)/($total_parties))*($total_parties)))";
819 $fin = "(\$tout ? $nombre_boucle : $fin)";
824 // $debut_boucle et $fin_boucle sont les indices SQL du premier
825 // et du dernier demandes dans la boucle : 0 pour le premier,
826 // n-1 pour le dernier ; donc total_boucle = 1 + debut - fin
827 // Utiliser min pour rabattre $fin_boucle sur total_boucle.
829 $boucles[$id_boucle]->mode_partie
= "\n\t"
830 .'$debut_boucle = '.$debut.";\n "
831 .'$fin_boucle = min('.$fin.", \$Numrows['$id_boucle']['total'] - 1);\n "
832 .'$Numrows[\''.$id_boucle."']['grand_total'] = \$Numrows['$id_boucle']['total'];\n "
833 .'$Numrows[\''.$id_boucle.'\']["total"] = max(0,$fin_boucle - $debut_boucle + 1);'
834 ."\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";
836 $boucles[$id_boucle]->partie
= "
837 if (\$Numrows['$id_boucle']['compteur_boucle'] <= \$debut_boucle) continue;
838 if (\$Numrows['$id_boucle']['compteur_boucle']-1 > \$fin_boucle) break;";
841 // http://doc.spip.org/@calculer_critere_parties_aux
842 function calculer_critere_parties_aux($idb, &$boucles, $param){
843 if ($param[0]->type
!='texte'){
844 $a1 = calculer_liste(array($param[0]), array('id_mere' => $idb), $boucles, $boucles[$idb]->id_parent
);
845 preg_match(',^ *(-([0-9]+))? *$,', $param[1]->texte
, $m);
846 return array("intval($a1)", ($m[2] ?
$m[2] : 0));
848 preg_match(',^ *(([0-9]+)|n) *(- *([0-9]+)? *)?$,', $param[0]->texte
, $m);
851 return array($a1, 0);
853 return array($a1, $m[4]);
854 else return array($a1,
855 calculer_liste(array($param[1]), array(), $boucles[$idb]->id_parent
, $boucles));
861 * Compile les critères d'une boucle
863 * Cette fonction d'aiguillage cherche des fonctions spécifiques déclarées
864 * pour chaque critère demandé, dans l'ordre ci-dessous :
866 * - critere_{serveur}_{table}_{critere}, sinon avec _dist
867 * - critere_{serveur}_{critere}, sinon avec _dist
868 * - critere_{table}_{critere}, sinon avec _dist
869 * - critere_{critere}, sinon avec _dist
870 * - critere_defaut, sinon avec _dist
872 * Émet une erreur de squelette si un critère retourne une erreur.
875 * Identifiant de la boucle
876 * @param array $boucles
878 * @return string|array
879 * string : Chaine vide sans erreur
880 * array : Erreur sur un des critères
882 function calculer_criteres($idb, &$boucles){
884 $boucle = $boucles[$idb];
885 $table = strtoupper($boucle->type_requete
);
886 $serveur = strtolower($boucle->sql_serveur
);
888 $defaut = charger_fonction('DEFAUT', 'calculer_critere');
889 // s'il y avait une erreur de syntaxe, propager cette info
890 if (!is_array($boucle->criteres
)) return array();
892 foreach ($boucle->criteres
as $crit){
893 $critere = $crit->op
;
894 // critere personnalise ?
897 ((!function_exists($f = "critere_".$serveur."_".$table."_".$critere))
898 AND (!function_exists($f = $f."_dist"))
899 AND (!function_exists($f = "critere_".$serveur."_".$critere))
900 AND (!function_exists($f = $f."_dist"))
903 AND (!function_exists($f = "critere_".$table."_".$critere))
904 AND (!function_exists($f = $f."_dist"))
905 AND (!function_exists($f = "critere_".$critere))
906 AND (!function_exists($f = $f."_dist"))
908 // fonction critere standard
911 // compile le critere
912 $res = $f($idb, $boucles, $crit);
914 // Gestion centralisee des erreurs pour pouvoir propager
917 erreur_squelette($msg, $boucle);
924 * Madeleine de Proust, revision MIT-1958 sqq, revision CERN-1989
925 * hum, c'est kwoi cette fonxion ? on va dire qu'elle desemberlificote les guillemets...
927 * http://doc.spip.org/@kwote
929 * @param string $lisp
930 * @param string $serveur
931 * @param string $type
934 function kwote($lisp, $serveur='', $type=''){
935 if (preg_match(_CODE_QUOTE
, $lisp, $r))
936 return $r[1]."\"".sql_quote(str_replace(array("\\'", "\\\\"), array("'", "\\"), $r[2]),$serveur,$type)."\"";
938 return "sql_quote($lisp)";
941 // Si on a une liste de valeurs dans #ENV{x}, utiliser la double etoile
942 // pour faire par exemple {id_article IN #ENV**{liste_articles}}
943 // http://doc.spip.org/@critere_IN_dist
944 function critere_IN_dist($idb, &$boucles, $crit){
945 $r = calculer_critere_infixe($idb, $boucles, $crit);
947 return (array('zbug_critere_inconnu', array('critere' => $crit->op
." ?")));
949 list($arg, $op, $val, $col, $where_complement) = $r;
951 $in = critere_IN_cas($idb, $boucles, $crit->not ?
'NOT' : ($crit->exclus ?
'exclus' : ''), $arg, $op, $val, $col);
953 // inserer la condition; exemple: {id_mot ?IN (66, 62, 64)}
956 $pred = calculer_argument_precedent($idb, $col, $boucles);
957 $where = array("'?'", $pred, $where, "''");
958 if ($where_complement) // condition annexe du type "AND (objet='article')"
959 $where_complement = array("'?'", $pred, $where_complement, "''");
962 if (!preg_match(",^L[0-9]+[.],", $arg))
963 $where = array("'NOT'", $where);
965 // un not sur un critere de jointure se traduit comme un NOT IN avec une sous requete
966 // c'est une sous requete identique a la requete principale sous la forme (SELF,$select,$where) avec $select et $where qui surchargent
967 $where = array("'NOT'", array("'IN'", "'".$boucles[$idb]->id_table
.".".$boucles[$idb]->primary
."'", array("'SELF'", "'".$boucles[$idb]->id_table
.".".$boucles[$idb]->primary
."'", $where)));
969 $boucles[$idb]->where
[] = $where;
970 if ($where_complement) // condition annexe du type "AND (objet='article')"
971 $boucles[$idb]->where
[] = $where_complement;
974 // http://doc.spip.org/@critere_IN_cas
975 function critere_IN_cas($idb, &$boucles, $crit2, $arg, $op, $val, $col){
976 static $num = array();
977 $descr = $boucles[$idb]->descr
;
978 $cpt = &$num[$descr['nom']][$descr['gram']][$idb];
981 $x = "\n\t$var = array();";
982 foreach ($val as $k => $v){
983 if (preg_match(",^(\n//.*\n)?'(.*)'$,", $v, $r)){
984 // optimiser le traitement des constantes
985 if (is_numeric($r[2]))
986 $x .= "\n\t$var"."[]= $r[2];";
988 $x .= "\n\t$var"."[]= ".sql_quote($r[2]).";";
990 // Pour permettre de passer des tableaux de valeurs
991 // on repere l'utilisation brute de #ENV**{X},
992 // c'est-a-dire sa traduction en ($PILE[0][X]).
993 // et on deballe mais en rajoutant l'anti XSS
994 $x .= "\n\tif (!(is_array(\$a = ($v))))\n\t\t$var"."[]= \$a;\n\telse $var = array_merge($var, \$a);";
998 $boucles[$idb]->in
.= $x;
1000 // inserer le tri par defaut selon les ordres du IN ...
1001 // avec une ecriture de type FIELD qui degrade les performances (du meme ordre qu'un rexgexp)
1002 // et que l'on limite donc strictement aux cas necessaires :
1003 // si ce n'est pas un !IN, et si il n'y a pas d'autre order dans la boucle
1005 $boucles[$idb]->default_order
[] = "((!sql_quote($var) OR sql_quote($var)===\"''\") ? 0 : ('FIELD($arg,' . sql_quote($var) . ')'))";
1008 return "sql_in('$arg',sql_quote($var)".($crit2=='NOT' ?
",'NOT'" : "").")";
1013 * tout simplement, pour faire le pont entre php et squelettes
1015 * @param <type> $idb
1016 * @param <type> $boucles
1017 * @param <type> $crit
1019 function critere_where_dist($idb, &$boucles, $crit){
1020 $boucle = &$boucles[$idb];
1021 if (isset($crit->param
[0]))
1022 $_where = calculer_liste($crit->param
[0], array(), $boucles, $boucle->id_parent
);
1024 $_where = '@$Pile[0]["where"]';
1027 $_where = "(($_where) ? ($_where) : '')";
1030 $_where = "array('NOT',$_where)";
1032 $boucle->where
[] = $_where;
1037 * Un critere pour gerer un champ de tri qui peut etre modifie dynamiquement
1038 * par la balise #TRI
1040 * {tri [champ_par_defaut][,sens_par_defaut][,nom_variable]}
1041 * champ_par_defaut : un champ de la table sql
1042 * sens_par_defaut : -1 ou inverse pour decroissant, 1 ou direct pour croissant
1043 * peut etre un tableau pour preciser des sens par defaut associes a chaque champ
1044 * exemple : array('titre'=>1,'date'=>-1) pour trier par defaut
1045 * les titre croissant et les dates decroissantes
1046 * dans ce cas, quand un champ est utilise pour le tri et n'est pas present dans le tableau
1047 * c'est la premiere valeur qui est utilisee
1048 * nom_variable : nom de la variable utilisee (par defaut tri_nomboucle)
1051 * {tri titre,inverse}
1053 * {tri titre,-1,truc}
1055 * le critere {tri} s'utilise conjointement avec la balise #TRI dans la meme boucle
1056 * pour generer les liens qui permettent de changer le critere de tri et le sens du tri
1058 * Exemple d'utilisation
1061 * <p>#TRI{titre,'Trier par titre'} | #TRI{date,'Trier par date'}</p>
1063 * <BOUCLE_articles(ARTICLES){tri titre}>
1064 * <li>#TITRE - [(#DATE|affdate_jourcourt)]</li>
1065 * </BOUCLE_articles>
1070 * contraitement a {par ...} {tri} ne peut prendre qu'un seul champ,
1071 * mais il peut etre complete avec {par ...} pour indiquer des criteres secondaires
1074 * {tri num titre}{par titre} permet de faire un tri sur le rang (modifiable dynamiquement)
1075 * avec un second critere sur le titre en cas d'egalite des rang
1077 * @param unknown_type $idb
1078 * @param unknown_type $boucles
1079 * @param unknown_type $crit
1081 function critere_tri_dist($idb, &$boucles, $crit){
1082 $boucle = &$boucles[$idb];
1084 // definition du champ par defaut
1085 $_champ_defaut = !isset($crit->param
[0][0]) ?
"''"
1086 : calculer_liste(array($crit->param
[0][0]), array(), $boucles, $boucle->id_parent
);
1087 $_sens_defaut = !isset($crit->param
[1][0]) ?
"1"
1088 : calculer_liste(array($crit->param
[1][0]), array(), $boucles, $boucle->id_parent
);
1089 $_variable = !isset($crit->param
[2][0]) ?
"'$idb'"
1090 : calculer_liste(array($crit->param
[2][0]), array(), $boucles, $boucle->id_parent
);
1092 $_tri = "((\$t=(isset(\$Pile[0]['tri'.$_variable]))?\$Pile[0]['tri'.$_variable]:$_champ_defaut)?tri_protege_champ(\$t):'')";
1094 $_sens_defaut = "(is_array(\$s=$_sens_defaut)?(isset(\$s[\$st=$_tri])?\$s[\$st]:reset(\$s)):\$s)";
1095 $_sens = "((intval(\$t=(isset(\$Pile[0]['sens'.$_variable]))?\$Pile[0]['sens'.$_variable]:$_sens_defaut)==-1 OR \$t=='inverse')?-1:1)";
1097 $boucle->modificateur
['tri_champ'] = $_tri;
1098 $boucle->modificateur
['tri_sens'] = $_sens;
1099 $boucle->modificateur
['tri_nom'] = $_variable;
1100 // faut il inserer un test sur l'existence de $tri parmi les champs de la table ?
1101 // evite des erreurs sql, mais peut empecher des tri sur jointure ...
1107 \$senstri = (\$senstri<0)?' DESC':'';
1110 $boucle->select
[] = "\".tri_champ_select(\$tri).\"";
1111 $boucle->order
[] = "tri_champ_order(\$tri,\$command['from']).\$senstri";
1114 # Criteres de comparaison
1116 // http://doc.spip.org/@calculer_critere_DEFAUT
1117 function calculer_critere_DEFAUT_dist($idb, &$boucles, $crit){
1118 // double cas particulier {0,1} et {1/2} repere a l'analyse lexicale
1119 if (($crit->op
==",") OR ($crit->op
=='/'))
1120 return calculer_critere_parties($idb, $boucles, $crit);
1122 $r = calculer_critere_infixe($idb, $boucles, $crit);
1124 # // on produit une erreur seulement si le critere n'a pas de '?'
1125 # if (!$crit->cond) {
1126 return (array('zbug_critere_inconnu', array('critere' => $crit->op
)));
1128 } else calculer_critere_DEFAUT_args($idb, $boucles, $crit, $r);
1131 function calculer_critere_DEFAUT_args($idb, &$boucles, $crit, $args){
1132 list($arg, $op, $val, $col, $where_complement) = $args;
1134 $where = array("'$op'", "'$arg'", $val[0]);
1136 // inserer la negation (cf !...)
1138 if ($crit->not
) $where = array("'NOT'", $where);
1140 if (!preg_match(",^L[0-9]+[.],", $arg))
1141 $where = array("'NOT'", $where);
1143 // un not sur un critere de jointure se traduit comme un NOT IN avec une sous requete
1144 // c'est une sous requete identique a la requete principale sous la forme (SELF,$select,$where) avec $select et $where qui surchargent
1145 $where = array("'NOT'", array("'IN'", "'".$boucles[$idb]->id_table
.".".$boucles[$idb]->primary
."'", array("'SELF'", "'".$boucles[$idb]->id_table
.".".$boucles[$idb]->primary
."'", $where)));
1147 // inserer la condition (cf {lang?})
1148 // traiter a part la date, elle est mise d'office par SPIP,
1150 $pred = calculer_argument_precedent($idb, $col, $boucles);
1151 if ($col=="date" OR $col=="date_redac"){
1152 if ($pred=="\$Pile[0]['".$col."']"){
1153 $pred = "(\$Pile[0]['{$col}_default']?'':$pred)";
1157 if ($op=='=' AND !$crit->not
)
1158 $where = array("'?'", "(is_array($pred))",
1159 critere_IN_cas($idb, $boucles, 'COND', $arg, $op, array($pred), $col),
1161 $where = array("'?'", "!(is_array($pred)?count($pred):strlen($pred))", "''", $where);
1162 if ($where_complement) // condition annexe du type "AND (objet='article')"
1163 $where_complement = array("'?'", "!(is_array($pred)?count($pred):strlen($pred))", "''", $where_complement);
1166 $boucles[$idb]->where
[] = $where;
1167 if ($where_complement) // condition annexe du type "AND (objet='article')"
1168 $boucles[$idb]->where
[] = $where_complement;
1171 // http://doc.spip.org/@calculer_critere_infixe
1172 function calculer_critere_infixe($idb, &$boucles, $crit){
1174 global $table_criteres_infixes;
1175 global $exceptions_des_jointures, $exceptions_des_tables;
1177 $boucle = &$boucles[$idb];
1178 $type = $boucle->type_requete
;
1179 $table = $boucle->id_table
;
1180 $desc = $boucle->show
;
1183 list($fct, $col, $op, $val, $args_sql) =
1184 calculer_critere_infixe_ops($idb, $boucles, $crit);
1187 $where_complement = false;
1189 // Cas particulier : id_enfant => utiliser la colonne id_objet
1190 if ($col=='id_enfant')
1191 $col = $boucle->primary
;
1193 // Cas particulier : id_parent => verifier les exceptions de tables
1194 if (in_array($col,array('id_parent','id_secteur'))
1195 AND isset($exceptions_des_tables[$table][$col]))
1196 $col = $exceptions_des_tables[$table][$col];
1198 // et possibilite de gerer un critere secteur sur des tables de plugins (ie forums)
1199 else if (($col=='id_secteur') AND ($critere_secteur = charger_fonction("critere_secteur_$type", "public", true))){
1200 $table = $critere_secteur($idb, $boucles, $val, $crit);
1203 // cas id_article=xx qui se mappe en id_objet=xx AND objet=article
1204 // sauf si exception declaree : sauter cette etape
1206 !isset($exceptions_des_jointures[table_objet_sql($table)][$col])
1207 AND !isset($exceptions_des_jointures[$col])
1208 AND count(trouver_champs_decomposes($col, $desc))>1
1210 $e = decompose_champ_id_objet($col);
1211 $col = array_shift($e);
1212 $where_complement = primary_doublee($e, $table);
1214 // Cas particulier : expressions de date
1215 else if ($c = calculer_critere_infixe_date($idb, $boucles, $col)){
1216 list($col,$col_vraie) = $c;
1219 else if (preg_match('/^(.*)\.(.*)$/', $col, $r)){
1220 list(, $table, $col) = $r;
1223 $trouver_table = charger_fonction('trouver_table','base');
1224 if ($desc = $trouver_table($table, $boucle->sql_serveur
)
1225 AND isset($desc['field'][$col])
1226 AND $cle = array_search($desc['table'],$boucle->from
))
1229 $table = trouver_jointure_champ($col, $boucle, array($table), ($crit->cond
OR $op!='='));
1231 #$table = calculer_critere_externe_init($boucle, array($table), $col, $desc, ($crit->cond OR $op!='='), true);
1232 if (!$table) return '';
1234 elseif (@!array_key_exists($col, $desc['field'])
1235 // Champ joker * des iterateurs DATA qui accepte tout
1236 AND @!array_key_exists('*', $desc['field'])
1238 $r = calculer_critere_infixe_externe($boucle, $crit, $op, $desc, $col, $col_alias, $table);
1240 list($col, $col_alias, $table, $where_complement, $desc) = $r;
1243 $col_vraie = ($col_vraie?
$col_vraie:$col);
1244 // Dans tous les cas,
1245 // virer les guillemets eventuels autour d'un int (qui sont refuses par certains SQL) et passer dans sql_quote avec le type si connu
1246 // et int sinon si la valeur est numerique
1247 // sinon introduire le vrai type du champ si connu dans le sql_quote (ou int NOT NULL sinon)
1248 // Ne pas utiliser intval, PHP tronquant les Bigint de SQL
1249 if ($op=='=' OR in_array($op, $table_criteres_infixes)){
1251 // defaire le quote des int et les passer dans sql_quote avec le bon type de champ si on le connait, int sinon
1252 // prendre en compte le debug ou la valeur arrive avec un commentaire PHP en debut
1253 if (preg_match(",^\\A(\s*//.*?$\s*)?\"'(-?\d+)'\"\\z,ms", $val[0], $r))
1254 $val[0] = $r[1].'"'.sql_quote($r[2],$boucle->sql_serveur
,(isset($desc['field'][$col_vraie])?
$desc['field'][$col_vraie]:'int NOT NULL')).'"';
1256 // sinon expliciter les
1257 // sql_quote(truc) en sql_quote(truc,'',type)
1258 // sql_quote(truc,serveur) en sql_quote(truc,serveur,type)
1260 // sql_quote(truc,'','varchar(10) DEFAULT \'oui\' COLLATE NOCASE')
1261 // sql_quote(truc,'','varchar')
1262 elseif (preg_match('/\Asql_quote[(](.*?)(,[^)]*?)?(,[^)]*(?:\(\d+\)[^)]*)?)?[)]\s*\z/ms', $val[0], $r)
1263 // si pas deja un type
1264 AND (!isset($r[3]) OR !$r[3])) {
1266 .((isset($r[2]) AND $r[2]) ?
$r[2] : ",''")
1267 .",'".(isset($desc['field'][$col_vraie])?
addslashes($desc['field'][$col_vraie]):'int NOT NULL')."'";
1268 $val[0] = "sql_quote($r)";
1271 // Indicateur pour permettre aux fonctionx boucle_X de modifier
1272 // leurs requetes par defaut, notamment le champ statut
1273 // Ne pas confondre champs de la table principale et des jointures
1274 if ($table===$boucle->id_table
){
1275 $boucles[$idb]->modificateur
['criteres'][$col_vraie] = true;
1276 if ($col_alias!=$col_vraie)
1277 $boucles[$idb]->modificateur
['criteres'][$col_alias] = true;
1280 // ajout pour le cas special d'une condition sur le champ statut:
1281 // il faut alors interdire a la fonction de boucle
1282 // de mettre ses propres criteres de statut
1283 // http://www.spip.net/@statut (a documenter)
1284 // garde pour compatibilite avec code des plugins anterieurs, mais redondant avec la ligne precedente
1285 if ($col=='statut') $boucles[$idb]->statut
= true;
1287 // inserer le nom de la table SQL devant le nom du champ
1290 $arg = "$table.".substr($col, 1, -1);
1291 else $arg = "$table.$col";
1294 // inserer la fonction SQL
1295 if ($fct) $arg = "$fct($arg$args_sql)";
1297 return array($arg, $op, $val, $col_alias, $where_complement);
1300 function calculer_critere_infixe_externe($boucle, $crit, $op, $desc, $col, $col_alias, $table){
1301 global $exceptions_des_jointures;
1304 $calculer_critere_externe = 'calculer_critere_externe_init';
1305 // gestion par les plugins des jointures tordues
1306 // pas automatiques mais necessaires
1307 $table_sql = table_objet_sql($table);
1308 if (isset($exceptions_des_jointures[$table_sql])
1309 AND is_array($exceptions_des_jointures[$table_sql])
1312 isset($exceptions_des_jointures[$table_sql][$col])
1314 isset($exceptions_des_jointures[$table_sql][''])
1317 $t = $exceptions_des_jointures[$table_sql];
1318 $index = isset($t[$col])
1319 ?
$t[$col] : (isset($t['']) ?
$t[''] : array());
1321 if (count($index)==3)
1322 list($t, $col, $calculer_critere_externe) = $index;
1323 elseif (count($index)==2) {
1324 list($t, $col) = $t[$col];
1326 elseif (count($index)==1) {
1327 list($calculer_critere_externe) = $index;
1331 $t = ''; // jointure non declaree. La trouver.
1333 elseif (isset($exceptions_des_jointures[$col]))
1334 list($t, $col) = $exceptions_des_jointures[$col];
1336 $t = ''; // jointure non declaree. La trouver.
1338 // ici on construit le from pour fournir $col en piochant dans les jointures
1340 // si des jointures explicites sont fournies, on cherche d'abord dans celles ci
1341 // permet de forcer une table de lien quand il y a ambiguite
1342 // <BOUCLE_(DOCUMENTS documents_liens){id_mot}>
1343 // alors que <BOUCLE_(DOCUMENTS){id_mot}> produit la meme chose que <BOUCLE_(DOCUMENTS mots_liens){id_mot}>
1345 if ($boucle->jointures_explicites
){
1346 $jointures_explicites = explode(' ', $boucle->jointures_explicites
);
1347 $table = $calculer_critere_externe($boucle, $jointures_explicites, $col, $desc, ($crit->cond
OR $op!='='), $t);
1350 // et sinon on cherche parmi toutes les jointures declarees
1352 $table = $calculer_critere_externe($boucle, $boucle->jointures
, $col, $desc, ($crit->cond
OR $op!='='), $t);
1355 if (!$table) return '';
1357 // il ne reste plus qu'a trouver le champ dans les from
1358 list($nom, $desc) = trouver_champ_exterieur($col, $boucle->from
, $boucle);
1360 if (count(trouver_champs_decomposes($col, $desc))>1){
1361 $col_alias = $col; // id_article devient juste le nom d'origine
1362 $e = decompose_champ_id_objet($col);
1363 $col = array_shift($e);
1364 $where = primary_doublee($e, $table);
1367 return array($col, $col_alias, $table, $where, $desc);
1370 // Ne pas appliquer sql_quote lors de la compilation,
1371 // car on ne connait pas le serveur SQL, donc s'il faut \' ou ''
1373 // http://doc.spip.org/@primary_doublee
1374 function primary_doublee($decompose, $table){
1375 $e1 = reset($decompose);
1376 $e2 = "sql_quote('".end($decompose)."')";
1377 return array("'='", "'$table.".$e1."'", $e2);
1381 * Champ hors table, ca ne peut etre qu'une jointure.
1382 * On cherche la table du champ et on regarde si elle est deja jointe
1383 * Si oui et qu'on y cherche un champ nouveau, pas de jointure supplementaire
1384 * Exemple: criteres {titre_mot=...}{type_mot=...}
1385 * Dans les 2 autres cas ==> jointure
1386 * (Exemple: criteres {type_mot=...}{type_mot=...} donne 2 jointures
1387 * pour selectioner ce qui a exactement ces 2 mots-cles.
1389 * http://doc.spip.org/@calculer_critere_externe_init
1396 * @param bool|string $checkarrivee
1397 * @return mixed|string
1399 function calculer_critere_externe_init(&$boucle, $joints, $col, $desc, $cond, $checkarrivee = false){
1400 // si on demande un truc du genre spip_mots
1401 // avec aussi spip_mots_liens dans les jointures dispo
1403 // il faut privilegier la jointure directe en 2 etapes spip_mots_liens, spip_mots
1405 AND is_string($checkarrivee)
1406 AND $a = table_objet($checkarrivee)
1407 AND in_array($a.'_liens', $joints)
1409 if ($res = calculer_lien_externe_init($boucle, $joints, $col, $desc, $cond, $checkarrivee)) {
1413 foreach ($joints as $joint){
1414 if ($arrivee = trouver_champ_exterieur($col, array($joint), $boucle, $checkarrivee)){
1415 $t = array_search($arrivee[0], $boucle->from
);
1416 // transformer eventuellement id_xx en (id_objet,objet)
1417 $cols = trouver_champs_decomposes($col, $arrivee[1]);
1420 foreach ($cols as $col){
1421 $c = '/\b'.$t.".$col".'\b/';
1422 if (trouver_champ($c, $boucle->where
)) $joindre = true;
1424 // mais ca peut etre dans le FIELD pour le Having
1425 $c = "/FIELD.$t".".$col,/";
1426 if (trouver_champ($c, $boucle->select
)) $joindre = true;
1429 if (!$joindre) return $t;
1431 if ($res = calculer_jointure($boucle, array($boucle->id_table
, $desc), $arrivee, $cols, $cond, 1)) {
1441 * Generer directement une jointure via une table de lien spip_xxx_liens
1442 * pour un critere {id_xxx}
1448 * @param bool $checkarrivee
1451 function calculer_lien_externe_init(&$boucle, $joints, $col, $desc, $cond, $checkarrivee = false){
1452 $primary_arrivee = id_table_objet($checkarrivee);
1454 $intermediaire = trouver_champ_exterieur($primary_arrivee, $joints, $boucle, $checkarrivee."_liens");
1455 $arrivee = trouver_champ_exterieur($col, $joints, $boucle, $checkarrivee);
1457 if (!$intermediaire OR !$arrivee) return '';
1459 $res = fabrique_jointures($boucle,
1461 array($boucle->id_table
, $intermediaire, array(id_table_objet($desc['table_objet']), 'id_objet', 'objet', $desc['type'])),
1462 array(reset($intermediaire), $arrivee, $primary_arrivee)
1464 , $cond, $desc, $boucle->id_table
, array($col));
1469 // http://doc.spip.org/@trouver_champ
1470 function trouver_champ($champ, $where){
1471 if (!is_array($where))
1472 return preg_match($champ, $where);
1474 foreach ($where as $clause){
1475 if (trouver_champ($champ, $clause)) return true;
1482 // determine l'operateur et les operandes
1484 // http://doc.spip.org/@calculer_critere_infixe_ops
1485 function calculer_critere_infixe_ops($idb, &$boucles, $crit){
1486 // cas d'une valeur comparee a elle-meme ou son referent
1487 if (count($crit->param
)==0){
1489 $col = $val = $crit->op
;
1490 if (preg_match('/^(.*)\.(.*)$/', $col, $r)) $val = $r[2];
1491 // Cas special {lang} : aller chercher $GLOBALS['spip_lang']
1493 $val = array(kwote('$GLOBALS[\'spip_lang\']'));
1496 if ($val=='id_parent') {
1497 // Si id_parent, comparer l'id_parent avec l'id_objet
1498 // de la boucle superieure.... faudrait verifier qu'il existe
1499 // pour eviter l'erreur SQL
1500 $val = $boucles[$idb]->primary
;
1501 // mais si pas de boucle superieure, prendre id_parent dans l'env
1502 $defaut = "\$Pile[0]['id_parent']";
1504 elseif ($val=='id_enfant'){
1505 // Si id_enfant, comparer l'id_objet avec l'id_parent
1506 // de la boucle superieure
1509 elseif ($crit->cond
AND ($col=="date" OR $col=="date_redac")){
1510 // un critere conditionnel sur date est traite a part
1511 // car la date est mise d'office par SPIP,
1512 $defaut = "(\$Pile[0]['{$col}_default']?'':\$Pile[0]['".$col."'])";
1515 $val = calculer_argument_precedent($idb, $val, $boucles, $defaut);
1516 $val = array(kwote($val));
1519 // comparaison explicite
1520 // le phraseur impose que le premier param soit du texte
1521 $params = $crit->param
;
1523 if ($op=='==') $op = 'REGEXP';
1524 $col = array_shift($params);
1525 $col = $col[0]->texte
;
1528 $desc = array('id_mere' => $idb);
1529 $parent = $boucles[$idb]->id_parent
;
1531 // Dans le cas {x=='#DATE'} etc, defaire le travail du phraseur,
1532 // celui ne sachant pas ce qu'est un critere infixe
1533 // et a fortiori son 2e operande qu'entoure " ou '
1534 if (count($params)==1
1535 AND count($params[0]==3)
1536 AND $params[0][0]->type
=='texte'
1537 AND @$params[0][2]->type
=='texte'
1538 AND ($p = $params[0][0]->texte
)==$params[0][2]->texte
1539 AND (($p=="'") OR ($p=='"'))
1540 AND $params[0][1]->type
=='champ'
1542 $val[] = "$p\\$p#".$params[0][1]->nom_champ
."\\$p$p";
1544 foreach ((($op!='IN') ?
$params : calculer_vieux_in($params)) as $p){
1545 $a = calculer_liste($p, $desc, $boucles, $parent);
1546 if (strcasecmp($op,'IN')==0) $val[] = $a;
1547 else $val[] = kwote($a, $boucles[$idb]->sql_serveur
, 'char'); // toujours quoter en char ici
1551 $fct = $args_sql = '';
1553 if (preg_match('/^(.*)'.SQL_ARGS
.'$/', $col, $m)){
1555 preg_match('/^\(([^,]*)(.*)\)$/', $m[2], $a);
1557 if (preg_match('/^(\S*)(\s+AS\s+.*)$/i', $col, $m)){
1565 return array($fct, $col, $op, $val, $args_sql);
1568 // compatibilite ancienne version
1570 // http://doc.spip.org/@calculer_vieux_in
1571 function calculer_vieux_in($params){
1572 $deb = $params[0][0];
1573 $k = count($params)-1;
1574 $last = $params[$k];
1575 $j = count($last)-1;
1577 $n = isset($last->texte
) ?
strlen($last->texte
) : 0;
1579 if (!((isset($deb->texte
[0]) AND $deb->texte
[0]=='(')
1580 && (isset($last->texte
[$n-1]) AND $last->texte
[$n-1]==')')))
1582 $params[0][0]->texte
= substr($deb->texte
, 1);
1583 // attention, on peut avoir k=0,j=0 ==> recalculer
1584 $last = $params[$k][$j];
1585 $n = strlen($last->texte
);
1586 $params[$k][$j]->texte
= substr($last->texte
, 0, $n-1);
1588 foreach ($params as $v){
1589 if ($v[0]->type
!='texte')
1592 foreach (explode(',', $v[0]->texte
) as $x){
1595 $newp[] = array($t);
1602 // http://doc.spip.org/@calculer_critere_infixe_date
1603 function calculer_critere_infixe_date($idb, &$boucles, $col){
1604 if (!preg_match(",^((age|jour|mois|annee)_relatif|date|mois|annee|jour|heure|age)(_[a-z]+)?$,", $col, $regs)) return '';
1605 $boucle = $boucles[$idb];
1606 $table = $boucle->show
;
1607 // si c'est une colonne de la table, ne rien faire
1608 if(isset($table['field'][$col])) return '';
1610 if (!$table['date'] && !isset($GLOBALS['table_date'][$table['id_table']])) return '';
1611 $pred = $date_orig = isset($GLOBALS['table_date'][$table['id_table']])?
$GLOBALS['table_date'][$table['id_table']] : $table['date'];
1613 if (isset($regs[3]) AND $suite = $regs[3]){
1614 # Recherche de l'existence du champ date_xxxx,
1615 # si oui choisir ce champ, sinon choisir xxxx
1617 if (isset($table['field']["date$suite"]))
1618 $date_orig = 'date'.$suite;
1620 $date_orig = substr($suite, 1);
1624 if (isset($regs[2]) AND $rel = $regs[2]) $pred = 'date';
1626 $date_compare = "\"' . normaliser_date(".
1627 calculer_argument_precedent($idb, $pred, $boucles).
1630 $col_vraie = $date_orig;
1631 $date_orig = $boucle->id_table
.'.'.$date_orig;
1638 $col = "DAYOFMONTH($date_orig)";
1641 $col = "MONTH($date_orig)";
1644 $col = "YEAR($date_orig)";
1647 $col = "DATE_FORMAT($date_orig, \\'%H:%i\\')";
1650 $col = calculer_param_date("NOW()", $date_orig);
1651 $col_vraie = "";// comparer a un int (par defaut)
1654 $col = calculer_param_date($date_compare, $date_orig);
1655 $col_vraie = "";// comparer a un int (par defaut)
1657 case 'jour_relatif':
1658 $col = "(TO_DAYS(".$date_compare.")-TO_DAYS(".$date_orig."))";
1659 $col_vraie = "";// comparer a un int (par defaut)
1661 case 'mois_relatif':
1662 $col = "MONTH(".$date_compare.")-MONTH(".
1663 $date_orig.")+12*(YEAR(".$date_compare.
1664 ")-YEAR(".$date_orig."))";
1665 $col_vraie = "";// comparer a un int (par defaut)
1667 case 'annee_relatif':
1668 $col = "YEAR(".$date_compare.")-YEAR(".
1670 $col_vraie = "";// comparer a un int (par defaut)
1673 return array($col,$col_vraie);
1676 // http://doc.spip.org/@calculer_param_date
1677 function calculer_param_date($date_compare, $date_orig){
1678 if (preg_match(",'\" *\.(.*)\. *\"',", $date_compare, $r)){
1679 $init = "'\" . (\$x = $r[1]) . \"'";
1680 $date_compare = '\'$x\'';
1683 $init = $date_compare;
1686 "LEAST((UNIX_TIMESTAMP(".
1688 ")-UNIX_TIMESTAMP(".
1690 "))/86400,\n\tTO_DAYS(".
1694 "),\n\tDAYOFMONTH(".
1698 ")+30.4368*(MONTH(".
1702 "))+365.2422*(YEAR(".
1710 * (DATA){source mode, "xxxxxx", arg, arg, arg}
1711 * @param string $idb
1712 * @param object $boucles
1713 * @param object $crit
1715 function critere_DATA_source_dist($idb, &$boucles, $crit){
1716 $boucle = &$boucles[$idb];
1719 foreach ($crit->param
as &$param)
1721 calculer_liste($param, array(), $boucles, $boucles[$idb]->id_parent
));
1724 $command[\'sourcemode\'] = '.array_shift($args).";\n";
1727 $command[\'source\'] = array('.join(', ', $args).");\n";
1732 * (DATA){datasource "xxxxxx", mode} <= deprecated
1733 * @param string $idb
1734 * @param object $boucles
1735 * @param object $crit
1737 function critere_DATA_datasource_dist($idb, &$boucles, $crit){
1738 $boucle = &$boucles[$idb];
1740 $command[\'source\'] = array('.calculer_liste($crit->param
[0], array(), $boucles, $boucles[$idb]->id_parent
).');
1741 $command[\'sourcemode\'] = '.calculer_liste($crit->param
[1], array(), $boucles, $boucles[$idb]->id_parent
).';';
1747 * @param string $idb
1748 * @param object $boucles
1749 * @param object $crit
1751 function critere_DATA_datacache_dist($idb, &$boucles, $crit){
1752 $boucle = &$boucles[$idb];
1754 $command[\'datacache\'] = '.calculer_liste($crit->param
[0], array(), $boucles, $boucles[$idb]->id_parent
).';';
1759 * Pour passer des arguments a un iterateur non-spip
1760 * (php:xxxIterator){args argument1, argument2, argument3}
1762 * @param string $idb
1763 * @param object $boucles
1764 * @param object $crit
1766 function critere_php_args_dist($idb, &$boucles, $crit){
1767 $boucle = &$boucles[$idb];
1768 $boucle->hash
.= '$command[\'args\']=array();';
1769 foreach ($crit->param
as $param){
1771 $command[\'args\'][] = '.calculer_liste($param, array(), $boucles, $boucles[$idb]->id_parent
).';';
1776 * Passer une liste de donnees a l'iterateur DATA
1777 * (DATA){liste X1, X2, X3}
1779 * @param string $idb
1780 * @param object $boucles
1781 * @param object $crit
1783 function critere_DATA_liste_dist($idb, &$boucles, $crit){
1784 $boucle = &$boucles[$idb];
1785 $boucle->hash
.= "\n\t".'$command[\'liste\'] = array();'."\n";
1786 foreach ($crit->param
as $param){
1787 $boucle->hash
.= "\t".'$command[\'liste\'][] = '.calculer_liste($param, array(), $boucles, $boucles[$idb]->id_parent
).";\n";
1792 * Passer un enum min max a l'iterateur DATA
1793 * (DATA){enum Xmin, Xmax}
1795 * @param string $idb
1796 * @param object $boucles
1797 * @param object $crit
1799 function critere_DATA_enum_dist($idb, &$boucles, $crit){
1800 $boucle = &$boucles[$idb];
1801 $boucle->hash
.= "\n\t".'$command[\'enum\'] = array();'."\n";
1802 foreach ($crit->param
as $param){
1803 $boucle->hash
.= "\t".'$command[\'enum\'][] = '.calculer_liste($param, array(), $boucles, $boucles[$idb]->id_parent
).";\n";
1808 * Extraire un chemin d'un tableau de donnees
1809 * (DATA){datapath query.results}
1811 * @param string $idb
1812 * @param object $boucles
1813 * @param object $crit
1815 function critere_DATA_datapath_dist($idb, &$boucles, $crit){
1816 $boucle = &$boucles[$idb];
1817 foreach ($crit->param
as $param){
1819 $command[\'datapath\'][] = '.calculer_liste($param, array(), $boucles, $boucles[$idb]->id_parent
).';';
1825 * le critere {si ...} applicable a toutes les boucles
1827 * @param string $idb
1828 * @param object $boucles
1829 * @param object $crit
1831 function critere_si_dist($idb, &$boucles, $crit){
1832 $boucle = &$boucles[$idb];
1833 // il faut initialiser 1 fois le tableau a chaque appel de la boucle
1834 // (par exemple lorsque notre boucle est appelee dans une autre boucle)
1835 // mais ne pas l'initialiser n fois si il y a n criteres {si } dans la boucle !
1836 $boucle->hash
.= "\n\tif (!isset(\$si_init)) { \$command['si'] = array(); \$si_init = true; }\n";
1838 foreach ($crit->param
as $param){
1839 $boucle->hash
.= "\t\$command['si'][] = "
1840 . calculer_liste($param, array(), $boucles, $boucles[$idb]->id_parent
) . ";\n";
1842 // interdire {si 0} aussi !
1844 $boucle->hash
.= '$command[\'si\'][] = 0;';
1849 * {tableau #XX} pour compatibilite ascendante boucle POUR
1850 * ... preferer la notation {datasource #XX,table}
1852 * @param string $idb
1853 * @param object $boucles
1854 * @param object $crit
1856 function critere_POUR_tableau_dist($idb, &$boucles, $crit){
1857 $boucle = &$boucles[$idb];
1859 $command[\'source\'] = array('.calculer_liste($crit->param
[0], array(), $boucles, $boucles[$idb]->id_parent
).');
1860 $command[\'sourcemode\'] = \'table\';';
1865 * Trouver toutes les objets qui ont des enfants (les noeuds de l'arbre)
1867 * {!noeud} retourne les feuilles
1869 * @global array $exceptions_des_tables
1870 * @param string $idb
1871 * @param array $boucles
1872 * @param Object $crit
1874 function critere_noeud_dist($idb, &$boucles, $crit){
1875 global $exceptions_des_tables;
1877 $boucle = &$boucles[$idb];
1878 $primary = $boucle->primary
;
1880 if (!$primary OR strpos($primary, ',')){
1881 erreur_squelette(_T('zbug_doublon_sur_table_sans_cle_primaire'), "BOUCLE$idb");
1884 $table = $boucle->type_requete
;
1885 $table_sql = table_objet_sql(objet_type($table));
1887 $id_parent = isset($exceptions_des_tables[$boucle->id_table
]['id_parent']) ?
1888 $exceptions_des_tables[$boucle->id_table
]['id_parent'] :
1892 $where = array("'IN'", "'$boucle->id_table."."$primary'", "'('.sql_get_select('$id_parent', '$table_sql').')'");
1894 $where = array("'NOT'", $where);
1896 $boucle->where
[] = $where;
1900 * Trouver toutes les objets qui n'ont pas d'enfants (les feuilles de l'arbre)
1902 * {!feuille} retourne les noeuds
1904 * @global array $exceptions_des_tables
1905 * @param string $idb
1906 * @param array $boucles
1907 * @param Object $crit
1909 function critere_feuille_dist($idb, &$boucles, $crit){
1911 $crit->not
= $not ?
false : true;
1912 critere_noeud_dist($idb, $boucles, $crit);