Completed
Push — master ( b70bf1...171d66 )
by cam
12:23
created

criteres.php ➔ critere_id__dist()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 3
dl 0
loc 17
rs 9.7
c 0
b 0
f 0
1
<?php
2
3
/***************************************************************************\
4
 *  SPIP, Systeme de publication pour l'internet                           *
5
 *                                                                         *
6
 *  Copyright (c) 2001-2019                                                *
7
 *  Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James  *
8
 *                                                                         *
9
 *  Ce programme est un logiciel libre distribue sous licence GNU/GPL.     *
10
 *  Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne.   *
11
\***************************************************************************/
12
13
/**
14
 * Définition des {criteres} d'une boucle
15
 *
16
 * @package SPIP\Core\Compilateur\Criteres
17
 **/
18
19
if (!defined('_ECRIRE_INC_VERSION')) {
20
	return;
21
}
22
23
/**
24
 * Une Regexp repérant une chaine produite par le compilateur,
25
 * souvent utilisée pour faire de la concaténation lors de la compilation
26
 * plutôt qu'à l'exécution, i.e. pour remplacer 'x'.'y' par 'xy'
27
 **/
28
define('_CODE_QUOTE', ",^(\n//[^\n]*\n)? *'(.*)' *$,");
29
30
31
/**
32
 * Compile le critère {racine}
33
 *
34
 * Ce critère sélectionne les éléments à la racine d'une hiérarchie,
35
 * c'est à dire ayant id_parent=0
36
 *
37
 * @link http://www.spip.net/@racine
38
 *
39
 * @param string $idb Identifiant de la boucle
40
 * @param array $boucles AST du squelette
41
 * @param Critere $crit Paramètres du critère dans cette boucle
42
 * @return void
43
 **/
44
function critere_racine_dist($idb, &$boucles, $crit) {
45
46
	$not = $crit->not;
0 ignored issues
show
Unused Code introduced by
$not is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
47
	$boucle = &$boucles[$idb];
48
	$id_parent = isset($GLOBALS['exceptions_des_tables'][$boucle->id_table]['id_parent']) ?
49
		$GLOBALS['exceptions_des_tables'][$boucle->id_table]['id_parent'] :
50
		'id_parent';
51
52
	$c = array("'='", "'$boucle->id_table." . "$id_parent'", 0);
53
	$boucle->where[] = ($crit->not ? array("'NOT'", $c) : $c);
54
}
55
56
57
/**
58
 * Compile le critère {exclus}
59
 *
60
 * Exclut du résultat l’élément dans lequel on se trouve déjà
61
 *
62
 * @link http://www.spip.net/@exclus
63
 *
64
 * @param string $idb Identifiant de la boucle
65
 * @param array $boucles AST du squelette
66
 * @param Critere $crit Paramètres du critère dans cette boucle
67
 * @return void
0 ignored issues
show
Documentation introduced by
Should the return type not be array<string|array<string,string>>|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
68
 **/
69
function critere_exclus_dist($idb, &$boucles, $crit) {
70
	$not = $crit->not;
71
	$boucle = &$boucles[$idb];
72
	$id = $boucle->primary;
73
74 View Code Duplication
	if ($not or !$id) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
75
		return (array('zbug_critere_inconnu', array('critere' => $not . $crit->op)));
76
	}
77
	$arg = kwote(calculer_argument_precedent($idb, $id, $boucles));
78
	$boucle->where[] = array("'!='", "'$boucle->id_table." . "$id'", $arg);
79
}
80
81
82
/**
83
 * Compile le critère {doublons} ou {unique}
84
 *
85
 * Ce critères enlève de la boucle les éléments déjà sauvegardés
86
 * dans un précédent critère {doublon} sur une boucle de même table.
87
 *
88
 * Il est possible de spécifier un nom au doublon tel que {doublons sommaire}
89
 *
90
 * @link http://www.spip.net/@doublons
91
 *
92
 * @param string $idb Identifiant de la boucle
93
 * @param array $boucles AST du squelette
94
 * @param Critere $crit Paramètres du critère dans cette boucle
95
 * @return void
0 ignored issues
show
Documentation introduced by
Should the return type not be string[]|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
96
 **/
97
function critere_doublons_dist($idb, &$boucles, $crit) {
98
	$boucle = &$boucles[$idb];
99
	$primary = $boucle->primary;
100
101
	// la table nécessite une clé primaire, non composée
102
	if (!$primary or strpos($primary, ',')) {
103
		return (array('zbug_doublon_sur_table_sans_cle_primaire'));
104
	}
105
106
	$not = ($crit->not ? '' : 'NOT');
107
108
	// le doublon s'applique sur un type de boucle (article)
109
	$nom = "'" . $boucle->type_requete . "'";
110
111
	// compléter le nom avec un nom précisé {doublons nom}
112
	// on obtient $nom = "'article' . 'nom'"
113
	if (isset($crit->param[0])) {
114
		$nom .= "." . calculer_liste($crit->param[0], array(), $boucles, $boucles[$idb]->id_parent);
115
	}
116
117
	// code qui déclarera l'index du stockage de nos doublons (pour éviter une notice PHP)
118
	$init_comment = "\n\n\t// Initialise le(s) critère(s) doublons\n";
119
	$init_code = "\tif (!isset(\$doublons[\$d = $nom])) { \$doublons[\$d] = ''; }\n";
120
121
	// on crée un sql_in avec la clé primaire de la table
122
	// et la collection des doublons déjà emmagasinés dans le tableau
123
	// $doublons et son index, ici $nom
124
125
	// debut du code "sql_in('articles.id_article', "
126
	$debut_in = "sql_in('" . $boucle->id_table . '.' . $primary . "', ";
127
	// lecture des données du doublon "$doublons[$doublon_index[] = "
128
	// Attention : boucle->doublons désigne une variable qu'on affecte
129
	$debut_doub = '$doublons[' . (!$not ? '' : ($boucle->doublons . "[]= "));
130
131
	// le debut complet du code des doublons
132
	$debut_doub = $debut_in . $debut_doub;
133
134
	// nom du doublon "('article' . 'nom')]"
135
	$fin_doub = "($nom)]";
136
137
	// si on trouve un autre critère doublon,
138
	// on fusionne pour avoir un seul IN, et on s'en va !
139
	foreach ($boucle->where as $k => $w) {
140
		if (strpos($w[0], $debut_doub) === 0) {
141
			// fusionner le sql_in (du where)
142
			$boucle->where[$k][0] = $debut_doub . $fin_doub . ' . ' . substr($w[0], strlen($debut_in));
143
			// fusionner l'initialisation (du hash) pour faire plus joli
144
			$x = strpos($boucle->hash, $init_comment);
145
			$len = strlen($init_comment);
146
			$boucle->hash =
147
				substr($boucle->hash, 0, $x + $len) . $init_code . substr($boucle->hash, $x + $len);
148
149
			return;
150
		}
151
	}
152
153
	// mettre l'ensemble dans un tableau pour que ce ne soit pas vu comme une constante
154
	$boucle->where[] = array($debut_doub . $fin_doub . ", '" . $not . "')");
155
156
	// déclarer le doublon s'il n'existe pas encore
157
	$boucle->hash .= $init_comment . $init_code;
158
159
160
	# la ligne suivante avait l'intention d'eviter une collecte deja faite
161
	# mais elle fait planter une boucle a 2 critere doublons:
162
	# {!doublons A}{doublons B}
163
	# (de http://article.gmane.org/gmane.comp.web.spip.devel/31034)
164
	#	if ($crit->not) $boucle->doublons = "";
165
}
166
167
168
/**
169
 * Compile le critère {lang_select}
170
 *
171
 * Permet de restreindre ou non une boucle en affichant uniquement
172
 * les éléments dans la langue en cours. Certaines boucles
173
 * tel que articles et rubriques restreignent par défaut sur la langue
174
 * en cours.
175
 *
176
 * Sans définir de valeur au critère, celui-ci utilise 'oui' comme
177
 * valeur par défaut.
178
 *
179
 * @param string $idb Identifiant de la boucle
180
 * @param array $boucles AST du squelette
181
 * @param Critere $crit Paramètres du critère dans cette boucle
182
 * @return void
183
 **/
184
function critere_lang_select_dist($idb, &$boucles, $crit) {
185
	if (!isset($crit->param[1][0]) or !($param = $crit->param[1][0]->texte)) {
186
		$param = 'oui';
187
	}
188
	if ($crit->not) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $crit->not of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
189
		$param = ($param == 'oui') ? 'non' : 'oui';
190
	}
191
	$boucle = &$boucles[$idb];
192
	$boucle->lang_select = $param;
193
}
194
195
196
/**
197
 * Compile le critère {debut_xxx}
198
 *
199
 * Limite le nombre d'éléments affichés.
200
 *
201
 * Ce critère permet de faire commencer la limitation des résultats
202
 * par une variable passée dans l’URL et commençant par 'debut_' tel que
203
 * {debut_page,10}. Le second paramètre est le nombre de résultats à
204
 * afficher.
205
 *
206
 * Note : il est plus simple d'utiliser le critère pagination.
207
 *
208
 * @param string $idb Identifiant de la boucle
209
 * @param array $boucles AST du squelette
210
 * @param Critere $crit Paramètres du critère dans cette boucle
211
 * @return void
212
 **/
213
function critere_debut_dist($idb, &$boucles, $crit) {
214
	list($un, $deux) = $crit->param;
215
	$un = $un[0]->texte;
216
	$deux = $deux[0]->texte;
217
	if ($deux) {
218
		$boucles[$idb]->limit = 'intval($Pile[0]["debut' .
219
			$un .
220
			'"]) . ",' .
221
			$deux .
222
			'"';
223
	} else {
224
		calculer_critere_DEFAUT_dist($idb, $boucles, $crit);
225
	}
226
}
227
228
229
/**
230
 * Compile le critère `pagination` qui demande à paginer une boucle.
231
 *
232
 * Demande à paginer la boucle pour n'afficher qu'une partie des résultats,
233
 * et gère l'affichage de la partie de page demandée par debut_xx dans
234
 * dans l'environnement du squelette.
235
 *
236
 * Le premier paramètre indique le nombre d'éléments par page, le second,
237
 * rarement utilisé permet de définir le nom de la variable désignant la
238
 * page demandée (`debut_xx`), qui par défaut utilise l'identifiant de la boucle.
239
 *
240
 * @critere
241
 * @see balise_PAGINATION_dist()
242
 * @link http://www.spip.net/3367 Le système de pagination
243
 * @link http://www.spip.net/4867 Le critère pagination
244
 * @example
245
 *     ```
246
 *     {pagination}
247
 *     {pagination 20}
248
 *     {pagination #ENV{pages,5}} etc
249
 *     {pagination 20 #ENV{truc,chose}} pour utiliser la variable debut_#ENV{truc,chose}
250
 *     ```
251
 *
252
 * @param string $idb Identifiant de la boucle
253
 * @param array $boucles AST du squelette
254
 * @param Critere $crit Paramètres du critère dans cette boucle
255
 * @return void
256
 **/
257
function critere_pagination_dist($idb, &$boucles, $crit) {
258
259
	$boucle = &$boucles[$idb];
260
	// definition de la taille de la page
261
	$pas = !isset($crit->param[0][0]) ? "''"
262
		: calculer_liste(array($crit->param[0][0]), array(), $boucles, $boucle->id_parent);
263
264
	if (!preg_match(_CODE_QUOTE, $pas, $r)) {
265
		$pas = "((\$a = intval($pas)) ? \$a : 10)";
266
	} else {
267
		$r = intval($r[2]);
268
		$pas = strval($r ? $r : 10);
269
	}
270
271
	// Calcul du nommage de la pagination si il existe.
272
	// La nouvelle syntaxe {pagination 20, nom} est prise en compte et privilégiée mais on reste
273
	// compatible avec l'ancienne car certains cas fonctionnent correctement
274
	$type = "'$idb'";
275
	// Calcul d'un nommage spécifique de la pagination si précisé.
276
	// Syntaxe {pagination 20, nom}
277
	if (isset($crit->param[0][1])) {
278
		$type = calculer_liste(array($crit->param[0][1]), array(), $boucles, $boucle->id_parent);
279
	} // Ancienne syntaxe {pagination 20 nom} pour compatibilité
280
	elseif (isset($crit->param[1][0])) {
281
		$type = calculer_liste(array($crit->param[1][0]), array(), $boucles, $boucle->id_parent);
282
	}
283
284
	$debut = ($type[0] !== "'") ? "'debut'.$type" : ("'debut" . substr($type, 1));
285
	$boucle->modificateur['debut_nom'] = $type;
286
	$partie =
287
		// tester si le numero de page demande est de la forme '@yyy'
288
		'isset($Pile[0][' . $debut . ']) ? $Pile[0][' . $debut . '] : _request(' . $debut . ");\n"
289
		. "\tif(substr(\$debut_boucle,0,1)=='@'){\n"
290
		. "\t\t" . '$debut_boucle = $Pile[0][' . $debut . '] = quete_debut_pagination(\'' . $boucle->primary . '\',$Pile[0][\'@' . $boucle->primary . '\'] = substr($debut_boucle,1),' . $pas . ',$iter);' . "\n"
291
		. "\t\t" . '$iter->seek(0);' . "\n"
292
		. "\t}\n"
293
		. "\t" . '$debut_boucle = intval($debut_boucle)';
294
295
	$boucle->hash .= '
296
	$command[\'pagination\'] = array((isset($Pile[0][' . $debut . ']) ? $Pile[0][' . $debut . '] : null), ' . $pas . ');';
297
298
	$boucle->total_parties = $pas;
299
	calculer_parties($boucles, $idb, $partie, 'p+');
300
	// ajouter la cle primaire dans le select pour pouvoir gerer la pagination referencee par @id
301
	// sauf si pas de primaire, ou si primaire composee
302
	// dans ce cas, on ne sait pas gerer une pagination indirecte
303
	$t = $boucle->id_table . '.' . $boucle->primary;
304
	if ($boucle->primary
305
		and !preg_match('/[,\s]/', $boucle->primary)
306
		and !in_array($t, $boucle->select)
307
	) {
308
		$boucle->select[] = $t;
309
	}
310
}
311
312
313
/**
314
 * Compile le critère `recherche` qui permet de sélectionner des résultats
315
 * d'une recherche.
316
 *
317
 * Le texte cherché est pris dans le premier paramètre `{recherche xx}`
318
 * ou à défaut dans la clé `recherche` de l'environnement du squelette.
319
 *
320
 * @critere
321
 * @link http://www.spip.net/3878
322
 * @see inc_prepare_recherche_dist()
323
 *
324
 * @param string $idb Identifiant de la boucle
325
 * @param array $boucles AST du squelette
326
 * @param Critere $crit Paramètres du critère dans cette boucle
327
 * @return void
328
 **/
329
function critere_recherche_dist($idb, &$boucles, $crit) {
330
331
	$boucle = &$boucles[$idb];
332
333
	if (!$boucle->primary or strpos($boucle->primary, ',')) {
334
		erreur_squelette(_T('zbug_critere_sur_table_sans_cle_primaire', array('critere' => 'recherche')), $boucle);
335
336
		return;
337
	}
338
339 View Code Duplication
	if (isset($crit->param[0])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
340
		$quoi = calculer_liste($crit->param[0], array(), $boucles, $boucles[$idb]->id_parent);
341
	} else {
342
		$quoi = '(isset($Pile[0]["recherche"])?$Pile[0]["recherche"]:(isset($GLOBALS["recherche"])?$GLOBALS["recherche"]:""))';
343
	}
344
345
	$_modificateur = var_export($boucle->modificateur, true);
346
	$boucle->hash .= '
347
	// RECHERCHE'
348
		. ($crit->cond ? '
349
	if (!strlen(' . $quoi . ')){
350
		list($rech_select, $rech_where) = array("0 as points","");
351
	} else' : '') . '
352
	{
353
		$prepare_recherche = charger_fonction(\'prepare_recherche\', \'inc\');
354
		list($rech_select, $rech_where) = $prepare_recherche(' . $quoi . ', "' . $boucle->id_table . '", "' . $crit->cond . '","' . $boucle->sql_serveur . '",' . $_modificateur . ',"' . $boucle->primary . '");
355
	}
356
	';
357
358
359
	$t = $boucle->id_table . '.' . $boucle->primary;
360
	if (!in_array($t, $boucles[$idb]->select)) {
361
		$boucle->select[] = $t;
362
	} # pour postgres, neuneu ici
363
	// jointure uniquement sur le serveur principal
364
	// (on ne peut joindre une table d'un serveur distant avec la table des resultats du serveur principal)
365
	if (!$boucle->sql_serveur) {
366
		$boucle->join['resultats'] = array("'" . $boucle->id_table . "'", "'id'", "'" . $boucle->primary . "'");
367
		$boucle->from['resultats'] = 'spip_resultats';
368
	}
369
	$boucle->select[] = '$rech_select';
370
	//$boucle->where[]= "\$rech_where?'resultats.id=".$boucle->id_table.".".$boucle->primary."':''";
371
372
	// et la recherche trouve
373
	$boucle->where[] = '$rech_where?$rech_where:\'\'';
374
}
375
376
/**
377
 * Compile le critère `traduction`
378
 *
379
 * Sélectionne toutes les traductions de l'élément courant (la boucle englobante)
380
 * en différentes langues (y compris l'élément englobant)
381
 *
382
 * Équivalent à `(id_trad>0 AND id_trad=id_trad(precedent)) OR id_xx=id_xx(precedent)`
383
 *
384
 * @param string $idb Identifiant de la boucle
385
 * @param array $boucles AST du squelette
386
 * @param Critere $crit Paramètres du critère dans cette boucle
387
 * @return void
388
 **/
389
function critere_traduction_dist($idb, &$boucles, $crit) {
0 ignored issues
show
Unused Code introduced by
The parameter $crit is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
390
	$boucle = &$boucles[$idb];
391
	$prim = $boucle->primary;
392
	$table = $boucle->id_table;
393
	$arg = kwote(calculer_argument_precedent($idb, 'id_trad', $boucles));
394
	$dprim = kwote(calculer_argument_precedent($idb, $prim, $boucles));
395
	$boucle->where[] =
396
		array(
397
			"'OR'",
398
			array(
399
				"'AND'",
400
				array("'='", "'$table.id_trad'", 0),
401
				array("'='", "'$table.$prim'", $dprim)
402
			),
403
			array(
404
				"'AND'",
405
				array("'>'", "'$table.id_trad'", 0),
406
				array("'='", "'$table.id_trad'", $arg)
407
			)
408
		);
409
}
410
411
412
/**
413
 * Compile le critère {origine_traduction}
414
 *
415
 * Sélectionne les éléments qui servent de base à des versions traduites
416
 * (par exemple les articles "originaux" sur une boucle articles)
417
 *
418
 * Équivalent à (id_trad>0 AND id_xx=id_trad) OR (id_trad=0)
419
 *
420
 * @param string $idb Identifiant de la boucle
421
 * @param array $boucles AST du squelette
422
 * @param Critere $crit Paramètres du critère dans cette boucle
423
 * @return void
424
 **/
425
function critere_origine_traduction_dist($idb, &$boucles, $crit) {
426
	$boucle = &$boucles[$idb];
427
	$prim = $boucle->primary;
428
	$table = $boucle->id_table;
429
430
	$c =
431
		array(
432
			"'OR'",
433
			array("'='", "'$table." . "id_trad'", "'$table.$prim'"),
434
			array("'='", "'$table.id_trad'", "'0'")
435
		);
436
	$boucle->where[] = ($crit->not ? array("'NOT'", $c) : $c);
437
}
438
439
440
/**
441
 * Compile le critère {meme_parent}
442
 *
443
 * Sélectionne les éléments ayant le même parent que la boucle parente,
444
 * c'est à dire les frères et sœurs.
445
 *
446
 * @param string $idb Identifiant de la boucle
447
 * @param array $boucles AST du squelette
448
 * @param Critere $crit Paramètres du critère dans cette boucle
449
 * @return void
0 ignored issues
show
Documentation introduced by
Should the return type not be null|array<string|array<string,string>>?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
450
 **/
451
function critere_meme_parent_dist($idb, &$boucles, $crit) {
452
453
	$boucle = &$boucles[$idb];
454
	$arg = kwote(calculer_argument_precedent($idb, 'id_parent', $boucles));
455
	$id_parent = isset($GLOBALS['exceptions_des_tables'][$boucle->id_table]['id_parent']) ?
456
		$GLOBALS['exceptions_des_tables'][$boucle->id_table]['id_parent'] :
457
		'id_parent';
458
	$mparent = $boucle->id_table . '.' . $id_parent;
459
460
	if ($boucle->type_requete == 'rubriques' or isset($GLOBALS['exceptions_des_tables'][$boucle->id_table]['id_parent'])) {
461
		$boucle->where[] = array("'='", "'$mparent'", $arg);
462
463
	} // le cas FORUMS est gere dans le plugin forum, dans la fonction critere_FORUMS_meme_parent_dist()
464
	else {
465
		return (array('zbug_critere_inconnu', array('critere' => $crit->op . ' ' . $boucle->type_requete)));
466
	}
467
}
468
469
470
/**
471
 * Compile le critère `branche` qui sélectionne dans une boucle les
472
 * éléments appartenant à une branche d'une rubrique.
473
 *
474
 * Cherche l'identifiant de la rubrique en premier paramètre du critère `{branche XX}`
475
 * s'il est renseigné, sinon, sans paramètre (`{branche}` tout court) dans les
476
 * boucles parentes. On calcule avec lui la liste des identifiants
477
 * de rubrique de toute la branche.
478
 *
479
 * La boucle qui possède ce critère cherche une liaison possible avec
480
 * la colonne `id_rubrique`, et tentera de trouver une jointure avec une autre
481
 * table si c'est nécessaire pour l'obtenir.
482
 * 
483
 * Ce critère peut être rendu optionnel avec `{branche ?}` en remarquant 
484
 * cependant que le test s'effectue sur la présence d'un champ 'id_rubrique'
485
 * sinon d'une valeur 'id_rubrique' dans l'environnement (et non 'branche'
486
 * donc).
487
 *
488
 * @link http://www.spip.net/@branche
489
 *
490
 * @param string $idb Identifiant de la boucle
491
 * @param array $boucles AST du squelette
492
 * @param Critere $crit Paramètres du critère dans cette boucle
493
 * @return void
494
 **/
495
function critere_branche_dist($idb, &$boucles, $crit) {
496
497
	$not = $crit->not;
498
	$boucle = &$boucles[$idb];
499
	// prendre en priorite un identifiant en parametre {branche XX}
500
	if (isset($crit->param[0])) {
501
		$arg = calculer_liste($crit->param[0], array(), $boucles, $boucles[$idb]->id_parent);
502
		// sinon on le prend chez une boucle parente
503
	} else {
504
		$arg = kwote(calculer_argument_precedent($idb, 'id_rubrique', $boucles), $boucle->sql_serveur, 'int NOT NULL');
505
	}
506
507
	//Trouver une jointure
508
	$champ = "id_rubrique";
509
	$desc = $boucle->show;
510
	//Seulement si necessaire
511
	if (!array_key_exists($champ, $desc['field'])) {
512
		$cle = trouver_jointure_champ($champ, $boucle);
513
		$trouver_table = charger_fonction("trouver_table", "base");
514
		$desc = $trouver_table($boucle->from[$cle]);
515
		if (count(trouver_champs_decomposes($champ, $desc)) > 1) {
516
			$decompose = decompose_champ_id_objet($champ);
517
			$champ = array_shift($decompose);
518
			$boucle->where[] = array("'='", _q($cle . "." . reset($decompose)), '"' . sql_quote(end($decompose)) . '"');
519
		}
520
	} else {
521
		$cle = $boucle->id_table;
522
	}
523
524
	$c = "sql_in('$cle" . ".$champ', calcul_branche_in($arg)"
525
		. ($not ? ", 'NOT'" : '') . ")";
526
	$boucle->where[] = !$crit->cond ? $c :
527
		("($arg ? $c : " . ($not ? "'0=1'" : "'1=1'") . ')');
528
}
529
530
/**
531
 * Compile le critère `logo` qui liste les objets qui ont un logo
532
 *
533
 * @uses lister_objets_avec_logos()
534
 *     Pour obtenir les éléments qui ont un logo
535
 *
536
 * @param string $idb Identifiant de la boucle
537
 * @param array $boucles AST du squelette
538
 * @param Critere $crit Paramètres du critère dans cette boucle
539
 * @return void
540
 **/
541
function critere_logo_dist($idb, &$boucles, $crit) {
542
543
	$not = $crit->not;
544
	$boucle = &$boucles[$idb];
545
546
	$c = "sql_in('" .
547
		$boucle->id_table . '.' . $boucle->primary
548
		. "', lister_objets_avec_logos('" . $boucle->primary . "'), '')";
549
550
	if ($crit->cond) {
551
		$c = "($arg ? $c : 1)";
0 ignored issues
show
Bug introduced by
The variable $arg does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
552
	}
553
554
	if ($not) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $not of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
555
		$boucle->where[] = array("'NOT'", $c);
556
	} else {
557
		$boucle->where[] = $c;
558
	}
559
}
560
561
562
/**
563
 * Compile le critère `fusion` qui regroupe les éléments selon une colonne.
564
 *
565
 * C'est la commande SQL «GROUP BY»
566
 *
567
 * @critere
568
 * @link http://www.spip.net/5166
569
 * @example
570
 *     ```
571
 *      <BOUCLE_a(articles){fusion lang}>
572
 *     ```
573
 *
574
 * @param string $idb Identifiant de la boucle
575
 * @param array $boucles AST du squelette
576
 * @param Critere $crit Paramètres du critère dans cette boucle
577
 * @return void
0 ignored issues
show
Documentation introduced by
Should the return type not be null|array<string|array<string,string>>?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
578
 **/
579
function critere_fusion_dist($idb, &$boucles, $crit) {
580
	if ($t = isset($crit->param[0])) {
581
		$t = $crit->param[0];
582
		if ($t[0]->type == 'texte') {
583
			$t = $t[0]->texte;
584 View Code Duplication
			if (preg_match("/^(.*)\.(.*)$/", $t, $r)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
585
				$t = table_objet_sql($r[1]);
586
				$t = array_search($t, $boucles[$idb]->from);
587
				if ($t) {
588
					$t .= '.' . $r[2];
589
				}
590
			}
591
		} else {
592
			$t = '".'
593
				. calculer_critere_arg_dynamique($idb, $boucles, $t)
594
				. '."';
595
		}
596
	}
597
	if ($t) {
598
		$boucles[$idb]->group[] = $t;
599 View Code Duplication
		if (!in_array($t, $boucles[$idb]->select)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
600
			$boucles[$idb]->select[] = $t;
601
		}
602
	} else {
603
		return (array('zbug_critere_inconnu', array('critere' => $crit->op . ' ?')));
604
	}
605
}
606
607
/**
608
 * Compile le critère `{collecte}` qui permet de spécifier l'interclassement
609
 * à utiliser pour les tris de la boucle.
610
 * 
611
 * Cela permet avec le critère `{par}` de trier un texte selon 
612
 * l'interclassement indiqué. L'instruction s'appliquera sur les critères `{par}`
613
 * qui succèdent ce critère, ainsi qu'au critère `{par}` précédent
614
 * si aucun interclassement ne lui est déjà appliqué.
615
 * 
616
 * Techniquement, c'est la commande SQL "COLLATE" qui utilisée.
617
 * (elle peut être appliquée sur les order by, group by, where, like ...)
618
 * 
619
 * @example  
620
 *     - `{par titre}{collecte utf8_spanish_ci}` ou `{collecte utf8_spanish_ci}{par titre}`
621
 *     - `{par titre}{par surtitre}{collecte utf8_spanish_ci}` : 
622
 *        Seul 'surtitre' (`par` précédent) utilisera l'interclassement
623
 *     - `{collecte utf8_spanish_ci}{par titre}{par surtitre}` : 
624
 *        'titre' et 'surtitre' utiliseront l'interclassement (tous les `par` suivants)
625
 * 
626
 * @note 
627
 *     Piège sur une éventuelle écriture peu probable :
628
 *     `{par a}{collecte c1}{par b}{collecte c2}` : le tri `{par b}` 
629
 *     utiliserait l'interclassement c1 (et non c2 qui ne s'applique pas
630
 *     au `par` précédent s'il a déjà un interclassement demandé).
631
 *
632
 * @critere
633
 * @link http://www.spip.net/4028
634
 * @see critere_par_dist() Le critère `{par}`
635
 * 
636
 * @param string $idb Identifiant de la boucle
637
 * @param array $boucles AST du squelette
638
 * @param Critere $crit Paramètres du critère dans cette boucle
639
 */
640
function critere_collecte_dist($idb, &$boucles, $crit) {
641
	if (isset($crit->param[0])) {
642
		$_coll = calculer_liste($crit->param[0], array(), $boucles, $boucles[$idb]->id_parent);
643
		$boucle = $boucles[$idb];
644
		$boucle->modificateur['collate'] = "($_coll ?' COLLATE '.$_coll:'')";
645
		$n = count($boucle->order);
646
		if ($n && (strpos($boucle->order[$n - 1], 'COLLATE') === false)) {
647
			// l'instruction COLLATE doit être placée avant ASC ou DESC
648
			// notamment lors de l'utilisation `{!par xxx}{collate yyy}`
649
			if (
650
				(false !== $i = strpos($boucle->order[$n - 1], 'ASC'))
651
				OR (false !== $i = strpos($boucle->order[$n - 1], 'DESC'))
652
			) {
653
				$boucle->order[$n - 1] = substr_replace($boucle->order[$n - 1], "' . " . $boucle->modificateur['collate'] . " . ' ", $i, 0);
654
			} else {
655
				$boucle->order[$n - 1] .= " . " . $boucle->modificateur['collate'];
656
			}
657
		}
658 View Code Duplication
	} else {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
659
		return (array('zbug_critere_inconnu', array('critere' => $crit->op . " " . count($boucles[$idb]->order))));
660
	}
661
}
662
663
// http://code.spip.net/@calculer_critere_arg_dynamique
664
function calculer_critere_arg_dynamique($idb, &$boucles, $crit, $suffix = '') {
665
	$boucle = $boucles[$idb];
666
	$alt = "('" . $boucle->id_table . '.\' . $x' . $suffix . ')';
667
	$var = '$champs_' . $idb;
668
	$desc = (strpos($boucle->in, "static $var =") !== false);
669
	if (!$desc) {
670
		$desc = $boucle->show['field'];
671
		$desc = implode(',', array_map('_q', array_keys($desc)));
672
		$boucles[$idb]->in .= "\n\tstatic $var = array(" . $desc . ");";
673
	}
674
	if ($desc) {
675
		$alt = "(in_array(\$x, $var)  ? $alt :(\$x$suffix))";
676
	}
677
	$arg = calculer_liste($crit, array(), $boucles, $boucle->id_parent);
678
679
	return "((\$x = preg_replace(\"/\\W/\",'', $arg)) ? $alt : '')";
680
}
681
682
/**
683
 * Compile le critère `{par}` qui permet d'ordonner les résultats d'une boucle
684
 *
685
 * Demande à trier la boucle selon certains champs (en SQL, la commande `ORDER BY`).
686
 * Si plusieurs tris sont demandés (plusieurs fois le critère `{par x}{par y}` dans une boucle ou plusieurs champs
687
 * séparés par des virgules dans le critère `{par x, y, z}`), ils seront appliqués dans l'ordre.
688
 *
689
 * Quelques particularités :
690
 * - `{par hasard}` : trie par hasard
691
 * - `{par num titre}` : trie par numéro de titre
692
 * - `{par multi titre}` : trie par la langue extraite d'une balise polyglotte `<multi>` sur le champ titre
693
 * - `{!par date}` : trie par date inverse en utilisant le champ date principal déclaré pour la table (si c'est un objet éditorial).
694
 * - `{!par points}` : trie par pertinence de résultat de recherche (avec le critère `{recherche}`)
695
 * - `{par FUNCTION_SQL(n)}` : trie en utilisant une fonction SQL (peut dépendre du moteur SQL utilisé).
696
 *     Exemple : `{par SUBSTRING_INDEX(titre, ".", -1)}` (tri ~ alphabétique en ignorant les numéros de titres
697
 *     (exemple erroné car faux dès qu'un titre possède un point.)).
698
 * - `{par table.champ}` : trie en effectuant une jointure sur la table indiquée.
699
 * - `{par #BALISE}` : trie sur la valeur retournée par la balise (doit être un champ de la table, ou 'hasard').
700
 *
701
 * @example
702
 *     - `{par titre}`
703
 *     - `{!par date}`
704
 *     - `{par num titre, multi titre, hasard}`
705
 *
706
 * @critere
707
 * @link http://www.spip.net/5531
708
 * @see critere_tri_dist() Le critère `{tri ...}`
709
 * @see critere_inverse_dist() Le critère `{inverse}`
710
 *
711
 * @uses critere_parinverse()
712
 *
713
 * @param string $idb Identifiant de la boucle
714
 * @param array $boucles AST du squelette
715
 * @param Critere $crit Paramètres du critère dans cette boucle
716
 */
717
function critere_par_dist($idb, &$boucles, $crit) {
718
	return critere_parinverse($idb, $boucles, $crit);
719
}
720
721
/**
722
 * Calculs pour le critère `{par}` ou `{inverse}` pour ordonner les résultats d'une boucle
723
 *
724
 * Les expressions intermédiaires `{par expr champ}` sont calculées dans des fonctions
725
 * `calculer_critere_par_expression_{expr}()` notamment `{par num champ}` ou `{par multi champ}`.
726
 *
727
 * @see critere_par_dist() Le critère `{par}` pour des exemples
728
 *
729
 * @uses calculer_critere_arg_dynamique() pour le calcul de `{par #ENV{tri}}`
730
 * @uses calculer_critere_par_hasard() pour le calcul de `{par hasard}`
731
 * @uses calculer_critere_par_champ()
732
 * @see calculer_critere_par_expression_num() pour le calcul de `{par num x}`
733
 * @see calculer_critere_par_expression_multi() pour le calcul de `{par multi x}`
734
 *
735
 * @param string $idb Identifiant de la boucle
736
 * @param array $boucles AST du squelette
737
 * @param Critere $crit Paramètres du critère dans cette boucle
738
 */
739
function critere_parinverse($idb, &$boucles, $crit) {
740
	$boucle = &$boucles[$idb];
741
742
	$sens = $collecte = '';
743
	if ($crit->not) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $crit->not of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
744
		$sens = " . ' DESC'";
745
	}
746
	if (isset($boucle->modificateur['collate'])) {
747
		$collecte = ' . ' . $boucle->modificateur['collate'];
748
	}
749
750
	// Pour chaque paramètre du critère
751
	foreach ($crit->param as $tri) {
752
		$order = $fct = '';
0 ignored issues
show
Unused Code introduced by
$order is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
753
		// tris specifiés dynamiquement {par #ENV{tri}}
754
		if ($tri[0]->type != 'texte') {
755
			// calculer le order dynamique qui verifie les champs
756
			$order = calculer_critere_arg_dynamique($idb, $boucles, $tri, $sens);
757
			// ajouter 'hasard' comme possibilité de tri dynamique
758
			calculer_critere_par_hasard($idb, $boucles, $crit);
759
		}
760
		// tris textuels {par titre}
761
		else {
762
			$par = array_shift($tri);
763
			$par = $par->texte;
764
765
			// tris de la forme {par expression champ} tel que {par num titre} ou {par multi titre}
766
			if (preg_match(",^(\w+)[\s]+(.*)$,", $par, $m)) {
767
				$expression = trim($m[1]);
768
				$champ = trim($m[2]);
769
				if (function_exists($f = 'calculer_critere_par_expression_' . $expression)) {
770
					$order = $f($idb, $boucles, $crit, $tri, $champ);
771
				} else {
772
					return array('zbug_critere_inconnu', array('critere' => $crit->op . " $par"));
773
				}
774
775
			// tris de la forme {par champ} ou {par FONCTION(champ)}
776
			} elseif (preg_match(",^" . CHAMP_SQL_PLUS_FONC . '$,is', $par, $match)) {
777
				// {par FONCTION(champ)}
778
				if (count($match) > 2) {
779
					$par = substr($match[2], 1, -1);
780
					$fct = $match[1];
781
				}
782
				// quelques cas spécifiques {par hasard}, {par date}
783
				if ($par == 'hasard') {
784
					$order = calculer_critere_par_hasard($idb, $boucles, $crit);
785
				} elseif ($par == 'date' and !empty($boucle->show['date'])) {
786
					$order = "'" . $boucle->id_table . "." . $boucle->show['date'] . "'";
787
				} else {
788
					// cas général {par champ}, {par table.champ}, ...
789
					$order = calculer_critere_par_champ($idb, $boucles, $crit, $par);
790
				}
791
			}
792
793
			// on ne sait pas traiter…
794
			else {
795
				return array('zbug_critere_inconnu', array('critere' => $crit->op . " $par"));
796
			}
797
798
			// En cas d'erreur de squelette retournée par une fonction
799
			if (is_array($order)) {
800
				return $order;
801
			}
802
		}
803
804 View Code Duplication
		if (preg_match('/^\'([^"]*)\'$/', $order, $m)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
805
			$t = $m[1];
806
			if (strpos($t, '.') and !in_array($t, $boucle->select)) {
807
				$boucle->select[] = $t;
808
			}
809
		} else {
810
			$sens = '';
811
		}
812
813
		if ($fct) {
814 View Code Duplication
			if (preg_match("/^\s*'(.*)'\s*$/", $order, $r)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
815
				$order = "'$fct(" . $r[1] . ")'";
816
			} else {
817
				$order = "'$fct(' . $order . ')'";
818
			}
819
		}
820
		$t = $order . $collecte . $sens;
821
		if (preg_match("/^(.*)'\s*\.\s*'([^']*')$/", $t, $r)) {
822
			$t = $r[1] . $r[2];
823
		}
824
825
		$boucle->order[] = $t;
826
	}
827
}
828
829
/**
830
 * Calculs pour le critère `{par hasard}`
831
 *
832
 * Ajoute le générateur d'aléatoire au SELECT de la boucle.
833
 *
834
 * @param string $idb Identifiant de la boucle
835
 * @param array $boucles AST du squelette
836
 * @param Critere $crit Paramètres du critère dans cette boucle
837
 * @return string Clause pour le Order by
838
 */
839
function calculer_critere_par_hasard($idb, &$boucles, $crit) {
0 ignored issues
show
Unused Code introduced by
The parameter $crit is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
840
	$boucle = &$boucles[$idb];
841
	// Si ce n'est fait, ajouter un champ 'hasard' dans le select
842
	$parha = "rand() AS hasard";
843
	if (!in_array($parha, $boucle->select)) {
844
		$boucle->select[] = $parha;
845
	}
846
	return "'hasard'";
847
}
848
849
/**
850
 * Calculs pour le critère `{par num champ}` qui extrait le numéro préfixant un texte
851
 *
852
 * Tri par numéro de texte (tel que "10. titre"). Le numéro calculé est ajouté au SELECT
853
 * de la boucle. L'écriture `{par num #ENV{tri}}` est aussi prise en compte.
854
 *
855
 * @note
856
 *     Les textes sans numéro valent 0 et sont donc placés avant les titres ayant des numéros.
857
 *     Utiliser `{par sinum champ, num champ}` pour avoir le comportement inverse.
858
 *
859
 * @see calculer_critere_par_expression_sinum() pour le critère `{par sinum champ}`
860
 * @uses calculer_critere_par_champ()
861
 *
862
 * @param string $idb Identifiant de la boucle
863
 * @param array $boucles AST du squelette
864
 * @param Critere $crit Paramètres du critère dans cette boucle
865
 * @param array $tri Paramètre en cours du critère
866
 * @param string $champ Texte suivant l'expression ('titre' dans {par num titre})
867
 * @return string Clause pour le Order by
868
 */
869 View Code Duplication
function calculer_critere_par_expression_num($idb, &$boucles, $crit, $tri, $champ) {
0 ignored issues
show
Duplication introduced by
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
870
	$_champ = calculer_critere_par_champ($idb, $boucles, $crit, $champ, true);
871
	if (is_array($_champ)) {
872
		return array('zbug_critere_inconnu', array('critere' => $crit->op . " num $champ"));
873
	}
874
	$boucle = &$boucles[$idb];
875
	$texte = '0+' . $_champ;
876
	$suite = calculer_liste($tri, array(), $boucles, $boucle->id_parent);
877
	if ($suite !== "''") {
878
		$texte = "\" . ((\$x = $suite) ? ('$texte' . \$x) : '0')" . " . \"";
879
	}
880
	$as = 'num' . ($boucle->order ? count($boucle->order) : "");
881
	$boucle->select[] = $texte . " AS $as";
882
	$order = "'$as'";
883
	return $order;
884
}
885
886
/**
887
 * Calculs pour le critère `{par sinum champ}` qui ordonne les champs avec numéros en premier
888
 *
889
 * Ajoute au SELECT la valeur 'sinum' qui vaut 0 si le champ a un numéro, 1 s'il n'en a pas.
890
 * Ainsi `{par sinum titre, num titre, titre}` mettra les éléments sans numéro en fin de liste,
891
 * contrairement à `{par num titre, titre}` seulement.
892
 *
893
 * @see calculer_critere_par_expression_num() pour le critère `{par num champ}`
894
 * @uses calculer_critere_par_champ()
895
 *
896
 * @param string $idb Identifiant de la boucle
897
 * @param array $boucles AST du squelette
898
 * @param Critere $crit Paramètres du critère dans cette boucle
899
 * @param array $tri Paramètre en cours du critère
900
 * @param string $champ Texte suivant l'expression ('titre' dans {par sinum titre})
901
 * @return string Clause pour le Order by
902
 */
903 View Code Duplication
function calculer_critere_par_expression_sinum($idb, &$boucles, $crit, $tri, $champ) {
0 ignored issues
show
Duplication introduced by
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
904
	$_champ = calculer_critere_par_champ($idb, $boucles, $crit, $champ, true);
905
	if (is_array($_champ)) {
906
		return array('zbug_critere_inconnu', array('critere' => $crit->op . " sinum $champ"));
907
	}
908
	$boucle = &$boucles[$idb];
909
	$texte = '0+' . $_champ;
910
	$suite = calculer_liste($tri, array(), $boucles, $boucle->id_parent);
911
	if ($suite !== "''") {
912
		$texte = "\" . ((\$x = $suite) ? ('$texte' . \$x) : '0')" . " . \"";
913
	}
914
	$as = 'sinum' . ($boucle->order ? count($boucle->order) : "");
915
	$boucle->select[] = 'CASE (' . $texte . ') WHEN 0 THEN 1 ELSE 0 END AS ' . $as;
916
	$order = "'$as'";
917
	return $order;
918
}
919
920
921
/**
922
 * Calculs pour le critère `{par multi champ}` qui extrait la langue en cours dans les textes
923
 * ayant des balises `<multi>` (polyglottes)
924
 *
925
 * Ajoute le calcul du texte multi extrait dans le SELECT de la boucle.
926
 * Il ne peut y avoir qu'un seul critère de tri `multi` par boucle.
927
 *
928
 * @uses calculer_critere_par_champ()
929
 * @param string $idb Identifiant de la boucle
930
 * @param array $boucles AST du squelette
931
 * @param Critere $crit Paramètres du critère dans cette boucle
932
 * @param array $tri Paramètre en cours du critère
933
 * @param string $champ Texte suivant l'expression ('titre' dans {par multi titre})
934
 * @return string Clause pour le Order by
0 ignored issues
show
Documentation introduced by
Should the return type not be array<string|array<string,string>>|string?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
935
 */
936
function calculer_critere_par_expression_multi($idb, &$boucles, $crit, $tri, $champ) {
0 ignored issues
show
Unused Code introduced by
The parameter $tri is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
937
	$_champ = calculer_critere_par_champ($idb, $boucles, $crit, $champ, true);
938
	if (is_array($_champ)) {
939
		return array('zbug_critere_inconnu', array('critere' => $crit->op . " multi $champ"));
940
	}
941
	$boucle = &$boucles[$idb];
942
	$boucle->select[] = "\".sql_multi('" . $_champ . "', \$GLOBALS['spip_lang']).\"";
943
	$order = "'multi'";
944
	return $order;
945
}
946
947
/**
948
 * Retourne le champ de tri demandé en ajoutant éventuellement les jointures nécessaires à la boucle.
949
 *
950
 * - si le champ existe dans la table, on l'utilise
951
 * - si c'est une exception de jointure, on l'utilise (et crée la jointure au besoin)
952
 * - si c'est un champ dont la jointure est déjà présente on la réutilise
953
 * - si c'est un champ dont la jointure n'est pas présente, on la crée.
954
 *
955
 * @param string $idb Identifiant de la boucle
956
 * @param array $boucles AST du squelette
957
 * @param Critere $crit Paramètres du critère dans cette boucle
958
 * @param string $par Nom du tri à analyser ('champ' ou 'table.champ')
959
 * @param bool $raw Retourne le champ pour le compilateur ("'alias.champ'") ou brut ('alias.champ')
960
 * @return array|string
961
 */
962
function calculer_critere_par_champ($idb, &$boucles, $crit,  $par, $raw = false) {
963
	$boucle = &$boucles[$idb];
964
965
	// le champ existe dans la table, pas de souci (le plus commun)
966
	if (isset($desc['field'][$par])) {
0 ignored issues
show
Bug introduced by
The variable $desc seems to never exist, and therefore isset should always return false. Did you maybe rename this variable?

This check looks for calls to isset(...) or empty() on variables that are yet undefined. These calls will always produce the same result and can be removed.

This is most likely caused by the renaming of a variable or the removal of a function/method parameter.

Loading history...
967
		$par = $boucle->id_table . "." . $par;
968
	}
969
	// le champ est peut être une jointure
970
	else {
971
		$table = $table_alias = false; // toutes les tables de jointure possibles
972
		$champ = $par;
973
974
		// le champ demandé est une exception de jointure {par titre_mot}
975
		if (isset($GLOBALS['exceptions_des_jointures'][$par])) {
976
			list($table, $champ) = $GLOBALS['exceptions_des_jointures'][$par];
977
		} // la table de jointure est explicitement indiquée {par truc.muche}
978
		elseif (preg_match("/^([^,]*)\.(.*)$/", $par, $r)) {
979
			list(, $table, $champ) = $r;
980
			$table_alias = $table; // c'est peut-être un alias de table {par L1.titre}
981
			$table = table_objet_sql($table);
982
		}
983
984
		// Si on connait la table d'arrivée, on la demande donc explicitement
985
		// Sinon on cherche le champ dans les tables possibles de jointures
986
		// Si la table est déjà dans le from, on la réutilise.
987
		if ($infos = chercher_champ_dans_tables($champ, $boucle->from, $boucle->sql_serveur, $table)) {
988
			$par = $infos['alias'] . "." . $champ;
989
		} elseif (
990
			$boucle->jointures_explicites
991
			and $alias = trouver_jointure_champ($champ, $boucle, explode(' ', $boucle->jointures_explicites), false, $table)
992
		) {
993
			$par = $alias . "." . $champ;
994
		} elseif ($alias = trouver_jointure_champ($champ, $boucle, $boucle->jointures, false, $table)) {
995
			$par = $alias . "." . $champ;
996
		// en spécifiant directement l'alias {par L2.titre} (situation hasardeuse tout de même)
997
		} elseif (
998
			$table_alias
999
			and isset($boucle->from[$table_alias])
1000
			and $infos = chercher_champ_dans_tables($champ, $boucle->from, $boucle->sql_serveur, $boucle->from[$table_alias])
1001
		) {
1002
			$par = $infos['alias'] . "." . $champ;
1003
		} elseif ($table) {
1004
			// On avait table + champ, mais on ne les a pas trouvés
1005
			return array('zbug_critere_inconnu', array('critere' => $crit->op . " $par"));
1006
		} else {
1007
			// Sinon tant pis, ca doit etre un champ synthetise (cf points)
1008
		}
1009
	}
1010
1011
	return $raw ? $par : "'$par'";
1012
}
1013
1014
/**
1015
 * Retourne un champ de tri en créant une jointure
1016
 * si la table n'est pas présente dans le from de la boucle.
1017
 *
1018
 * @deprecated
1019
 * @param string $table Table du champ désiré
1020
 * @param string $champ Champ désiré
1021
 * @param Boucle $boucle Boucle en cours de compilation
1022
 * @return string Champ pour le compilateur si trouvé, tel que "'alias.champ'", sinon vide.
1023
 */
1024
function critere_par_joint($table, $champ, &$boucle) {
1025
	$t = array_search($table, $boucle->from);
1026
	if (!$t) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $t of type false|integer is loosely compared to false; this is ambiguous if the integer can be zero. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
1027
		$t = trouver_jointure_champ($champ, $boucle);
1028
	}
1029
	return !$t ? '' : ("'" . $t . '.' . $champ . "'");
1030
}
1031
1032
/**
1033
 * Compile le critère `{inverse}` qui inverse l'ordre utilisé par le précédent critère `{par}`
1034
 *
1035
 * Accèpte un paramètre pour déterminer le sens : `{inverse #X}` utilisera un tri croissant (ASC) 
1036
 * si la valeur retournée par `#X` est considérée vrai (`true`),
1037
 * le sens contraire (DESC) sinon.
1038
 * 
1039
 * @example
1040
 *     - `{par date}{inverse}`, équivalent à `{!par date}`
1041
 *     - `{par date}{inverse #ENV{sens}}` utilise la valeur d'environnement sens pour déterminer le sens.
1042
 *
1043
 * @critere
1044
 * @see critere_par_dist() Le critère `{par}`
1045
 * @link http://www.spip.net/5530
1046
 * @uses critere_parinverse()
1047
 *
1048
 * @param string $idb Identifiant de la boucle
1049
 * @param array $boucles AST du squelette
1050
 * @param Critere $crit Paramètres du critère dans cette boucle
1051
 */
1052
function critere_inverse_dist($idb, &$boucles, $crit) {
1053
1054
	$boucle = &$boucles[$idb];
1055
	// Classement par ordre inverse
1056
	if ($crit->not) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $crit->not of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1057
		critere_parinverse($idb, $boucles, $crit);
1058
	} else {
1059
		$order = "' DESC'";
1060
		// Classement par ordre inverse fonction eventuelle de #ENV{...}
1061 View Code Duplication
		if (isset($crit->param[0])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1062
			$critere = calculer_liste($crit->param[0], array(), $boucles, $boucles[$idb]->id_parent);
1063
			$order = "(($critere)?' DESC':'')";
1064
		}
1065
1066
		$n = count($boucle->order);
1067
		if (!$n) {
1068
			if (isset($boucle->default_order[0])) {
1069
				$boucle->default_order[0] .= ' . " DESC"';
1070
			} else {
1071
				$boucle->default_order[] = ' DESC';
1072
			}
1073
		} else {
1074
			$t = $boucle->order[$n - 1] . " . $order";
1075
			if (preg_match("/^(.*)'\s*\.\s*'([^']*')$/", $t, $r)) {
1076
				$t = $r[1] . $r[2];
1077
			}
1078
			$boucle->order[$n - 1] = $t;
1079
		}
1080
	}
1081
}
1082
1083
// http://code.spip.net/@critere_agenda_dist
1084
function critere_agenda_dist($idb, &$boucles, $crit) {
1085
	$params = $crit->param;
1086
1087
	if (count($params) < 1) {
1088
		return array('zbug_critere_inconnu', array('critere' => $crit->op . " ?"));
1089
	}
1090
1091
	$boucle = &$boucles[$idb];
1092
	$parent = $boucle->id_parent;
1093
	$fields = $boucle->show['field'];
1094
1095
	$date = array_shift($params);
1096
	$type = array_shift($params);
1097
1098
	// la valeur $type doit etre connue a la compilation
1099
	// donc etre forcement reduite a un litteral unique dans le source
1100
	$type = is_object($type[0]) ? $type[0]->texte : null;
1101
1102
	// La valeur date doit designer un champ de la table SQL.
1103
	// Si c'est un litteral unique dans le source, verifier a la compil,
1104
	// sinon synthetiser le test de verif pour execution ulterieure
1105
	// On prendra arbitrairement le premier champ si test negatif.
1106
	if ((count($date) == 1) and ($date[0]->type == 'texte')) {
1107
		$date = $date[0]->texte;
1108
		if (!isset($fields[$date])) {
1109
			return array('zbug_critere_inconnu', array('critere' => $crit->op . " " . $date));
1110
		}
1111
	} else {
1112
		$a = calculer_liste($date, array(), $boucles, $parent);
1113
		$noms = array_keys($fields);
1114
		$defaut = $noms[0];
1115
		$noms = join(" ", $noms);
1116
		# bien laisser 2 espaces avant $nom pour que strpos<>0
1117
		$cond = "(\$a=strval($a))AND\nstrpos(\"  $noms \",\" \$a \")";
1118
		$date = "'.(($cond)\n?\$a:\"$defaut\").'";
1119
	}
1120
	$annee = $params ? array_shift($params) : "";
1121
	$annee = "\n" . 'sprintf("%04d", ($x = ' .
1122
		calculer_liste($annee, array(), $boucles, $parent) .
1123
		') ? $x : date("Y"))';
1124
1125
	$mois = $params ? array_shift($params) : "";
1126
	$mois = "\n" . 'sprintf("%02d", ($x = ' .
1127
		calculer_liste($mois, array(), $boucles, $parent) .
1128
		') ? $x : date("m"))';
1129
1130
	$jour = $params ? array_shift($params) : "";
1131
	$jour = "\n" . 'sprintf("%02d", ($x = ' .
1132
		calculer_liste($jour, array(), $boucles, $parent) .
1133
		') ? $x : date("d"))';
1134
1135
	$annee2 = $params ? array_shift($params) : "";
1136
	$annee2 = "\n" . 'sprintf("%04d", ($x = ' .
1137
		calculer_liste($annee2, array(), $boucles, $parent) .
1138
		') ? $x : date("Y"))';
1139
1140
	$mois2 = $params ? array_shift($params) : "";
1141
	$mois2 = "\n" . 'sprintf("%02d", ($x = ' .
1142
		calculer_liste($mois2, array(), $boucles, $parent) .
1143
		') ? $x : date("m"))';
1144
1145
	$jour2 = $params ? array_shift($params) : "";
1146
	$jour2 = "\n" . 'sprintf("%02d", ($x = ' .
1147
		calculer_liste($jour2, array(), $boucles, $parent) .
1148
		') ? $x : date("d"))';
1149
1150
	$date = $boucle->id_table . ".$date";
1151
1152
	$quote_end = ",'" . $boucle->sql_serveur . "','text'";
1153
	if ($type == 'jour') {
1154
		$boucle->where[] = array(
1155
			"'='",
1156
			"'DATE_FORMAT($date, \'%Y%m%d\')'",
1157
			("sql_quote($annee . $mois . $jour$quote_end)")
1158
		);
1159
	} elseif ($type == 'mois') {
1160
		$boucle->where[] = array(
1161
			"'='",
1162
			"'DATE_FORMAT($date, \'%Y%m\')'",
1163
			("sql_quote($annee . $mois$quote_end)")
1164
		);
1165
	} elseif ($type == 'semaine') {
1166
		$boucle->where[] = array(
1167
			"'AND'",
1168
			array(
1169
				"'>='",
1170
				"'DATE_FORMAT($date, \'%Y%m%d\')'",
1171
				("date_debut_semaine($annee, $mois, $jour)")
1172
			),
1173
			array(
1174
				"'<='",
1175
				"'DATE_FORMAT($date, \'%Y%m%d\')'",
1176
				("date_fin_semaine($annee, $mois, $jour)")
1177
			)
1178
		);
1179
	} elseif (count($crit->param) > 2) {
1180
		$boucle->where[] = array(
1181
			"'AND'",
1182
			array(
1183
				"'>='",
1184
				"'DATE_FORMAT($date, \'%Y%m%d\')'",
1185
				("sql_quote($annee . $mois . $jour$quote_end)")
1186
			),
1187
			array("'<='", "'DATE_FORMAT($date, \'%Y%m%d\')'", ("sql_quote($annee2 . $mois2 . $jour2$quote_end)"))
1188
		);
1189
	}
1190
	// sinon on prend tout
1191
}
1192
1193
1194
/**
1195
 * Compile les critères {i,j} et {i/j}
1196
 *
1197
 * Le critère {i,j} limite l'affiche de la boucle en commançant l'itération
1198
 * au i-ème élément, et pour j nombre d'éléments.
1199
 * Le critère {n-i,j} limite en commençant au n moins i-ème élément de boucle
1200
 * Le critère {i,n-j} limite en terminant au n moins j-ème élément de boucle.
1201
 *
1202
 * Le critère {i/j} affiche une part d'éléments de la boucle.
1203
 * Commence à i*n/j élément et boucle n/j éléments. {2/4} affiche le second
1204
 * quart des éléments d'une boucle.
1205
 *
1206
 * Traduit si possible (absence de n dans {i,j}) la demande en une
1207
 * expression LIMIT du gestionnaire SQL
1208
 *
1209
 * @param string $idb Identifiant de la boucle
1210
 * @param array $boucles AST du squelette
1211
 * @param Critere $crit Paramètres du critère dans cette boucle
1212
 * @return void
1213
 **/
1214
function calculer_critere_parties($idb, &$boucles, $crit) {
1215
	$boucle = &$boucles[$idb];
1216
	$a1 = $crit->param[0];
1217
	$a2 = $crit->param[1];
1218
	$op = $crit->op;
1219
1220
	list($a11, $a12) = calculer_critere_parties_aux($idb, $boucles, $a1);
1221
	list($a21, $a22) = calculer_critere_parties_aux($idb, $boucles, $a2);
1222
1223
	if (($op == ',') && (is_numeric($a11) && (is_numeric($a21)))) {
1224
		$boucle->limit = $a11 . ',' . $a21;
1225
	} else {
1226
		// 3 dans {1/3}, {2,3} ou {1,n-3}
1227
		$boucle->total_parties = ($a21 != 'n') ? $a21 : $a22;
1228
		// 2 dans {2/3}, {2,5}, {n-2,1}
1229
		$partie = ($a11 != 'n') ? $a11 : $a12;
1230
		$mode = (($op == '/') ? '/' :
1231
			(($a11 == 'n') ? '-' : '+') . (($a21 == 'n') ? '-' : '+'));
1232
		// cas simple {0,#ENV{truc}} compilons le en LIMIT :
1233
		if ($a11 !== 'n' and $a21 !== 'n' and $mode == "++" and $op == ',') {
1234
			$boucle->limit =
1235
				(is_numeric($a11) ? "'$a11'" : $a11)
1236
				. ".','."
1237
				. (is_numeric($a21) ? "'$a21'" : $a21);
1238
		} else {
1239
			calculer_parties($boucles, $idb, $partie, $mode);
1240
		}
1241
	}
1242
}
1243
1244
/**
1245
 * Compile certains critères {i,j} et {i/j}
1246
 *
1247
 * Calcule une expression déterminant $debut_boucle et $fin_boucle (le
1248
 * début et la fin des éléments de la boucle qui doivent être affichés)
1249
 * et les déclare dans la propriété «mode_partie» de la boucle, qui se
1250
 * charge également de déplacer le pointeur de boucle sur le premier
1251
 * élément à afficher.
1252
 *
1253
 * Place dans la propriété partie un test vérifiant que l'élément de
1254
 * boucle en cours de lecture appartient bien à la plage autorisée.
1255
 * Trop tôt, passe à l'élément suivant, trop tard, sort de l'itération de boucle.
1256
 *
1257
 * @param array $boucles AST du squelette
1258
 * @param string $id_boucle Identifiant de la boucle
1259
 * @param string $debut Valeur ou code pour trouver le début (i dans {i,j})
1260
 * @param string $mode
1261
 *     Mode (++, p+, +- ...) : 2 signes début & fin
1262
 *     - le signe - indique
1263
 *       -- qu'il faut soustraire debut du total {n-3,x}. 3 étant $debut
1264
 *       -- qu'il faut raccourcir la fin {x,n-3} de 3 elements. 3 étant $total_parties
1265
 *     - le signe p indique une pagination
1266
 * @return void
1267
 **/
1268
function calculer_parties(&$boucles, $id_boucle, $debut, $mode) {
1269
	$total_parties = $boucles[$id_boucle]->total_parties;
1270
1271
	preg_match(",([+-/p])([+-/])?,", $mode, $regs);
1272
	list(, $op1, $op2) = array_pad($regs, 3, null);
1273
	$nombre_boucle = "\$Numrows['$id_boucle']['total']";
1274
	// {1/3}
1275
	if ($op1 == '/') {
1276
		$pmoins1 = is_numeric($debut) ? ($debut - 1) : "($debut-1)";
1277
		$totpos = is_numeric($total_parties) ? ($total_parties) :
1278
			"($total_parties ? $total_parties : 1)";
1279
		$fin = "ceil(($nombre_boucle * $debut )/$totpos) - 1";
1280
		$debut = !$pmoins1 ? 0 : "ceil(($nombre_boucle * $pmoins1)/$totpos);";
1281
	} else {
1282
		// cas {n-1,x}
1283
		if ($op1 == '-') {
1284
			$debut = "$nombre_boucle - $debut;";
1285
		}
1286
1287
		// cas {x,n-1}
1288
		if ($op2 == '-') {
1289
			$fin = '$debut_boucle + ' . $nombre_boucle . ' - '
1290
				. (is_numeric($total_parties) ? ($total_parties + 1) :
1291
					($total_parties . ' - 1'));
1292
		} else {
1293
			// {x,1} ou {pagination}
1294
			$fin = '$debut_boucle'
1295
				. (is_numeric($total_parties) ?
1296
					(($total_parties == 1) ? "" : (' + ' . ($total_parties - 1))) :
1297
					('+' . $total_parties . ' - 1'));
1298
		}
1299
1300
		// {pagination}, gerer le debut_xx=-1 pour tout voir
1301
		if ($op1 == 'p') {
1302
			$debut .= ";\n	\$debut_boucle = ((\$tout=(\$debut_boucle == -1))?0:(\$debut_boucle))";
1303
			$debut .= ";\n	\$debut_boucle = max(0,min(\$debut_boucle,floor(($nombre_boucle-1)/($total_parties))*($total_parties)))";
1304
			$fin = "(\$tout ? $nombre_boucle : $fin)";
1305
		}
1306
	}
1307
1308
	// Notes :
1309
	// $debut_boucle et $fin_boucle sont les indices SQL du premier
1310
	// et du dernier demandes dans la boucle : 0 pour le premier,
1311
	// n-1 pour le dernier ; donc total_boucle = 1 + debut - fin
1312
	// Utiliser min pour rabattre $fin_boucle sur total_boucle.
1313
1314
	$boucles[$id_boucle]->mode_partie = "\n\t"
1315
		. '$debut_boucle = ' . $debut . ";\n	"
1316
		. "\$debut_boucle = intval(\$debut_boucle);\n	"
1317
		. '$fin_boucle = min(' . $fin . ", \$Numrows['$id_boucle']['total'] - 1);\n	"
1318
		. '$Numrows[\'' . $id_boucle . "']['grand_total'] = \$Numrows['$id_boucle']['total'];\n	"
1319
		. '$Numrows[\'' . $id_boucle . '\']["total"] = max(0,$fin_boucle - $debut_boucle + 1);'
1320
		. "\n\tif (\$debut_boucle>0"
1321
		. " AND \$debut_boucle < \$Numrows['$id_boucle']['grand_total']"
1322
		. " AND \$iter->seek(\$debut_boucle,'continue'))"
1323
		. "\n\t\t\$Numrows['$id_boucle']['compteur_boucle'] = \$debut_boucle;\n\t";
1324
1325
	$boucles[$id_boucle]->partie = "
1326
		if (\$Numrows['$id_boucle']['compteur_boucle'] <= \$debut_boucle) continue;
1327
		if (\$Numrows['$id_boucle']['compteur_boucle']-1 > \$fin_boucle) break;";
1328
}
1329
1330
/**
1331
 * Analyse un des éléments des critères {a,b} ou {a/b}
1332
 *
1333
 * Pour l'élément demandé (a ou b) retrouve la valeur de l'élément,
1334
 * et de combien il est soustrait si c'est le cas comme dans {a-3,b}
1335
 *
1336
 * @param string $idb Identifiant de la boucle
1337
 * @param array $boucles AST du squelette
1338
 * @param array $param Paramètre à analyser (soit a, soit b dans {a,b} ou {a/b})
1339
 * @return array          Valeur de l'élément (peut être une expression PHP), Nombre soustrait
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array<string|integer>.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
1340
 **/
1341
function calculer_critere_parties_aux($idb, &$boucles, $param) {
1342
	if ($param[0]->type != 'texte') {
1343
		$a1 = calculer_liste(array($param[0]), array('id_mere' => $idb), $boucles, $boucles[$idb]->id_parent);
1344
		if (isset($param[1]->texte)) {
1345
			preg_match(',^ *(-([0-9]+))? *$,', $param[1]->texte, $m);
1346
1347
			return array("intval($a1)", ((isset($m[2]) and $m[2]) ? $m[2] : 0));
1348
		} else {
1349
			return array("intval($a1)", 0);
1350
		}
1351
	} else {
1352
		preg_match(',^ *(([0-9]+)|n) *(- *([0-9]+)? *)?$,', $param[0]->texte, $m);
1353
		$a1 = $m[1];
1354
		if (empty($m[3])) {
1355
			return array($a1, 0);
1356
		} elseif (!empty($m[4])) {
1357
			return array($a1, $m[4]);
1358
		} else {
1359
			return array($a1, calculer_liste(array($param[1]), array(), $boucles, $boucles[$idb]->id_parent));
1360
		}
1361
	}
1362
}
1363
1364
1365
/**
1366
 * Compile les critères d'une boucle
1367
 *
1368
 * Cette fonction d'aiguillage cherche des fonctions spécifiques déclarées
1369
 * pour chaque critère demandé, dans l'ordre ci-dessous :
1370
 *
1371
 * - critere_{serveur}_{table}_{critere}, sinon avec _dist
1372
 * - critere_{serveur}_{critere}, sinon avec _dist
1373
 * - critere_{table}_{critere}, sinon avec _dist
1374
 * - critere_{critere}, sinon avec _dist
1375
 * - calculer_critere_defaut, sinon avec _dist
1376
 *
1377
 * Émet une erreur de squelette si un critère retourne une erreur.
1378
 *
1379
 * @param string $idb
1380
 *     Identifiant de la boucle
1381
 * @param array $boucles
1382
 *     AST du squelette
1383
 * @return string|array
1384
 *     string : Chaine vide sans erreur
1385
 *     array : Erreur sur un des critères
1386
 **/
1387
function calculer_criteres($idb, &$boucles) {
1388
	$msg = '';
1389
	$boucle = $boucles[$idb];
1390
	$table = strtoupper($boucle->type_requete);
1391
	$serveur = strtolower($boucle->sql_serveur);
1392
1393
	$defaut = charger_fonction('DEFAUT', 'calculer_critere');
1394
	// s'il y avait une erreur de syntaxe, propager cette info
1395
	if (!is_array($boucle->criteres)) {
1396
		return array();
1397
	}
1398
1399
	foreach ($boucle->criteres as $crit) {
1400
		$critere = $crit->op;
1401
		// critere personnalise ?
1402
		if (
1403
			(!$serveur or
1404
				((!function_exists($f = "critere_" . $serveur . "_" . $table . "_" . $critere))
1405
					and (!function_exists($f = $f . "_dist"))
1406
					and (!function_exists($f = "critere_" . $serveur . "_" . $critere))
1407
					and (!function_exists($f = $f . "_dist"))
1408
				)
1409
			)
1410
			and (!function_exists($f = "critere_" . $table . "_" . $critere))
1411
			and (!function_exists($f = $f . "_dist"))
1412
			and (!function_exists($f = "critere_" . $critere))
1413
			and (!function_exists($f = $f . "_dist"))
1414
		) {
1415
			// fonction critere standard
1416
			$f = $defaut;
1417
		}
1418
		// compile le critere
1419
		$res = $f($idb, $boucles, $crit);
1420
1421
		// Gestion centralisee des erreurs pour pouvoir propager
1422
		if (is_array($res)) {
1423
			$msg = $res;
1424
			erreur_squelette($msg, $boucle);
1425
		}
1426
	}
1427
1428
	return $msg;
1429
}
1430
1431
/**
1432
 * Désemberlificote les guillements et échappe (ou fera échapper) le contenu...
1433
 *
1434
 * Madeleine de Proust, revision MIT-1958 sqq, revision CERN-1989
1435
 * hum, c'est kwoi cette fonxion ? on va dire qu'elle desemberlificote les guillemets...
1436
 *
1437
 * http://code.spip.net/@kwote
1438
 *
1439
 * @param string $lisp Code compilé
1440
 * @param string $serveur Connecteur de bdd utilisé
1441
 * @param string $type Type d'échappement (char, int...)
1442
 * @return string         Code compilé rééchappé
1443
 */
1444
function kwote($lisp, $serveur = '', $type = '') {
1445
	if (preg_match(_CODE_QUOTE, $lisp, $r)) {
1446
		return $r[1] . "\"" . sql_quote(str_replace(array("\\'", "\\\\"), array("'", "\\"), $r[2]), $serveur, $type) . "\"";
1447
	} else {
1448
		return "sql_quote($lisp, '$serveur', '" . str_replace("'", "\\'", $type) . "')";
1449
	}
1450
}
1451
1452
1453
/**
1454
 * Compile un critère possédant l'opérateur IN : {xx IN yy}
1455
 *
1456
 * Permet de restreindre un champ sur une liste de valeurs tel que
1457
 * {id_article IN 3,4} {id_article IN #LISTE{3,4}}
1458
 *
1459
 * Si on a une liste de valeurs dans #ENV{x}, utiliser la double etoile
1460
 * pour faire par exemple {id_article IN #ENV**{liste_articles}}
1461
 *
1462
 * @param string $idb Identifiant de la boucle
1463
 * @param array $boucles AST du squelette
1464
 * @param Critere $crit Paramètres du critère dans cette boucle
1465
 * @return void
0 ignored issues
show
Documentation introduced by
Should the return type not be array<string|array<string,string>>|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
1466
 **/
1467
function critere_IN_dist($idb, &$boucles, $crit) {
1468
	$r = calculer_critere_infixe($idb, $boucles, $crit);
1469
	if (!$r) {
1470
		return (array('zbug_critere_inconnu', array('critere' => $crit->op . " ?")));
1471
	}
1472
	list($arg, $op, $val, $col, $where_complement) = $r;
1473
1474
	$in = critere_IN_cas($idb, $boucles, $crit->not ? 'NOT' : ($crit->exclus ? 'exclus' : ''), $arg, $op, $val, $col);
1475
1476
	//	inserer la condition; exemple: {id_mot ?IN (66, 62, 64)}
1477
	$where = $in;
1478
	if ($crit->cond) {
1479
		$pred = calculer_argument_precedent($idb, $col, $boucles);
1480
		$where = array("'?'", $pred, $where, "''");
1481
		if ($where_complement) // condition annexe du type "AND (objet='article')"
1482
		{
1483
			$where_complement = array("'?'", $pred, $where_complement, "''");
1484
		}
1485
	}
1486 View Code Duplication
	if ($crit->exclus) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $crit->exclus of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1487
		if (!preg_match(",^L[0-9]+[.],", $arg)) {
1488
			$where = array("'NOT'", $where);
1489
		} else
1490
			// un not sur un critere de jointure se traduit comme un NOT IN avec une sous requete
1491
			// c'est une sous requete identique a la requete principale sous la forme (SELF,$select,$where) avec $select et $where qui surchargent
1492
		{
1493
			$where = array(
1494
				"'NOT'",
1495
				array(
1496
					"'IN'",
1497
					"'" . $boucles[$idb]->id_table . "." . $boucles[$idb]->primary . "'",
1498
					array("'SELF'", "'" . $boucles[$idb]->id_table . "." . $boucles[$idb]->primary . "'", $where)
1499
				)
1500
			);
1501
		}
1502
	}
1503
1504
	$boucles[$idb]->where[] = $where;
1505
	if ($where_complement) // condition annexe du type "AND (objet='article')"
1506
	{
1507
		$boucles[$idb]->where[] = $where_complement;
1508
	}
1509
}
1510
1511
// http://code.spip.net/@critere_IN_cas
1512
function critere_IN_cas($idb, &$boucles, $crit2, $arg, $op, $val, $col) {
0 ignored issues
show
Unused Code introduced by
The parameter $op is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $col is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1513
	static $num = array();
1514
	$descr = $boucles[$idb]->descr;
1515
	$cpt = &$num[$descr['nom']][$descr['gram']][$idb];
1516
1517
	$var = '$in' . $cpt++;
1518
	$x = "\n\t$var = array();";
1519
	foreach ($val as $k => $v) {
1520
		if (preg_match(",^(\n//.*\n)?'(.*)'$,", $v, $r)) {
1521
			// optimiser le traitement des constantes
1522
			if (is_numeric($r[2])) {
1523
				$x .= "\n\t$var" . "[]= $r[2];";
1524
			} else {
1525
				$x .= "\n\t$var" . "[]= " . sql_quote($r[2]) . ";";
1526
			}
1527
		} else {
1528
			// Pour permettre de passer des tableaux de valeurs
1529
			// on repere l'utilisation brute de #ENV**{X},
1530
			// c'est-a-dire sa  traduction en ($PILE[0][X]).
1531
			// et on deballe mais en rajoutant l'anti XSS
1532
			$x .= "\n\tif (!(is_array(\$a = ($v))))\n\t\t$var" . "[]= \$a;\n\telse $var = array_merge($var, \$a);";
1533
		}
1534
	}
1535
1536
	$boucles[$idb]->in .= $x;
1537
1538
	// inserer le tri par defaut selon les ordres du IN ...
1539
	// avec une ecriture de type FIELD qui degrade les performances (du meme ordre qu'un regexp)
1540
	// et que l'on limite donc strictement aux cas necessaires :
1541
	// si ce n'est pas un !IN, et si il n'y a pas d'autre order dans la boucle
1542
	if (!$crit2) {
1543
		$boucles[$idb]->default_order[] = "((!sql_quote($var) OR sql_quote($var)===\"''\") ? 0 : ('FIELD($arg,' . sql_quote($var) . ')'))";
1544
	}
1545
1546
	return "sql_in('$arg',sql_quote($var)" . ($crit2 == 'NOT' ? ",'NOT'" : "") . ")";
1547
}
1548
1549
/**
1550
 * Compile le critère {where}
1551
 *
1552
 * Ajoute une contrainte sql WHERE, tout simplement pour faire le pont
1553
 * entre php et squelettes, en utilisant la syntaxe attendue par
1554
 * la propriété $where d'une Boucle.
1555
 *
1556
 * @param string $idb Identifiant de la boucle
1557
 * @param array $boucles AST du squelette
1558
 * @param Critere $crit Paramètres du critère dans cette boucle
1559
 * @return void
1560
 */
1561
function critere_where_dist($idb, &$boucles, $crit) {
1562
	$boucle = &$boucles[$idb];
1563 View Code Duplication
	if (isset($crit->param[0])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1564
		$_where = calculer_liste($crit->param[0], array(), $boucles, $boucle->id_parent);
1565
	} else {
1566
		$_where = '@$Pile[0]["where"]';
1567
	}
1568
1569
	if ($crit->cond) {
1570
		$_where = "(($_where) ? ($_where) : '')";
1571
	}
1572
1573
	if ($crit->not) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $crit->not of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1574
		$_where = "array('NOT',$_where)";
1575
	}
1576
1577
	$boucle->where[] = $_where;
1578
}
1579
1580
/**
1581
 * Compile le critère `{id_?}`
1582
 *
1583
 * Ajoute automatiquement à la boucle des contraintes (nommées sélections conditionnelles)
1584
 * équivalentes à `{id_article ?}{id_rubrique ?}...`, adaptées à la table en cours d’utilisation.
1585
 *
1586
 * Les champs sélectionnés par défaut sont :
1587
 * - chaque champ id_xx de la table en cours d’utilisation (par exemple id_secteur sur la boucle ARTICLES)
1588
 * - un champ 'objet', si cette table en dispose
1589
 * - chaque clé primaire des tables des objets éditoriaux, s’ils sont éditables et liables (par exemple id_mot).
1590
 *
1591
 * @example
1592
 *     ```
1593
 *      <BOUCLE_liste_articles(ARTICLES){id_?}{tout}> ...
1594
 *      Est équivalent (selon les plugins actifs) à :
1595
 *      <BOUCLE_liste_articles(ARTICLES){id_article?}{id_rubrique?}{id_secteur?}{id_trad?}{id_mot?}{id_document?} ... {tout}> ...
1596
 *     ```
1597
 *
1598
 * @uses lister_champs_selection_conditionnelle()
1599
 * @param string $idb Identifiant de la boucle
1600
 * @param array $boucles AST du squelette
1601
 * @param Critere $crit Paramètres du critère dans cette boucle
1602
 * @return void
1603
 */
1604
function critere_id__dist($idb, &$boucles, $crit) {
1605
1606
	$champs = lister_champs_selection_conditionnelle(
1607
		$boucles[$idb]->show['table'],
1608
		$boucles[$idb]->show,
1609
		$boucles[$idb]->sql_serveur
1610
	);
1611
1612
	// créer un critère {id_xxx?} de chaque champ retenu
1613
	foreach ($champs as $champ) {
1614
		$critere_id_table = new Critere;
1615
		$critere_id_table->op = $champ;
1616
		$critere_id_table->cond = '?';
0 ignored issues
show
Documentation Bug introduced by
The property $cond was declared of type boolean, but '?' is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
1617
		$critere_id_table->ligne = $crit->ligne;
1618
		calculer_critere_DEFAUT_dist($idb, $boucles, $critere_id_table);
1619
	}
1620
}
1621
1622
/**
1623
 * Liste les champs qui peuvent servir de selection conditionnelle à une table SQL
1624
 *
1625
 * Retourne, pour la table demandée :
1626
 * - chaque champ id_xx de la table en cours d’utilisation (par exemple id_secteur sur la boucle ARTICLES)
1627
 * - un champ 'objet' si la table le contient (pour les tables avec objet / id_objet par exemple)
1628
 * - chaque clé primaire des tables des objets éditoriaux qui peuvent se lier facilement à cette table,
1629
 * -- soit parce que sa clé primaire de la table demandée est un champ dans la table principale
1630
 * -- soit parce qu’une table de liaison existe, d’un côté ou de l’autre
1631
 *
1632
 * @pipeline_appel lister_champs_selection_conditionnelle
1633
 * @param string $table Nom de la table SQL
1634
 * @param array|null $desc Description de la table SQL, si connu
1635
 * @param string $serveur Connecteur sql a utiliser
1636
 * @return array Liste de nom de champs (tel que id_article, id_mot, id_parent ...)
1637
 */
1638
function lister_champs_selection_conditionnelle($table, $desc = null, $serveur = '') {
1639
	// calculer la description de la table
1640
	if (!is_array($desc)) {
1641
		$desc = description_table($table, $serveur);
1642
	}
1643
	if (!$desc) {
1644
		return [];
1645
	}
1646
1647
	// Les champs id_xx de la table demandée
1648
	$champs = array_filter(
1649
		array_keys($desc['field']),
1650
		function($champ){
1651
			return
1652
				strpos($champ, 'id_') === 0
1653
				or (in_array($champ, array('objet')));
1654
		}
1655
	);
1656
1657
	// On ne fera pas mieux pour les tables d’un autre serveur
1658
	if ($serveur) {
1659
		return $champs;
1660
	}
1661
1662
	$primary = false;
1663
	$associable = false;
1664
	include_spip('action/editer_liens');
1665
1666
	if (isset($desc['type'])) {
1667
		$primary = id_table_objet($desc['type']);
1668
		$associable = objet_associable($desc['type']);
1669
	} elseif (substr($table, -6) === '_liens') {
1670
		$associable = true;
1671
	}
1672
1673
	// liste de toutes les tables principales, sauf la notre
1674
	$tables = lister_tables_objets_sql();
1675
	unset($tables[$table]);
1676
1677
	foreach ($tables as $_table => $_desc) {
0 ignored issues
show
Bug introduced by
The expression $tables of type array|boolean is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
1678
		if (
1679
			$associable
1680
			or ($primary and in_array($primary, array_keys($_desc['field'])))
1681
			or objet_associable($_desc['type'])
1682
		) {
1683
			$champs[] = id_table_objet($_table);
1684
		}
1685
	}
1686
	$champs = array_unique($champs);
1687
	$champs = pipeline(
1688
		'lister_champs_selection_conditionnelle',
1689
		array(
1690
			'args' => array(
1691
				'table' => $table,
1692
				'id_table_objet' => $primary,
1693
				'associable' => $associable,
1694
			),
1695
			'data' => $champs,
1696
		)
1697
	);
1698
	return $champs;
1699
}
1700
1701
/**
1702
 * Compile le critère `{tri}` permettant le tri dynamique d'un champ
1703
 *
1704
 * Le critère `{tri}` gère un champ de tri  qui peut être modifié dynamiquement par la balise `#TRI`.
1705
 * Il s'utilise donc conjointement avec la balise `#TRI` dans la même boucle
1706
 * pour génerér les liens qui permettent de changer le critère de tri et le sens du tri
1707
 *
1708
 * @syntaxe `{tri [champ_par_defaut][,sens_par_defaut][,nom_variable]}`
1709
 *
1710
 * - champ_par_defaut : un champ de la table sql
1711
 * - sens_par_defaut : -1 ou inverse pour décroissant, 1 ou direct pour croissant
1712
 *     peut être un tableau pour préciser des sens par défaut associés à chaque champ
1713
 *     exemple : `array('titre' => 1, 'date' => -1)` pour trier par défaut
1714
 *     les titre croissants et les dates décroissantes
1715
 *     dans ce cas, quand un champ est utilisé pour le tri et n'est pas présent dans le tableau
1716
 *     c'est la première valeur qui est utilisée
1717
 * - nom_variable : nom de la variable utilisée (par defaut `tri_{nomboucle}`)
1718
 *
1719
 *     {tri titre}
1720
 *     {tri titre,inverse}
1721
 *     {tri titre,-1}
1722
 *     {tri titre,-1,truc}
1723
 *
1724
 * Exemple d'utilisation :
1725
 *
1726
 *     <B_articles>
1727
 *     <p>#TRI{titre,'Trier par titre'} | #TRI{date,'Trier par date'}</p>
1728
 *     <ul>
1729
 *     <BOUCLE_articles(ARTICLES){tri titre}>
1730
 *      <li>#TITRE - [(#DATE|affdate_jourcourt)]</li>
1731
 *     </BOUCLE_articles>
1732
 *     </ul>
1733
 *     </B_articles>
1734
 *
1735
 * @note
1736
 *     Contraitement à `{par ...}`, `{tri}` ne peut prendre qu'un seul champ,
1737
 *     mais il peut être complété avec `{par ...}` pour indiquer des criteres secondaires
1738
 *
1739
 *     Exemble :
1740
 *     `{tri num titre}{par titre}` permet de faire un tri sur le rang (modifiable dynamiquement)
1741
 *     avec un second critère sur le titre en cas d'égalité des rangs
1742
 *
1743
 * @link http://www.spip.net/5429
1744
 * @see critere_par_dist() Le critère `{par ...}`
1745
 * @see balise_TRI_dist() La balise `#TRI`
1746
 *
1747
 * @param string $idb Identifiant de la boucle
1748
 * @param array $boucles AST du squelette
1749
 * @param Critere $crit Paramètres du critère dans cette boucle
1750
 * @return void
1751
 */
1752
function critere_tri_dist($idb, &$boucles, $crit) {
1753
	$boucle = &$boucles[$idb];
1754
1755
	// definition du champ par defaut
1756
	$_champ_defaut = !isset($crit->param[0][0]) ? "''"
1757
		: calculer_liste(array($crit->param[0][0]), array(), $boucles, $boucle->id_parent);
1758
	$_sens_defaut = !isset($crit->param[1][0]) ? "1"
1759
		: calculer_liste(array($crit->param[1][0]), array(), $boucles, $boucle->id_parent);
1760
	$_variable = !isset($crit->param[2][0]) ? "'$idb'"
1761
		: calculer_liste(array($crit->param[2][0]), array(), $boucles, $boucle->id_parent);
1762
1763
	$_tri = "((\$t=(isset(\$Pile[0]['tri'.$_variable]))?\$Pile[0]['tri'.$_variable]:((strncmp($_variable,'session',7)==0 AND session_get('tri'.$_variable))?session_get('tri'.$_variable):$_champ_defaut))?tri_protege_champ(\$t):'')";
1764
1765
	$_sens_defaut = "(is_array(\$s=$_sens_defaut)?(isset(\$s[\$st=$_tri])?\$s[\$st]:reset(\$s)):\$s)";
1766
	$_sens = "((intval(\$t=(isset(\$Pile[0]['sens'.$_variable]))?\$Pile[0]['sens'.$_variable]:((strncmp($_variable,'session',7)==0 AND session_get('sens'.$_variable))?session_get('sens'.$_variable):$_sens_defaut))==-1 OR \$t=='inverse')?-1:1)";
1767
1768
	$boucle->modificateur['tri_champ'] = $_tri;
1769
	$boucle->modificateur['tri_sens'] = $_sens;
1770
	$boucle->modificateur['tri_nom'] = $_variable;
1771
	// faut il inserer un test sur l'existence de $tri parmi les champs de la table ?
1772
	// evite des erreurs sql, mais peut empecher des tri sur jointure ...
1773
	$boucle->hash .= "
1774
	\$senstri = '';
1775
	\$tri = $_tri;
1776
	if (\$tri){
1777
		\$senstri = $_sens;
1778
		\$senstri = (\$senstri<0)?' DESC':'';
1779
	};
1780
	";
1781
	$boucle->select[] = "\".tri_champ_select(\$tri).\"";
1782
	$boucle->order[] = "tri_champ_order(\$tri,\$command['from']).\$senstri";
1783
}
1784
1785
# Criteres de comparaison
1786
1787
/**
1788
 * Compile un critère non déclaré explicitement
1789
 *
1790
 * Compile les critères non déclarés, ainsi que les parties de boucles
1791
 * avec les critères {0,1} ou {1/2}
1792
 *
1793
 * @param string $idb Identifiant de la boucle
1794
 * @param array $boucles AST du squelette
1795
 * @param Critere $crit Paramètres du critère dans cette boucle
1796
 * @return void
0 ignored issues
show
Documentation introduced by
Should the return type not be null|array<string|array<string,null|string>>?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
1797
 **/
1798
function calculer_critere_DEFAUT_dist($idb, &$boucles, $crit) {
1799
	// double cas particulier {0,1} et {1/2} repere a l'analyse lexicale
1800
	if (($crit->op == ",") or ($crit->op == '/')) {
1801
		return calculer_critere_parties($idb, $boucles, $crit);
1802
	}
1803
1804
	$r = calculer_critere_infixe($idb, $boucles, $crit);
1805
	if (!$r) {
1806
		#	// on produit une erreur seulement si le critere n'a pas de '?'
1807
		#	if (!$crit->cond) {
1808
		return (array('zbug_critere_inconnu', array('critere' => $crit->op)));
1809
		#	}
1810
	} else {
1811
		calculer_critere_DEFAUT_args($idb, $boucles, $crit, $r);
0 ignored issues
show
Bug introduced by
It seems like $r defined by calculer_critere_infixe($idb, $boucles, $crit) on line 1804 can also be of type string; however, calculer_critere_DEFAUT_args() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1812
	}
1813
}
1814
1815
1816
/**
1817
 * Compile un critère non déclaré explicitement, dont on reçoit une analyse
1818
 *
1819
 * Ajoute en fonction des arguments trouvés par calculer_critere_infixe()
1820
 * les conditions WHERE à appliquer sur la boucle.
1821
 *
1822
 * @see calculer_critere_infixe()
1823
 *
1824
 * @param string $idb Identifiant de la boucle
1825
 * @param array $boucles AST du squelette
1826
 * @param Critere $crit Paramètres du critère dans cette boucle
1827
 * @param array $args Description du critère
1828
 *                        Cf. retour de calculer_critere_infixe()
1829
 * @return void
1830
 **/
1831
function calculer_critere_DEFAUT_args($idb, &$boucles, $crit, $args) {
1832
	list($arg, $op, $val, $col, $where_complement) = $args;
1833
1834
	$where = array("'$op'", "'$arg'", $val[0]);
1835
1836
	// inserer la negation (cf !...)
1837
1838
	if ($crit->not) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $crit->not of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1839
		$where = array("'NOT'", $where);
1840
	}
1841 View Code Duplication
	if ($crit->exclus) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $crit->exclus of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1842
		if (!preg_match(",^L[0-9]+[.],", $arg)) {
1843
			$where = array("'NOT'", $where);
1844
		} else
1845
			// un not sur un critere de jointure se traduit comme un NOT IN avec une sous requete
1846
			// c'est une sous requete identique a la requete principale sous la forme (SELF,$select,$where) avec $select et $where qui surchargent
1847
		{
1848
			$where = array(
1849
				"'NOT'",
1850
				array(
1851
					"'IN'",
1852
					"'" . $boucles[$idb]->id_table . "." . $boucles[$idb]->primary . "'",
1853
					array("'SELF'", "'" . $boucles[$idb]->id_table . "." . $boucles[$idb]->primary . "'", $where)
1854
				)
1855
			);
1856
		}
1857
	}
1858
1859
	// inserer la condition (cf {lang?})
1860
	// traiter a part la date, elle est mise d'office par SPIP,
1861
	if ($crit->cond) {
1862
		$pred = calculer_argument_precedent($idb, $col, $boucles);
1863
		if ($col == "date" or $col == "date_redac") {
1864
			if ($pred == "\$Pile[0]['" . $col . "']") {
1865
				$pred = "(\$Pile[0]['{$col}_default']?'':$pred)";
1866
			}
1867
		}
1868
1869
		if ($op == '=' and !$crit->not) {
1870
			$where = array(
1871
				"'?'",
1872
				"(is_array($pred))",
1873
				critere_IN_cas($idb, $boucles, 'COND', $arg, $op, array($pred), $col),
1874
				$where
1875
			);
1876
		}
1877
		$where = array("'?'", "!(is_array($pred)?count($pred):strlen($pred))", "''", $where);
1878
		if ($where_complement) // condition annexe du type "AND (objet='article')"
1879
		{
1880
			$where_complement = array("'?'", "!(is_array($pred)?count($pred):strlen($pred))", "''", $where_complement);
1881
		}
1882
	}
1883
1884
	$boucles[$idb]->where[] = $where;
1885
	if ($where_complement) // condition annexe du type "AND (objet='article')"
1886
	{
1887
		$boucles[$idb]->where[] = $where_complement;
1888
	}
1889
}
1890
1891
1892
/**
1893
 * Décrit un critère non déclaré explicitement
1894
 *
1895
 * Décrit un critère non déclaré comme {id_article} {id_article>3} en
1896
 * retournant un tableau de l'analyse si la colonne (ou l'alias) existe vraiment.
1897
 *
1898
 * Ajoute au passage pour chaque colonne utilisée (alias et colonne véritable)
1899
 * un modificateur['criteres'][colonne].
1900
 *
1901
 * S'occupe de rechercher des exceptions, tel que
1902
 * - les id_parent, id_enfant, id_secteur,
1903
 * - des colonnes avec des exceptions déclarées,
1904
 * - des critères de date (jour_relatif, ...),
1905
 * - des critères sur tables jointes explicites (mots.titre),
1906
 * - des critères sur tables de jointure non explicite (id_mot sur une boucle articles...)
1907
 *
1908
 *
1909
 * @param string $idb Identifiant de la boucle
1910
 * @param array $boucles AST du squelette
1911
 * @param Critere $crit Paramètres du critère dans cette boucle
1912
 * @return array|string
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use string|array.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
1913
 *     Liste si on trouve le champ :
1914
 *     - string $arg
1915
 *         Opérande avant l'opérateur : souvent la colonne d'application du critère, parfois un calcul
1916
 *         plus complexe dans le cas des dates.
1917
 *     - string $op
1918
 *         L'opérateur utilisé, tel que '='
1919
 *     - string[] $val
1920
 *         Liste de codes PHP obtenant les valeurs des comparaisons (ex: id_article sur la boucle parente)
1921
 *         Souvent (toujours ?) un tableau d'un seul élément.
1922
 *     - $col_alias
1923
 *     - $where_complement
1924
 *
1925
 *     Chaîne vide si on ne trouve pas le champ...
1926
 **/
1927
function calculer_critere_infixe($idb, &$boucles, $crit) {
1928
1929
	$boucle = &$boucles[$idb];
1930
	$type = $boucle->type_requete;
1931
	$table = $boucle->id_table;
1932
	$desc = $boucle->show;
1933
	$col_vraie = null;
1934
1935
	list($fct, $col, $op, $val, $args_sql) =
1936
		calculer_critere_infixe_ops($idb, $boucles, $crit);
1937
1938
	$col_alias = $col;
1939
	$where_complement = false;
1940
1941
	// Cas particulier : id_enfant => utiliser la colonne id_objet
1942
	if ($col == 'id_enfant') {
1943
		$col = $boucle->primary;
1944
	}
1945
1946
	// Cas particulier : id_parent => verifier les exceptions de tables
1947
	if ((in_array($col, array('id_parent', 'id_secteur')) and isset($GLOBALS['exceptions_des_tables'][$table][$col]))
1948
		or (isset($GLOBALS['exceptions_des_tables'][$table][$col]) and is_string($GLOBALS['exceptions_des_tables'][$table][$col]))
1949
	) {
1950
		$col = $GLOBALS['exceptions_des_tables'][$table][$col];
1951
	} // et possibilite de gerer un critere secteur sur des tables de plugins (ie forums)
1952
	else {
1953
		if (($col == 'id_secteur') and ($critere_secteur = charger_fonction("critere_secteur_$type", "public", true))) {
1954
			$table = $critere_secteur($idb, $boucles, $val, $crit);
1955
		}
1956
1957
		// cas id_article=xx qui se mappe en id_objet=xx AND objet=article
1958
		// sauf si exception declaree : sauter cette etape
1959
		else {
1960
			if (
1961
				!isset($GLOBALS['exceptions_des_jointures'][table_objet_sql($table)][$col])
1962
				and !isset($GLOBALS['exceptions_des_jointures'][$col])
1963
				and count(trouver_champs_decomposes($col, $desc)) > 1
1964
			) {
1965
				$e = decompose_champ_id_objet($col);
1966
				$col = array_shift($e);
1967
				$where_complement = primary_doublee($e, $table);
1968
			} // Cas particulier : expressions de date
1969
			else {
1970
				if ($c = calculer_critere_infixe_date($idb, $boucles, $col)) {
1971
					list($col, $col_vraie) = $c;
1972
					$table = '';
1973
				} // table explicitée {mots.titre}
1974
				else {
1975
					if (preg_match('/^(.*)\.(.*)$/', $col, $r)) {
1976
						list(, $table, $col) = $r;
1977
						$col_alias = $col;
1978
1979
						$trouver_table = charger_fonction('trouver_table', 'base');
1980
						if ($desc = $trouver_table($table, $boucle->sql_serveur)
1981
							and isset($desc['field'][$col])
1982
							and $cle = array_search($desc['table'], $boucle->from)
1983
						) {
1984
							$table = $cle;
1985
						} else {
1986
							$table = trouver_jointure_champ($col, $boucle, array($table), ($crit->cond or $op != '='));
1987
						}
1988
						#$table = calculer_critere_externe_init($boucle, array($table), $col, $desc, ($crit->cond OR $op!='='), true);
1989
						if (!$table) {
1990
							return '';
1991
						}
1992
					}
1993
					// si le champ n'est pas trouvé dans la table,
1994
					// on cherche si une jointure peut l'obtenir
1995
					elseif (@!array_key_exists($col, $desc['field'])) {
1996
						// Champ joker * des iterateurs DATA qui accepte tout
1997
						if (@array_key_exists('*', $desc['field'])) {
1998
							$desc['field'][$col_vraie ? $col_vraie : $col] = ''; // on veut pas de cast INT par defaut car le type peut etre n'importe quoi dans les boucles DATA
1999
						}
2000
						else {
2001
							$r = calculer_critere_infixe_externe($boucle, $crit, $op, $desc, $col, $col_alias, $table);
2002
							if (!$r) {
2003
								return '';
2004
							}
2005
							list($col, $col_alias, $table, $where_complement, $desc) = $r;
2006
						}
2007
					}
2008
				}
2009
			}
2010
		}
2011
	}
2012
2013
	$col_vraie = ($col_vraie ? $col_vraie : $col);
2014
	// Dans tous les cas,
2015
	// virer les guillemets eventuels autour d'un int (qui sont refuses par certains SQL)
2016
	// et passer dans sql_quote avec le type si connu
2017
	// et int sinon si la valeur est numerique
2018
	// sinon introduire le vrai type du champ si connu dans le sql_quote (ou int NOT NULL sinon)
2019
	// Ne pas utiliser intval, PHP tronquant les Bigint de SQL
2020
	if ($op == '=' or in_array($op, $GLOBALS['table_criteres_infixes'])) {
2021
		$type_cast_quote = (isset($desc['field'][$col_vraie]) ? $desc['field'][$col_vraie] : 'int NOT NULL');
2022
		// defaire le quote des int et les passer dans sql_quote avec le bon type de champ si on le connait, int sinon
2023
		// prendre en compte le debug ou la valeur arrive avec un commentaire PHP en debut
2024
		if (preg_match(",^\\A(\s*//.*?$\s*)?\"'(-?\d+)'\"\\z,ms", $val[0], $r)) {
2025
			$val[0] = $r[1] . '"' . sql_quote($r[2], $boucle->sql_serveur, $type_cast_quote) . '"';
2026
		}
2027
		// sinon expliciter les
2028
		// sql_quote(truc) en sql_quote(truc,'',type)
2029
		// sql_quote(truc,serveur) en sql_quote(truc,serveur,type)
2030
		// sql_quote(truc,serveur,'') en sql_quote(truc,serveur,type)
2031
		// sans toucher aux
2032
		// sql_quote(truc,'','varchar(10) DEFAULT \'oui\' COLLATE NOCASE')
2033
		// sql_quote(truc,'','varchar')
2034
		elseif (preg_match('/\Asql_quote[(](.*?)(,[^)]*?)?(,[^)]*(?:\(\d+\)[^)]*)?)?[)]\s*\z/ms', $val[0], $r)
2035
			// si pas deja un type
2036
			and (!isset($r[3]) or !$r[3] or !trim($r[3],", '"))
2037
		) {
2038
			$r = $r[1]
2039
				. ((isset($r[2]) and $r[2]) ? $r[2] : ",''")
2040
				. ",'" . addslashes($type_cast_quote) . "'";
2041
			$val[0] = "sql_quote($r)";
2042
		}
2043
		elseif(strpos($val[0], '@@defaultcast@@') !== false
2044
		  and preg_match("/'@@defaultcast@@'\s*\)\s*\z/ms", $val[0], $r)) {
2045
			$val[0] = substr($val[0], 0, -strlen($r[0])) . "'" . addslashes($type_cast_quote) . "')";
2046
		}
2047
	}
2048
2049
	if(strpos($val[0], '@@defaultcast@@') !== false
2050
	  and preg_match("/'@@defaultcast@@'\s*\)\s*\z/ms", $val[0], $r)) {
2051
		$val[0] = substr($val[0], 0, -strlen($r[0])) . "'char')";
2052
	}
2053
2054
	// Indicateur pour permettre aux fonctionx boucle_X de modifier
2055
	// leurs requetes par defaut, notamment le champ statut
2056
	// Ne pas confondre champs de la table principale et des jointures
2057
	if ($table === $boucle->id_table) {
2058
		$boucles[$idb]->modificateur['criteres'][$col_vraie] = true;
2059
		if ($col_alias != $col_vraie) {
2060
			$boucles[$idb]->modificateur['criteres'][$col_alias] = true;
2061
		}
2062
	}
2063
2064
	// ajout pour le cas special d'une condition sur le champ statut:
2065
	// il faut alors interdire a la fonction de boucle
2066
	// de mettre ses propres criteres de statut
2067
	// http://www.spip.net/@statut (a documenter)
2068
	// garde pour compatibilite avec code des plugins anterieurs, mais redondant avec la ligne precedente
2069
	if ($col == 'statut') {
2070
		$boucles[$idb]->statut = true;
2071
	}
2072
2073
	// inserer le nom de la table SQL devant le nom du champ
2074
	if ($table) {
2075
		if ($col[0] == "`") {
2076
			$arg = "$table." . substr($col, 1, -1);
2077
		} else {
2078
			$arg = "$table.$col";
2079
		}
2080
	} else {
2081
		$arg = $col;
2082
	}
2083
2084
	// inserer la fonction SQL
2085
	if ($fct) {
2086
		$arg = "$fct($arg$args_sql)";
2087
	}
2088
2089
	return array($arg, $op, $val, $col_alias, $where_complement);
2090
}
2091
2092
2093
/**
2094
 * Décrit un critère non déclaré explicitement, sur un champ externe à la table
2095
 *
2096
 * Décrit un critère non déclaré comme {id_article} {id_article>3} qui correspond
2097
 * à un champ non présent dans la table, et donc à retrouver par jointure si possible.
2098
 *
2099
 * @param Boucle $boucle Description de la boucle
2100
 * @param Critere $crit Paramètres du critère dans cette boucle
2101
 * @param string $op L'opérateur utilisé, tel que '='
2102
 * @param array $desc Description de la table
2103
 * @param string $col Nom de la colonne à trouver (la véritable)
2104
 * @param string $col_alias Alias de la colonne éventuel utilisé dans le critère ex: id_enfant
2105
 * @param string $table Nom de la table SQL de la boucle
2106
 * @return array|string
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use string|array.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
2107
 *     Liste si jointure possible :
2108
 *     - string $col
2109
 *     - string $col_alias
2110
 *     - string $table
2111
 *     - array $where
2112
 *     - array $desc
2113
 *
2114
 *     Chaîne vide si on ne trouve pas le champ par jointure...
2115
 **/
2116
function calculer_critere_infixe_externe($boucle, $crit, $op, $desc, $col, $col_alias, $table) {
2117
2118
	$where = '';
2119
2120
	$calculer_critere_externe = 'calculer_critere_externe_init';
2121
	// gestion par les plugins des jointures tordues
2122
	// pas automatiques mais necessaires
2123
	$table_sql = table_objet_sql($table);
2124
	if (isset($GLOBALS['exceptions_des_jointures'][$table_sql])
2125
		and is_array($GLOBALS['exceptions_des_jointures'][$table_sql])
2126
		and
2127
		(
2128
			isset($GLOBALS['exceptions_des_jointures'][$table_sql][$col])
2129
			or
2130
			isset($GLOBALS['exceptions_des_jointures'][$table_sql][''])
2131
		)
2132
	) {
2133
		$t = $GLOBALS['exceptions_des_jointures'][$table_sql];
2134
		$index = isset($t[$col])
2135
			? $t[$col] : (isset($t['']) ? $t[''] : array());
2136
2137
		if (count($index) == 3) {
2138
			list($t, $col, $calculer_critere_externe) = $index;
2139
		} elseif (count($index) == 2) {
2140
			list($t, $col) = $t[$col];
2141
		} elseif (count($index) == 1) {
2142
			list($calculer_critere_externe) = $index;
2143
			$t = $table;
2144
		} else {
2145
			$t = '';
2146
		} // jointure non declaree. La trouver.
2147
	} elseif (isset($GLOBALS['exceptions_des_jointures'][$col])) {
2148
		list($t, $col) = $GLOBALS['exceptions_des_jointures'][$col];
2149
	} else {
2150
		$t = '';
2151
	} // jointure non declaree. La trouver.
2152
2153
	// ici on construit le from pour fournir $col en piochant dans les jointures
2154
2155
	// si des jointures explicites sont fournies, on cherche d'abord dans celles ci
2156
	// permet de forcer une table de lien quand il y a ambiguite
2157
	// <BOUCLE_(DOCUMENTS documents_liens){id_mot}>
2158
	// alors que <BOUCLE_(DOCUMENTS){id_mot}> produit la meme chose que <BOUCLE_(DOCUMENTS mots_liens){id_mot}>
2159
	$table = "";
2160
	if ($boucle->jointures_explicites) {
2161
		$jointures_explicites = explode(' ', $boucle->jointures_explicites);
2162
		$table = $calculer_critere_externe($boucle, $jointures_explicites, $col, $desc, ($crit->cond or $op != '='), $t);
2163
	}
2164
2165
	// et sinon on cherche parmi toutes les jointures declarees
2166
	if (!$table) {
2167
		$table = $calculer_critere_externe($boucle, $boucle->jointures, $col, $desc, ($crit->cond or $op != '='), $t);
2168
	}
2169
2170
	if (!$table) {
2171
		return '';
2172
	}
2173
2174
	// il ne reste plus qu'a trouver le champ dans les from
2175
	list($nom, $desc, $cle) = trouver_champ_exterieur($col, $boucle->from, $boucle);
0 ignored issues
show
Unused Code introduced by
The assignment to $nom is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
2176
2177
	if (count($cle) > 1 or reset($cle) !== $col) {
2178
		$col_alias = $col; // id_article devient juste le nom d'origine
2179
		if (count($cle) > 1 and reset($cle) == 'id_objet') {
2180
			$e = decompose_champ_id_objet($col);
2181
			$col = array_shift($e);
2182
			$where = primary_doublee($e, $table);
2183
		} else {
2184
			$col = reset($cle);
2185
		}
2186
	}
2187
2188
	return array($col, $col_alias, $table, $where, $desc);
2189
}
2190
2191
2192
/**
2193
 * Calcule une condition WHERE entre un nom du champ et une valeur
2194
 *
2195
 * Ne pas appliquer sql_quote lors de la compilation,
2196
 * car on ne connait pas le serveur SQL
2197
 *
2198
 * @todo Ce nom de fonction n'est pas très clair ?
2199
 *
2200
 * @param array $decompose Liste nom du champ, code PHP pour obtenir la valeur
2201
 * @param string $table Nom de la table
2202
 * @return string[]
2203
 *     Liste de 3 éléments pour une description where du compilateur :
2204
 *     - operateur (=),
2205
 *     - table.champ,
2206
 *     - valeur
2207
 **/
2208
function primary_doublee($decompose, $table) {
2209
	$e1 = reset($decompose);
2210
	$e2 = "sql_quote('" . end($decompose) . "')";
2211
2212
	return array("'='", "'$table." . $e1 . "'", $e2);
2213
}
2214
2215
/**
2216
 * Champ hors table, ça ne peut être qu'une jointure.
2217
 *
2218
 * On cherche la table du champ et on regarde si elle est déjà jointe
2219
 * Si oui et qu'on y cherche un champ nouveau, pas de jointure supplementaire
2220
 * Exemple: criteres {titre_mot=...}{type_mot=...}
2221
 * Dans les 2 autres cas ==> jointure
2222
 * (Exemple: criteres {type_mot=...}{type_mot=...} donne 2 jointures
2223
 * pour selectioner ce qui a exactement ces 2 mots-cles.
2224
 *
2225
 * @param Boucle $boucle
2226
 *     Description de la boucle
2227
 * @param array $joints
2228
 *     Liste de jointures possibles (ex: $boucle->jointures ou $boucle->jointures_explicites)
2229
 * @param string $col
2230
 *     Colonne cible de la jointure
2231
 * @param array $desc
2232
 *     Description de la table
2233
 * @param bool $cond
2234
 *     Flag pour savoir si le critère est conditionnel ou non
2235
 * @param bool|string $checkarrivee
2236
 *     string : nom de la table jointe où on veut trouver le champ.
2237
 *     n'a normalement pas d'appel sans $checkarrivee.
2238
 * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be string|integer?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
2239
 *     Alias de la table de jointure (Lx)
2240
 *     Vide sinon.
2241
 */
2242
function calculer_critere_externe_init(&$boucle, $joints, $col, $desc, $cond, $checkarrivee = false) {
2243
	// si on demande un truc du genre spip_mots
2244
	// avec aussi spip_mots_liens dans les jointures dispo
2245
	// et qu'on est la
2246
	// il faut privilegier la jointure directe en 2 etapes spip_mots_liens, spip_mots
2247
	if ($checkarrivee
2248
		and is_string($checkarrivee)
2249
		and $a = table_objet($checkarrivee)
2250
		and in_array($a . '_liens', $joints)
2251
	) {
2252
		if ($res = calculer_lien_externe_init($boucle, $joints, $col, $desc, $cond, $checkarrivee)) {
2253
			return $res;
2254
		}
2255
	}
2256
	foreach ($joints as $joint) {
2257
		if ($arrivee = trouver_champ_exterieur($col, array($joint), $boucle, $checkarrivee)) {
2258
			// alias de table dans le from
2259
			$t = array_search($arrivee[0], $boucle->from);
2260
			// recuperer la cle id_xx eventuellement decomposee en (id_objet,objet)
2261
			$cols = $arrivee[2];
2262
			// mais on ignore la 3eme cle si presente qui correspond alors au point de depart
2263
			if (count($cols) > 2) {
2264
				array_pop($cols);
2265
			}
2266
			if ($t) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $t of type false|integer is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
2267
				// la table est déjà dans le FROM, on vérifie si le champ est utilisé.
2268
				$joindre = false;
2269
				foreach ($cols as $col) {
2270
					$c = '/\b' . $t . ".$col" . '\b/';
2271
					if (trouver_champ($c, $boucle->where)) {
2272
						$joindre = true;
2273
					} else {
2274
						// mais ca peut etre dans le FIELD pour le Having
2275
						$c = "/FIELD.$t" . ".$col,/";
2276
						if (trouver_champ($c, $boucle->select)) {
2277
							$joindre = true;
2278
						}
2279
					}
2280
				}
2281
				if (!$joindre) {
2282
					return $t;
2283
				}
2284
			}
2285
			array_pop($arrivee);
2286
			if ($res = calculer_jointure($boucle, array($boucle->id_table, $desc), $arrivee, $cols, $cond, 1)) {
2287
				return $res;
2288
			}
2289
		}
2290
	}
2291
2292
	return '';
2293
2294
}
2295
2296
/**
2297
 * Générer directement une jointure via une table de lien spip_xxx_liens
2298
 * pour un critère {id_xxx}
2299
 *
2300
 * @todo $checkarrivee doit être obligatoire ici ?
2301
 *
2302
 * @param Boucle $boucle
2303
 *     Description de la boucle
2304
 * @param array $joints
2305
 *     Liste de jointures possibles (ex: $boucle->jointures ou $boucle->jointures_explicites)
2306
 * @param string $col
2307
 *     Colonne cible de la jointure
2308
 * @param array $desc
2309
 *     Description de la table
2310
 * @param bool $cond
2311
 *     Flag pour savoir si le critère est conditionnel ou non
2312
 * @param bool|string $checkarrivee
2313
 *     string : nom de la table jointe où on veut trouver le champ.
2314
 *     n'a normalement pas d'appel sans $checkarrivee.
2315
 * @return string
2316
 *     Alias de la table de jointure (Lx)
2317
 */
2318
function calculer_lien_externe_init(&$boucle, $joints, $col, $desc, $cond, $checkarrivee = false) {
2319
	$primary_arrivee = id_table_objet($checkarrivee);
0 ignored issues
show
Bug introduced by
It seems like $checkarrivee defined by parameter $checkarrivee on line 2318 can also be of type boolean; however, id_table_objet() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
2320
2321
	// [FIXME] $checkarrivee peut-il arriver avec false ????
2322
	$intermediaire = trouver_champ_exterieur($primary_arrivee, $joints, $boucle, $checkarrivee . "_liens");
2323
	$arrivee = trouver_champ_exterieur($col, $joints, $boucle, $checkarrivee);
2324
2325
	if (!$intermediaire or !$arrivee) {
2326
		return '';
2327
	}
2328
	array_pop($intermediaire); // enlever la cle en 3eme argument
2329
	array_pop($arrivee); // enlever la cle en 3eme argument
2330
2331
	$res = fabrique_jointures($boucle,
2332
		array(
2333
			array(
2334
				$boucle->id_table,
2335
				$intermediaire,
2336
				array(id_table_objet($desc['table_objet']), 'id_objet', 'objet', $desc['type'])
2337
			),
2338
			array(reset($intermediaire), $arrivee, $primary_arrivee)
2339
		), $cond, $desc, $boucle->id_table, array($col));
2340
2341
	return $res;
2342
}
2343
2344
2345
/**
2346
 * Recherche la présence d'un champ dans une valeur de tableau
2347
 *
2348
 * @param string $champ
2349
 *     Expression régulière pour trouver un champ donné.
2350
 *     Exemple : /\barticles.titre\b/
2351
 * @param array $where
2352
 *     Tableau de valeurs dans lesquels chercher le champ.
2353
 * @return bool
0 ignored issues
show
Documentation introduced by
Should the return type not be integer|boolean?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
2354
 *     true si le champ est trouvé quelque part dans $where
2355
 *     false sinon.
2356
 **/
2357
function trouver_champ($champ, $where) {
2358
	if (!is_array($where)) {
2359
		return preg_match($champ, $where);
2360
	} else {
2361
		foreach ($where as $clause) {
2362
			if (trouver_champ($champ, $clause)) {
2363
				return true;
2364
			}
2365
		}
2366
2367
		return false;
2368
	}
2369
}
2370
2371
2372
/**
2373
 * Détermine l'operateur et les opérandes d'un critère non déclaré
2374
 *
2375
 * Lorsque l'opérateur n'est pas explicite comme sur {id_article>0} c'est
2376
 * l'opérateur '=' qui est utilisé.
2377
 *
2378
 * Traite les cas particuliers id_parent, id_enfant, date, lang
2379
 *
2380
 * @param string $idb Identifiant de la boucle
2381
 * @param array $boucles AST du squelette
2382
 * @param Critere $crit Paramètres du critère dans cette boucle
2383
 * @return array
2384
 *     Liste :
2385
 *     - string $fct       Nom d'une fonction SQL sur le champ ou vide (ex: SUM)
2386
 *     - string $col       Nom de la colonne SQL utilisée
2387
 *     - string $op        Opérateur
2388
 *     - string[] $val
2389
 *         Liste de codes PHP obtenant les valeurs des comparaisons (ex: id_article sur la boucle parente)
2390
 *         Souvent un tableau d'un seul élément.
2391
 *     - string $args_sql  Suite des arguments du critère. ?
2392
 **/
2393
function calculer_critere_infixe_ops($idb, &$boucles, $crit) {
2394
	// cas d'une valeur comparee a elle-meme ou son referent
2395
	if (count($crit->param) == 0) {
2396
		$op = '=';
2397
		$col = $val = $crit->op;
2398
		if (preg_match('/^(.*)\.(.*)$/', $col, $r)) {
2399
			$val = $r[2];
2400
		}
2401
		// Cas special {lang} : aller chercher $GLOBALS['spip_lang']
2402
		if ($val == 'lang') {
2403
			$val = array(kwote('$GLOBALS[\'spip_lang\']'));
2404
		} else {
2405
			$defaut = null;
2406
			if ($val == 'id_parent') {
2407
				// Si id_parent, comparer l'id_parent avec l'id_objet
2408
				// de la boucle superieure.... faudrait verifier qu'il existe
2409
				// pour eviter l'erreur SQL
2410
				$val = $boucles[$idb]->primary;
2411
				// mais si pas de boucle superieure, prendre id_parent dans l'env
2412
				$defaut = "@\$Pile[0]['id_parent']";
2413
			} elseif ($val == 'id_enfant') {
2414
				// Si id_enfant, comparer l'id_objet avec l'id_parent
2415
				// de la boucle superieure
2416
				$val = 'id_parent';
2417
			} elseif ($crit->cond and ($col == "date" or $col == "date_redac")) {
2418
				// un critere conditionnel sur date est traite a part
2419
				// car la date est mise d'office par SPIP,
2420
				$defaut = "(\$Pile[0]['{$col}_default']?'':\$Pile[0]['" . $col . "'])";
2421
			}
2422
2423
			$val = calculer_argument_precedent($idb, $val, $boucles, $defaut);
2424
			$val = array(kwote($val));
2425
		}
2426
	} else {
2427
		// comparaison explicite
2428
		// le phraseur impose que le premier param soit du texte
2429
		$params = $crit->param;
2430
		$op = $crit->op;
2431
		if ($op == '==') {
2432
			$op = 'REGEXP';
2433
		}
2434
		$col = array_shift($params);
2435
		$col = $col[0]->texte;
2436
2437
		$val = array();
2438
		$desc = array('id_mere' => $idb);
2439
		$parent = $boucles[$idb]->id_parent;
2440
2441
		// Dans le cas {x=='#DATE'} etc, defaire le travail du phraseur,
2442
		// celui ne sachant pas ce qu'est un critere infixe
2443
		// et a fortiori son 2e operande qu'entoure " ou '
2444
		if (count($params) == 1
2445
			and count($params[0]) == 3
2446
			and $params[0][0]->type == 'texte'
2447
			and $params[0][2]->type == 'texte'
2448
			and ($p = $params[0][0]->texte) == $params[0][2]->texte
2449
			and (($p == "'") or ($p == '"'))
2450
			and $params[0][1]->type == 'champ'
2451
		) {
2452
			$val[] = "$p\\$p#" . $params[0][1]->nom_champ . "\\$p$p";
2453
		} else {
2454
			foreach ((($op != 'IN') ? $params : calculer_vieux_in($params)) as $p) {
2455
				$a = calculer_liste($p, $desc, $boucles, $parent);
2456
				if (strcasecmp($op, 'IN') == 0) {
2457
					$val[] = $a;
2458
				} else {
2459
					$val[] = kwote($a, $boucles[$idb]->sql_serveur, '@@defaultcast@@');
2460
				} // toujours quoter en char ici
2461
			}
2462
		}
2463
	}
2464
2465
	$fct = $args_sql = '';
2466
	// fonction SQL ?
2467
	// chercher FONCTION(champ) tel que CONCAT(titre,descriptif)
2468
	if (preg_match('/^(.*)' . SQL_ARGS . '$/', $col, $m)) {
2469
		$fct = $m[1];
2470
		preg_match('/^\(([^,]*)(.*)\)$/', $m[2], $a);
2471
		$col = $a[1];
2472
		if (preg_match('/^(\S*)(\s+AS\s+.*)$/i', $col, $m)) {
2473
			$col = $m[1];
2474
			$args_sql = $m[2];
2475
		}
2476
		$args_sql .= $a[2];
2477
	}
2478
2479
	return array($fct, $col, $op, $val, $args_sql);
2480
}
2481
2482
// compatibilite ancienne version
2483
2484
// http://code.spip.net/@calculer_vieux_in
2485
function calculer_vieux_in($params) {
2486
	$deb = $params[0][0];
2487
	$k = count($params) - 1;
2488
	$last = $params[$k];
2489
	$j = count($last) - 1;
2490
	$last = $last[$j];
2491
	$n = isset($last->texte) ? strlen($last->texte) : 0;
2492
2493
	if (!((isset($deb->texte[0]) and $deb->texte[0] == '(')
2494
		&& (isset($last->texte[$n - 1]) and $last->texte[$n - 1] == ')'))
2495
	) {
2496
		return $params;
2497
	}
2498
	$params[0][0]->texte = substr($deb->texte, 1);
2499
	// attention, on peut avoir k=0,j=0 ==> recalculer
2500
	$last = $params[$k][$j];
2501
	$n = strlen($last->texte);
2502
	$params[$k][$j]->texte = substr($last->texte, 0, $n - 1);
2503
	$newp = array();
2504
	foreach ($params as $v) {
2505
		if ($v[0]->type != 'texte') {
2506
			$newp[] = $v;
2507
		} else {
2508
			foreach (explode(',', $v[0]->texte) as $x) {
2509
				$t = new Texte;
2510
				$t->texte = $x;
2511
				$newp[] = array($t);
2512
			}
2513
		}
2514
	}
2515
2516
	return $newp;
2517
}
2518
2519
/**
2520
 * Calcule les cas particuliers de critères de date
2521
 *
2522
 * Lorsque la colonne correspond à un critère de date, tel que
2523
 * jour, jour_relatif, jour_x, age, age_relatif, age_x...
2524
 *
2525
 * @param string $idb Identifiant de la boucle
2526
 * @param array $boucles AST du squelette
2527
 * @param string $col Nom du champ demandé
2528
 * @return string|array
2529
 *     chaine vide si ne correspond pas à une date,
2530
 *     sinon liste
2531
 *     - expression SQL de calcul de la date,
2532
 *     - nom de la colonne de date (si le calcul n'est pas relatif)
2533
 **/
2534
function calculer_critere_infixe_date($idb, &$boucles, $col) {
2535
	if (!preg_match(",^((age|jour|mois|annee)_relatif|date|mois|annee|jour|heure|age)(_[a-z]+)?$,", $col, $regs)) {
2536
		return '';
2537
	}
2538
2539
	$boucle = $boucles[$idb];
2540
	$table = $boucle->show;
2541
2542
	// si c'est une colonne de la table, ne rien faire
2543
	if (isset($table['field'][$col])) {
2544
		return '';
2545
	}
2546
2547
	if (!$table['date'] && !isset($GLOBALS['table_date'][$table['id_table']])) {
2548
		return '';
2549
	}
2550
	$pred = $date_orig = isset($GLOBALS['table_date'][$table['id_table']]) ? $GLOBALS['table_date'][$table['id_table']] : $table['date'];
2551
2552
	$col = $regs[1];
2553
	if (isset($regs[3]) and $suite = $regs[3]) {
2554
		# Recherche de l'existence du champ date_xxxx,
2555
		# si oui choisir ce champ, sinon choisir xxxx
2556
2557
		if (isset($table['field']["date$suite"])) {
2558
			$date_orig = 'date' . $suite;
2559
		} else {
2560
			$date_orig = substr($suite, 1);
2561
		}
2562
		$pred = $date_orig;
2563
	} else {
2564
		if (isset($regs[2]) and $rel = $regs[2]) {
2565
			$pred = 'date';
2566
		}
2567
	}
2568
2569
	$date_compare = "\"' . normaliser_date(" .
2570
		calculer_argument_precedent($idb, $pred, $boucles) .
2571
		") . '\"";
2572
2573
	$col_vraie = $date_orig;
2574
	$date_orig = $boucle->id_table . '.' . $date_orig;
2575
2576
	switch ($col) {
2577
		case 'date':
2578
			$col = $date_orig;
2579
			break;
2580
		case 'jour':
2581
			$col = "DAYOFMONTH($date_orig)";
2582
			break;
2583
		case 'mois':
2584
			$col = "MONTH($date_orig)";
2585
			break;
2586
		case 'annee':
2587
			$col = "YEAR($date_orig)";
2588
			break;
2589
		case 'heure':
2590
			$col = "DATE_FORMAT($date_orig, \\'%H:%i\\')";
2591
			break;
2592
		case 'age':
2593
			$col = calculer_param_date("NOW()", $date_orig);
2594
			$col_vraie = "";// comparer a un int (par defaut)
2595
			break;
2596
		case 'age_relatif':
2597
			$col = calculer_param_date($date_compare, $date_orig);
2598
			$col_vraie = "";// comparer a un int (par defaut)
2599
			break;
2600
		case 'jour_relatif':
2601
			$col = "(TO_DAYS(" . $date_compare . ")-TO_DAYS(" . $date_orig . "))";
2602
			$col_vraie = "";// comparer a un int (par defaut)
2603
			break;
2604
		case 'mois_relatif':
2605
			$col = "MONTH(" . $date_compare . ")-MONTH(" .
2606
				$date_orig . ")+12*(YEAR(" . $date_compare .
2607
				")-YEAR(" . $date_orig . "))";
2608
			$col_vraie = "";// comparer a un int (par defaut)
2609
			break;
2610
		case 'annee_relatif':
2611
			$col = "YEAR(" . $date_compare . ")-YEAR(" .
2612
				$date_orig . ")";
2613
			$col_vraie = "";// comparer a un int (par defaut)
2614
			break;
2615
	}
2616
2617
	return array($col, $col_vraie);
2618
}
2619
2620
/**
2621
 * Calcule l'expression SQL permettant de trouver un nombre de jours écoulés.
2622
 *
2623
 * Le calcul SQL retournera un nombre de jours écoulés entre la date comparée
2624
 * et la colonne SQL indiquée
2625
 *
2626
 * @param string $date_compare
2627
 *     Code PHP permettant d'obtenir le timestamp référent.
2628
 *     C'est à partir de lui que l'on compte les jours
2629
 * @param string $date_orig
2630
 *     Nom de la colonne SQL qui possède la date
2631
 * @return string
2632
 *     Expression SQL calculant le nombre de jours écoulé entre une valeur
2633
 *     de colonne SQL et une date.
2634
 **/
2635
function calculer_param_date($date_compare, $date_orig) {
2636
	if (preg_match(",'\" *\.(.*)\. *\"',", $date_compare, $r)) {
2637
		$init = "'\" . (\$x = $r[1]) . \"'";
2638
		$date_compare = '\'$x\'';
0 ignored issues
show
Unused Code introduced by
$date_compare is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
2639
	} else {
2640
		$init = $date_compare;
2641
	}
2642
2643
	return
2644
		// optimisation : mais prevoir le support SQLite avant
2645
		"TIMESTAMPDIFF(HOUR,$date_orig,$init)/24";
2646
}
2647
2648
/**
2649
 * Compile le critère {source} d'une boucle DATA
2650
 *
2651
 * Permet de déclarer le mode d'obtention des données dans une boucle
2652
 * DATA (premier argument) et les données (la suite).
2653
 *
2654
 * @example
2655
 *     (DATA){source mode, "xxxxxx", arg, arg, arg}
2656
 *     (DATA){source tableau, #LISTE{un,deux,trois}}
2657
 *
2658
 * @param string $idb Identifiant de la boucle
2659
 * @param array $boucles AST du squelette
2660
 * @param Critere $crit Paramètres du critère dans cette boucle
2661
 */
2662
function critere_DATA_source_dist($idb, &$boucles, $crit) {
2663
	$boucle = &$boucles[$idb];
2664
2665
	$args = array();
2666
	foreach ($crit->param as &$param) {
2667
		array_push($args,
2668
			calculer_liste($param, array(), $boucles, $boucles[$idb]->id_parent));
2669
	}
2670
2671
	$boucle->hash .= '
2672
	$command[\'sourcemode\'] = ' . array_shift($args) . ";\n";
2673
2674
	$boucle->hash .= '
2675
	$command[\'source\'] = array(' . join(', ', $args) . ");\n";
2676
}
2677
2678
2679
/**
2680
 * Compile le critère {datasource} d'une boucle DATA
2681
 *
2682
 * Permet de déclarer le mode d'obtention des données dans une boucle DATA
2683
 *
2684
 * @deprecated Utiliser directement le critère {source}
2685
 *
2686
 * @param string $idb Identifiant de la boucle
2687
 * @param array $boucles AST du squelette
2688
 * @param Critere $crit Paramètres du critère dans cette boucle
2689
 */
2690
function critere_DATA_datasource_dist($idb, &$boucles, $crit) {
2691
	$boucle = &$boucles[$idb];
2692
	$boucle->hash .= '
2693
	$command[\'source\'] = array(' . calculer_liste($crit->param[0], array(), $boucles, $boucles[$idb]->id_parent) . ');
2694
	$command[\'sourcemode\'] = ' . calculer_liste($crit->param[1], array(), $boucles, $boucles[$idb]->id_parent) . ';';
2695
}
2696
2697
2698
/**
2699
 * Compile le critère {datacache} d'une boucle DATA
2700
 *
2701
 * Permet de transmettre une durée de cache (time to live) utilisée
2702
 * pour certaines sources d'obtention des données (par exemple RSS),
2703
 * indiquant alors au bout de combien de temps la donnée est à réobtenir.
2704
 *
2705
 * La durée par défaut est 1 journée.
2706
 *
2707
 * @param string $idb Identifiant de la boucle
2708
 * @param array $boucles AST du squelette
2709
 * @param Critere $crit Paramètres du critère dans cette boucle
2710
 */
2711
function critere_DATA_datacache_dist($idb, &$boucles, $crit) {
2712
	$boucle = &$boucles[$idb];
2713
	$boucle->hash .= '
2714
	$command[\'datacache\'] = ' . calculer_liste($crit->param[0], array(), $boucles, $boucles[$idb]->id_parent) . ';';
2715
}
2716
2717
2718
/**
2719
 * Compile le critère {args} d'une boucle PHP
2720
 *
2721
 * Permet de passer des arguments à un iterateur non-spip
2722
 * (PHP:xxxIterator){args argument1, argument2, argument3}
2723
 *
2724
 * @param string $idb Identifiant de la boucle
2725
 * @param array $boucles AST du squelette
2726
 * @param Critere $crit Paramètres du critère dans cette boucle
2727
 */
2728 View Code Duplication
function critere_php_args_dist($idb, &$boucles, $crit) {
0 ignored issues
show
Duplication introduced by
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2729
	$boucle = &$boucles[$idb];
2730
	$boucle->hash .= '$command[\'args\']=array();';
2731
	foreach ($crit->param as $param) {
2732
		$boucle->hash .= '
2733
			$command[\'args\'][] = ' . calculer_liste($param, array(), $boucles, $boucles[$idb]->id_parent) . ';';
2734
	}
2735
}
2736
2737
/**
2738
 * Compile le critère {liste} d'une boucle DATA
2739
 *
2740
 * Passe une liste de données à l'itérateur DATA
2741
 *
2742
 * @example
2743
 *     (DATA){liste X1, X2, X3}
2744
 *     équivalent à (DATA){source tableau,#LISTE{X1, X2, X3}}
2745
 *
2746
 * @param string $idb Identifiant de la boucle
2747
 * @param array $boucles AST du squelette
2748
 * @param Critere $crit Paramètres du critère dans cette boucle
2749
 */
2750 View Code Duplication
function critere_DATA_liste_dist($idb, &$boucles, $crit) {
0 ignored issues
show
Duplication introduced by
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2751
	$boucle = &$boucles[$idb];
2752
	$boucle->hash .= "\n\t" . '$command[\'liste\'] = array();' . "\n";
2753
	foreach ($crit->param as $param) {
2754
		$boucle->hash .= "\t" . '$command[\'liste\'][] = ' . calculer_liste($param, array(), $boucles,
2755
				$boucles[$idb]->id_parent) . ";\n";
2756
	}
2757
}
2758
2759
/**
2760
 * Compile le critère {enum} d'une boucle DATA
2761
 *
2762
 * Passe les valeurs de début et de fin d'une énumération, qui seront
2763
 * vues comme une liste d'autant d'éléments à parcourir pour aller du
2764
 * début à la fin.
2765
 *
2766
 * Cela utilisera la fonction range() de PHP.
2767
 *
2768
 * @example
2769
 *     (DATA){enum Xdebut, Xfin}
2770
 *     (DATA){enum a,z}
2771
 *     (DATA){enum z,a}
2772
 *     (DATA){enum 1.0,9.2}
2773
 *
2774
 * @link http://php.net/manual/fr/function.range.php
2775
 *
2776
 * @param string $idb Identifiant de la boucle
2777
 * @param array $boucles AST du squelette
2778
 * @param Critere $crit Paramètres du critère dans cette boucle
2779
 */
2780 View Code Duplication
function critere_DATA_enum_dist($idb, &$boucles, $crit) {
0 ignored issues
show
Duplication introduced by
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2781
	$boucle = &$boucles[$idb];
2782
	$boucle->hash .= "\n\t" . '$command[\'enum\'] = array();' . "\n";
2783
	foreach ($crit->param as $param) {
2784
		$boucle->hash .= "\t" . '$command[\'enum\'][] = ' . calculer_liste($param, array(), $boucles,
2785
				$boucles[$idb]->id_parent) . ";\n";
2786
	}
2787
}
2788
2789
/**
2790
 * Compile le critère {datapath} d'une boucle DATA
2791
 *
2792
 * Extrait un chemin d'un tableau de données
2793
 *
2794
 * (DATA){datapath query.results}
2795
 *
2796
 * @param string $idb Identifiant de la boucle
2797
 * @param array $boucles AST du squelette
2798
 * @param Critere $crit Paramètres du critère dans cette boucle
2799
 */
2800 View Code Duplication
function critere_DATA_datapath_dist($idb, &$boucles, $crit) {
0 ignored issues
show
Duplication introduced by
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2801
	$boucle = &$boucles[$idb];
2802
	foreach ($crit->param as $param) {
2803
		$boucle->hash .= '
2804
			$command[\'datapath\'][] = ' . calculer_liste($param, array(), $boucles, $boucles[$idb]->id_parent) . ';';
2805
	}
2806
}
2807
2808
2809
/**
2810
 * Compile le critère {si}
2811
 *
2812
 * Le critère {si condition} est applicable à toutes les boucles et conditionne
2813
 * l'exécution de la boucle au résultat de la condition. La partie alternative
2814
 * de la boucle est alors affichée si une condition n'est pas remplie (comme
2815
 * lorsque la boucle ne ramène pas de résultat).
2816
 * La différence étant que si la boucle devait réaliser une requête SQL
2817
 * (par exemple une boucle ARTICLES), celle ci n'est pas réalisée si la
2818
 * condition n'est pas remplie.
2819
 *
2820
 * Les valeurs de la condition sont forcément extérieures à cette boucle
2821
 * (sinon il faudrait l'exécuter pour connaître le résultat, qui doit tester
2822
 * si on exécute la boucle !)
2823
 *
2824
 * Si plusieurs critères {si} sont présents, ils sont cumulés :
2825
 * si une seule des conditions n'est pas vérifiée, la boucle n'est pas exécutée.
2826
 *
2827
 * @example
2828
 *     {si #ENV{exec}|=={article}}
2829
 *     {si (#_contenu:GRAND_TOTAL|>{10})}
2830
 *     {si #AUTORISER{voir,articles}}
2831
 *
2832
 * @param string $idb Identifiant de la boucle
2833
 * @param array $boucles AST du squelette
2834
 * @param Critere $crit Paramètres du critère dans cette boucle
2835
 */
2836
function critere_si_dist($idb, &$boucles, $crit) {
2837
	$boucle = &$boucles[$idb];
2838
	// il faut initialiser 1 fois le tableau a chaque appel de la boucle
2839
	// (par exemple lorsque notre boucle est appelee dans une autre boucle)
2840
	// mais ne pas l'initialiser n fois si il y a n criteres {si } dans la boucle !
2841
	$boucle->hash .= "\n\tif (!isset(\$si_init)) { \$command['si'] = array(); \$si_init = true; }\n";
2842
	if ($crit->param) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $crit->param of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
2843
		foreach ($crit->param as $param) {
2844
			$boucle->hash .= "\t\$command['si'][] = "
2845
				. calculer_liste($param, array(), $boucles, $boucles[$idb]->id_parent) . ";\n";
2846
		}
2847
		// interdire {si 0} aussi !
2848
	} else {
2849
		$boucle->hash .= '$command[\'si\'][] = 0;';
2850
	}
2851
}
2852
2853
/**
2854
 * Compile le critère {tableau} d'une boucle POUR
2855
 *
2856
 * {tableau #XX} pour compatibilite ascendante boucle POUR
2857
 * ... préférer la notation (DATA){source tableau,#XX}
2858
 *
2859
 * @deprecated Utiliser une boucle (DATA){source tableau,#XX}
2860
 *
2861
 * @param string $idb Identifiant de la boucle
2862
 * @param array $boucles AST du squelette
2863
 * @param Critere $crit Paramètres du critère dans cette boucle
2864
 */
2865
function critere_POUR_tableau_dist($idb, &$boucles, $crit) {
2866
	$boucle = &$boucles[$idb];
2867
	$boucle->hash .= '
2868
	$command[\'source\'] = array(' . calculer_liste($crit->param[0], array(), $boucles, $boucles[$idb]->id_parent) . ');
2869
	$command[\'sourcemode\'] = \'table\';';
2870
}
2871
2872
2873
/**
2874
 * Compile le critère {noeud}
2875
 *
2876
 * Trouver tous les objets qui ont des enfants (les noeuds de l'arbre)
2877
 * {noeud}
2878
 * {!noeud} retourne les feuilles
2879
 *
2880
 * @global array $exceptions_des_tables
2881
 *
2882
 * @param string $idb Identifiant de la boucle
2883
 * @param array $boucles AST du squelette
2884
 * @param Critere $crit Paramètres du critère dans cette boucle
2885
 */
2886
function critere_noeud_dist($idb, &$boucles, $crit) {
2887
2888
	$not = $crit->not;
2889
	$boucle = &$boucles[$idb];
2890
	$primary = $boucle->primary;
2891
2892
	if (!$primary or strpos($primary, ',')) {
2893
		erreur_squelette(_T('zbug_doublon_sur_table_sans_cle_primaire'), $boucle);
2894
2895
		return;
2896
	}
2897
	$table = $boucle->type_requete;
2898
	$table_sql = table_objet_sql(objet_type($table));
2899
2900
	$id_parent = isset($GLOBALS['exceptions_des_tables'][$boucle->id_table]['id_parent']) ?
2901
		$GLOBALS['exceptions_des_tables'][$boucle->id_table]['id_parent'] :
2902
		'id_parent';
2903
2904
	$in = "IN";
0 ignored issues
show
Unused Code introduced by
$in is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
2905
	$where = array("'IN'", "'$boucle->id_table." . "$primary'", "'('.sql_get_select('$id_parent', '$table_sql').')'");
2906
	if ($not) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $not of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
2907
		$where = array("'NOT'", $where);
2908
	}
2909
2910
	$boucle->where[] = $where;
2911
}
2912
2913
/**
2914
 * Compile le critère {feuille}
2915
 *
2916
 * Trouver tous les objets qui n'ont pas d'enfants (les feuilles de l'arbre)
2917
 * {feuille}
2918
 * {!feuille} retourne les noeuds
2919
 *
2920
 * @global array $exceptions_des_tables
2921
 * @param string $idb Identifiant de la boucle
2922
 * @param array $boucles AST du squelette
2923
 * @param Critere $crit Paramètres du critère dans cette boucle
2924
 */
2925
function critere_feuille_dist($idb, &$boucles, $crit) {
2926
	$not = $crit->not;
2927
	$crit->not = $not ? false : true;
0 ignored issues
show
Documentation Bug introduced by
It seems like $not ? false : true of type boolean is incompatible with the declared type null|string of property $not.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
2928
	critere_noeud_dist($idb, $boucles, $crit);
2929
	$crit->not = $not;
2930
}
2931