Completed
Push — master ( c454b4...d048f3 )
by cam
35:45 queued 31:19
created

criteres.php ➔ critere_fusion_supprimer_dist()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 3
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
/***************************************************************************\
4
 *  SPIP, Système de publication pour l'internet                           *
5
 *                                                                         *
6
 *  Copyright © avec tendresse depuis 2001                                 *
7
 *  Arnaud Martin, Antoine Pitrou, Philippe Rivière, Emmanuel Saint-James  *
8
 *                                                                         *
9
 *  Ce programme est un logiciel libre distribué sous licence GNU/GPL.     *
10
 *  Pour plus de détails 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 https://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 https://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
	if ($not or !$id) {
75
		return (array('zbug_critere_inconnu', array('critere' => $not . $crit->op)));
76
	}
77
	$arg = kwote(calculer_argument_precedent($idb, $id, $boucles));
78
	$boucle->where[] = array("'!='", "'$boucle->id_table." . "$id'", $arg);
79
}
80
81
82
/**
83
 * Compile le critère {doublons} ou {unique}
84
 *
85
 * Ce critères enlève de la boucle les éléments déjà sauvegardés
86
 * dans un précédent critère {doublon} sur une boucle de même table.
87
 *
88
 * Il est possible de spécifier un nom au doublon tel que {doublons sommaire}
89
 *
90
 * @link https://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], $idb, $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 https://www.spip.net/3367 Le système de pagination
243
 * @link https://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]), $idb, $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]), $idb, $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]), $idb, $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 https://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], $idb, $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 https://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], $idb, $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
	$boucle = &$boucles[$idb];
544
	$not = ($crit->not ? 'NOT' : '');
545
	$serveur = $boucle->sql_serveur;
546
547
	$c = "sql_in('" .
548
		$boucle->id_table . '.' . $boucle->primary
549
		. "', lister_objets_avec_logos('" . $boucle->primary . "'), '$not', '$serveur')";
550
551
	$boucle->where[] = $c;
552
}
553
554
555
/**
556
 * Compile le critère `fusion` qui regroupe les éléments selon une colonne.
557
 *
558
 * C'est la commande SQL «GROUP BY»
559
 *
560
 * @critere
561
 * @link https://www.spip.net/5166
562
 * @example
563
 *     ```
564
 *      <BOUCLE_a(articles){fusion lang}>
565
 *     ```
566
 *
567
 * @param string $idb Identifiant de la boucle
568
 * @param array $boucles AST du squelette
569
 * @param Critere $crit Paramètres du critère dans cette boucle
570
 * @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...
571
 **/
572
function critere_fusion_dist($idb, &$boucles, $crit) {
573
	if ($t = isset($crit->param[0])) {
574
		$t = $crit->param[0];
575
		if ($t[0]->type == 'texte') {
576
			$t = $t[0]->texte;
577 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...
578
				$t = table_objet_sql($r[1]);
579
				$t = array_search($t, $boucles[$idb]->from);
580
				if ($t) {
581
					$t .= '.' . $r[2];
582
				}
583
			}
584
		} else {
585
			$t = '".'
586
				. calculer_critere_arg_dynamique($idb, $boucles, $t)
587
				. '."';
588
		}
589
	}
590
	if ($t) {
591
		$boucles[$idb]->group[] = $t;
592 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...
593
			$boucles[$idb]->select[] = $t;
594
		}
595
	} else {
596
		return (array('zbug_critere_inconnu', array('critere' => $crit->op . ' ?')));
597
	}
598
}
599
600
/**
601
 * Compile le critère `fusion_supprimer` qui supprime toutes les fusions qui le précèdent
602
 *
603
 * Ce critère retire toutes les définitions de "group by" qui le précèdent. Par exemple pour en ajouter d'autres ensuite.
604
 *
605
 * @critere
606
 * @example
607
 *     ```
608
 *      <BOUCLE_a(ARTICLES){gis}{fusion_supprimer}{fusion ville} 
609
 *     ```
610
 *
611
 * @param string $idb Identifiant de la boucle
612
 * @param array $boucles AST du squelette
613
 * @param Critere $crit Paramètres du critère dans cette boucle
614
 * @return void
615
 **/
616
function critere_fusion_supprimer_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...
617
	$boucles[$idb]->group = array();
618
}
619
620
/**
621
 * Compile le critère `{collecte}` qui permet de spécifier l'interclassement
622
 * à utiliser pour les tris de la boucle.
623
 * 
624
 * Cela permet avec le critère `{par}` de trier un texte selon 
625
 * l'interclassement indiqué. L'instruction s'appliquera sur les critères `{par}`
626
 * qui succèdent ce critère, ainsi qu'au critère `{par}` précédent
627
 * si aucun interclassement ne lui est déjà appliqué.
628
 * 
629
 * Techniquement, c'est la commande SQL "COLLATE" qui utilisée.
630
 * (elle peut être appliquée sur les order by, group by, where, like ...)
631
 * 
632
 * @example  
633
 *     - `{par titre}{collecte utf8_spanish_ci}` ou `{collecte utf8_spanish_ci}{par titre}`
634
 *     - `{par titre}{par surtitre}{collecte utf8_spanish_ci}` : 
635
 *        Seul 'surtitre' (`par` précédent) utilisera l'interclassement
636
 *     - `{collecte utf8_spanish_ci}{par titre}{par surtitre}` : 
637
 *        'titre' et 'surtitre' utiliseront l'interclassement (tous les `par` suivants)
638
 * 
639
 * @note 
640
 *     Piège sur une éventuelle écriture peu probable :
641
 *     `{par a}{collecte c1}{par b}{collecte c2}` : le tri `{par b}` 
642
 *     utiliserait l'interclassement c1 (et non c2 qui ne s'applique pas
643
 *     au `par` précédent s'il a déjà un interclassement demandé).
644
 *
645
 * @critere
646
 * @link https://www.spip.net/4028
647
 * @see critere_par_dist() Le critère `{par}`
648
 * 
649
 * @param string $idb Identifiant de la boucle
650
 * @param array $boucles AST du squelette
651
 * @param Critere $crit Paramètres du critère dans cette boucle
652
 */
653
function critere_collecte_dist($idb, &$boucles, $crit) {
654
	if (isset($crit->param[0])) {
655
		$_coll = calculer_liste($crit->param[0], $idb, $boucles, $boucles[$idb]->id_parent);
656
		$boucle = $boucles[$idb];
657
		$boucle->modificateur['collate'] = "($_coll ?' COLLATE '.$_coll:'')";
658
		$n = count($boucle->order);
659
		if ($n && (strpos($boucle->order[$n - 1], 'COLLATE') === false)) {
660
			// l'instruction COLLATE doit être placée avant ASC ou DESC
661
			// notamment lors de l'utilisation `{!par xxx}{collate yyy}`
662
			if (
663
				(false !== $i = strpos($boucle->order[$n - 1], 'ASC'))
664
				OR (false !== $i = strpos($boucle->order[$n - 1], 'DESC'))
665
			) {
666
				$boucle->order[$n - 1] = substr_replace($boucle->order[$n - 1], "' . " . $boucle->modificateur['collate'] . " . ' ", $i, 0);
667
			} else {
668
				$boucle->order[$n - 1] .= " . " . $boucle->modificateur['collate'];
669
			}
670
		}
671
	} else {
672
		return (array('zbug_critere_inconnu', array('critere' => $crit->op . " " . count($boucles[$idb]->order))));
673
	}
674
}
675
676
// https://code.spip.net/@calculer_critere_arg_dynamique
677
function calculer_critere_arg_dynamique($idb, &$boucles, $crit, $suffix = '') {
678
	$boucle = $boucles[$idb];
679
	$alt = "('" . $boucle->id_table . '.\' . $x' . $suffix . ')';
680
	$var = '$champs_' . $idb;
681
	$desc = (strpos($boucle->in, "static $var =") !== false);
682
	if (!$desc) {
683
		$desc = $boucle->show['field'];
684
		$desc = implode(',', array_map('_q', array_keys($desc)));
685
		$boucles[$idb]->in .= "\n\tstatic $var = array(" . $desc . ");";
686
	}
687
	if ($desc) {
688
		$alt = "(in_array(\$x, $var)  ? $alt :(\$x$suffix))";
689
	}
690
	$arg = calculer_liste($crit, $idb, $boucles, $boucle->id_parent);
691
692
	return "((\$x = preg_replace(\"/\\W/\",'', $arg)) ? $alt : '')";
693
}
694
695
/**
696
 * Compile le critère `{par}` qui permet d'ordonner les résultats d'une boucle
697
 *
698
 * Demande à trier la boucle selon certains champs (en SQL, la commande `ORDER BY`).
699
 * Si plusieurs tris sont demandés (plusieurs fois le critère `{par x}{par y}` dans une boucle ou plusieurs champs
700
 * séparés par des virgules dans le critère `{par x, y, z}`), ils seront appliqués dans l'ordre.
701
 *
702
 * Quelques particularités :
703
 * - `{par hasard}` : trie par hasard
704
 * - `{par num titre}` : trie par numéro de titre
705
 * - `{par multi titre}` : trie par la langue extraite d'une balise polyglotte `<multi>` sur le champ titre
706
 * - `{!par date}` : trie par date inverse en utilisant le champ date principal déclaré pour la table (si c'est un objet éditorial).
707
 * - `{!par points}` : trie par pertinence de résultat de recherche (avec le critère `{recherche}`)
708
 * - `{par FUNCTION_SQL(n)}` : trie en utilisant une fonction SQL (peut dépendre du moteur SQL utilisé).
709
 *     Exemple : `{par SUBSTRING_INDEX(titre, ".", -1)}` (tri ~ alphabétique en ignorant les numéros de titres
710
 *     (exemple erroné car faux dès qu'un titre possède un point.)).
711
 * - `{par table.champ}` : trie en effectuant une jointure sur la table indiquée.
712
 * - `{par #BALISE}` : trie sur la valeur retournée par la balise (doit être un champ de la table, ou 'hasard').
713
 *
714
 * @example
715
 *     - `{par titre}`
716
 *     - `{!par date}`
717
 *     - `{par num titre, multi titre, hasard}`
718
 *
719
 * @critere
720
 * @link https://www.spip.net/5531
721
 * @see critere_tri_dist() Le critère `{tri ...}`
722
 * @see critere_inverse_dist() Le critère `{inverse}`
723
 *
724
 * @uses critere_parinverse()
725
 *
726
 * @param string $idb Identifiant de la boucle
727
 * @param array $boucles AST du squelette
728
 * @param Critere $crit Paramètres du critère dans cette boucle
729
 */
730
function critere_par_dist($idb, &$boucles, $crit) {
731
	return critere_parinverse($idb, $boucles, $crit);
732
}
733
734
/**
735
 * Calculs pour le critère `{par}` ou `{inverse}` pour ordonner les résultats d'une boucle
736
 *
737
 * Les expressions intermédiaires `{par expr champ}` sont calculées dans des fonctions
738
 * `calculer_critere_par_expression_{expr}()` notamment `{par num champ}` ou `{par multi champ}`.
739
 *
740
 * @see critere_par_dist() Le critère `{par}` pour des exemples
741
 *
742
 * @uses calculer_critere_arg_dynamique() pour le calcul de `{par #ENV{tri}}`
743
 * @uses calculer_critere_par_hasard() pour le calcul de `{par hasard}`
744
 * @uses calculer_critere_par_champ()
745
 * @see calculer_critere_par_expression_num() pour le calcul de `{par num x}`
746
 * @see calculer_critere_par_expression_multi() pour le calcul de `{par multi x}`
747
 *
748
 * @param string $idb Identifiant de la boucle
749
 * @param array $boucles AST du squelette
750
 * @param Critere $crit Paramètres du critère dans cette boucle
751
 */
752
function critere_parinverse($idb, &$boucles, $crit) {
753
	$boucle = &$boucles[$idb];
754
755
	$sens = $collecte = '';
756
	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...
757
		$sens = " . ' DESC'";
758
	}
759
	if (isset($boucle->modificateur['collate'])) {
760
		$collecte = ' . ' . $boucle->modificateur['collate'];
761
	}
762
763
	// Pour chaque paramètre du critère
764
	foreach ($crit->param as $tri) {
765
		$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...
766
		// tris specifiés dynamiquement {par #ENV{tri}}
767
		if ($tri[0]->type != 'texte') {
768
			// calculer le order dynamique qui verifie les champs
769
			$order = calculer_critere_arg_dynamique($idb, $boucles, $tri, $sens);
770
			// ajouter 'hasard' comme possibilité de tri dynamique
771
			calculer_critere_par_hasard($idb, $boucles, $crit);
772
		}
773
		// tris textuels {par titre}
774
		else {
775
			$par = array_shift($tri);
776
			$par = $par->texte;
777
778
			// tris de la forme {par expression champ} tel que {par num titre} ou {par multi titre}
779
			if (preg_match(",^(\w+)[\s]+(.*)$,", $par, $m)) {
780
				$expression = trim($m[1]);
781
				$champ = trim($m[2]);
782
				if (function_exists($f = 'calculer_critere_par_expression_' . $expression)) {
783
					$order = $f($idb, $boucles, $crit, $tri, $champ);
784
				} else {
785
					return array('zbug_critere_inconnu', array('critere' => $crit->op . " $par"));
786
				}
787
788
			// tris de la forme {par champ} ou {par FONCTION(champ)}
789
			} elseif (preg_match(",^" . CHAMP_SQL_PLUS_FONC . '$,is', $par, $match)) {
790
				// {par FONCTION(champ)}
791
				if (count($match) > 2) {
792
					$par = substr($match[2], 1, -1);
793
					$fct = $match[1];
794
				}
795
				// quelques cas spécifiques {par hasard}, {par date}
796
				if ($par == 'hasard') {
797
					$order = calculer_critere_par_hasard($idb, $boucles, $crit);
798
				} elseif ($par == 'date' and !empty($boucle->show['date'])) {
799
					$order = "'" . $boucle->id_table . "." . $boucle->show['date'] . "'";
800
				} else {
801
					// cas général {par champ}, {par table.champ}, ...
802
					$order = calculer_critere_par_champ($idb, $boucles, $crit, $par);
803
				}
804
			}
805
806
			// on ne sait pas traiter…
807
			else {
808
				return array('zbug_critere_inconnu', array('critere' => $crit->op . " $par"));
809
			}
810
811
			// En cas d'erreur de squelette retournée par une fonction
812
			if (is_array($order)) {
813
				return $order;
814
			}
815
		}
816
817 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...
818
			$t = $m[1];
819
			if (strpos($t, '.') and !in_array($t, $boucle->select)) {
820
				$boucle->select[] = $t;
821
			}
822
		} else {
823
			$sens = '';
824
		}
825
826
		if ($fct) {
827 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...
828
				$order = "'$fct(" . $r[1] . ")'";
829
			} else {
830
				$order = "'$fct(' . $order . ')'";
831
			}
832
		}
833
		$t = $order . $collecte . $sens;
834
		if (preg_match("/^(.*)'\s*\.\s*'([^']*')$/", $t, $r)) {
835
			$t = $r[1] . $r[2];
836
		}
837
838
		$boucle->order[] = $t;
839
	}
840
}
841
842
/**
843
 * Calculs pour le critère `{par hasard}`
844
 *
845
 * Ajoute le générateur d'aléatoire au SELECT de la boucle.
846
 *
847
 * @param string $idb Identifiant de la boucle
848
 * @param array $boucles AST du squelette
849
 * @param Critere $crit Paramètres du critère dans cette boucle
850
 * @return string Clause pour le Order by
851
 */
852
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...
853
	$boucle = &$boucles[$idb];
854
	// Si ce n'est fait, ajouter un champ 'hasard' dans le select
855
	$parha = "rand() AS hasard";
856
	if (!in_array($parha, $boucle->select)) {
857
		$boucle->select[] = $parha;
858
	}
859
	return "'hasard'";
860
}
861
862
/**
863
 * Calculs pour le critère `{par num champ}` qui extrait le numéro préfixant un texte
864
 *
865
 * Tri par numéro de texte (tel que "10. titre"). Le numéro calculé est ajouté au SELECT
866
 * de la boucle. L'écriture `{par num #ENV{tri}}` est aussi prise en compte.
867
 *
868
 * @note
869
 *     Les textes sans numéro valent 0 et sont donc placés avant les titres ayant des numéros.
870
 *     Utiliser `{par sinum champ, num champ}` pour avoir le comportement inverse.
871
 *
872
 * @see calculer_critere_par_expression_sinum() pour le critère `{par sinum champ}`
873
 * @uses calculer_critere_par_champ()
874
 *
875
 * @param string $idb Identifiant de la boucle
876
 * @param array $boucles AST du squelette
877
 * @param Critere $crit Paramètres du critère dans cette boucle
878
 * @param array $tri Paramètre en cours du critère
879
 * @param string $champ Texte suivant l'expression ('titre' dans {par num titre})
880
 * @return string Clause pour le Order by
881
 */
882 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...
883
	$_champ = calculer_critere_par_champ($idb, $boucles, $crit, $champ, true);
884
	if (is_array($_champ)) {
885
		return array('zbug_critere_inconnu', array('critere' => $crit->op . " num $champ"));
886
	}
887
	$boucle = &$boucles[$idb];
888
	$texte = '0+' . $_champ;
889
	$suite = calculer_liste($tri, $idb, $boucles, $boucle->id_parent);
890
	if ($suite !== "''") {
891
		$texte = "\" . ((\$x = $suite) ? ('$texte' . \$x) : '0')" . " . \"";
892
	}
893
	$as = 'num' . ($boucle->order ? count($boucle->order) : "");
894
	$boucle->select[] = $texte . " AS $as";
895
	$order = "'$as'";
896
	return $order;
897
}
898
899
/**
900
 * Calculs pour le critère `{par sinum champ}` qui ordonne les champs avec numéros en premier
901
 *
902
 * Ajoute au SELECT la valeur 'sinum' qui vaut 0 si le champ a un numéro, 1 s'il n'en a pas.
903
 * Ainsi `{par sinum titre, num titre, titre}` mettra les éléments sans numéro en fin de liste,
904
 * contrairement à `{par num titre, titre}` seulement.
905
 *
906
 * @see calculer_critere_par_expression_num() pour le critère `{par num champ}`
907
 * @uses calculer_critere_par_champ()
908
 *
909
 * @param string $idb Identifiant de la boucle
910
 * @param array $boucles AST du squelette
911
 * @param Critere $crit Paramètres du critère dans cette boucle
912
 * @param array $tri Paramètre en cours du critère
913
 * @param string $champ Texte suivant l'expression ('titre' dans {par sinum titre})
914
 * @return string Clause pour le Order by
915
 */
916 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...
917
	$_champ = calculer_critere_par_champ($idb, $boucles, $crit, $champ, true);
918
	if (is_array($_champ)) {
919
		return array('zbug_critere_inconnu', array('critere' => $crit->op . " sinum $champ"));
920
	}
921
	$boucle = &$boucles[$idb];
922
	$texte = '0+' . $_champ;
923
	$suite = calculer_liste($tri, $idb, $boucles, $boucle->id_parent);
924
	if ($suite !== "''") {
925
		$texte = "\" . ((\$x = $suite) ? ('$texte' . \$x) : '0')" . " . \"";
926
	}
927
	$as = 'sinum' . ($boucle->order ? count($boucle->order) : "");
928
	$boucle->select[] = 'CASE (' . $texte . ') WHEN 0 THEN 1 ELSE 0 END AS ' . $as;
929
	$order = "'$as'";
930
	return $order;
931
}
932
933
934
/**
935
 * Calculs pour le critère `{par multi champ}` qui extrait la langue en cours dans les textes
936
 * ayant des balises `<multi>` (polyglottes)
937
 *
938
 * Ajoute le calcul du texte multi extrait dans le SELECT de la boucle.
939
 * Il ne peut y avoir qu'un seul critère de tri `multi` par boucle.
940
 *
941
 * @uses calculer_critere_par_champ()
942
 * @param string $idb Identifiant de la boucle
943
 * @param array $boucles AST du squelette
944
 * @param Critere $crit Paramètres du critère dans cette boucle
945
 * @param array $tri Paramètre en cours du critère
946
 * @param string $champ Texte suivant l'expression ('titre' dans {par multi titre})
947
 * @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...
948
 */
949
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...
950
	$_champ = calculer_critere_par_champ($idb, $boucles, $crit, $champ, true);
951
	if (is_array($_champ)) {
952
		return array('zbug_critere_inconnu', array('critere' => $crit->op . " multi $champ"));
953
	}
954
	$boucle = &$boucles[$idb];
955
	$boucle->select[] = "\".sql_multi('" . $_champ . "', \$GLOBALS['spip_lang']).\"";
956
	$order = "'multi'";
957
	return $order;
958
}
959
960
/**
961
 * Retourne le champ de tri demandé en ajoutant éventuellement les jointures nécessaires à la boucle.
962
 *
963
 * - si le champ existe dans la table, on l'utilise
964
 * - si c'est une exception de jointure, on l'utilise (et crée la jointure au besoin)
965
 * - si c'est un champ dont la jointure est déjà présente on la réutilise
966
 * - si c'est un champ dont la jointure n'est pas présente, on la crée.
967
 *
968
 * @param string $idb Identifiant de la boucle
969
 * @param array $boucles AST du squelette
970
 * @param Critere $crit Paramètres du critère dans cette boucle
971
 * @param string $par Nom du tri à analyser ('champ' ou 'table.champ')
972
 * @param bool $raw Retourne le champ pour le compilateur ("'alias.champ'") ou brut ('alias.champ')
973
 * @return array|string
974
 */
975
function calculer_critere_par_champ($idb, &$boucles, $crit,  $par, $raw = false) {
976
	$boucle = &$boucles[$idb];
977
	$desc = $boucle->show;
978
979
	// le champ existe dans la table, pas de souci (le plus commun)
980
	if (isset($desc['field'][$par])) {
981
		$par = $boucle->id_table . "." . $par;
982
	}
983
	// le champ est peut être une jointure
984
	else {
985
		$table = $table_alias = false; // toutes les tables de jointure possibles
986
		$champ = $par;
987
988
		// le champ demandé est une exception de jointure {par titre_mot}
989
		if (isset($GLOBALS['exceptions_des_jointures'][$par])) {
990
			list($table, $champ) = $GLOBALS['exceptions_des_jointures'][$par];
991
		} // la table de jointure est explicitement indiquée {par truc.muche}
992
		elseif (preg_match("/^([^,]*)\.(.*)$/", $par, $r)) {
993
			list(, $table, $champ) = $r;
994
			$table_alias = $table; // c'est peut-être un alias de table {par L1.titre}
995
			$table = table_objet_sql($table);
996
		}
997
998
		// Si on connait la table d'arrivée, on la demande donc explicitement
999
		// Sinon on cherche le champ dans les tables possibles de jointures
1000
		// Si la table est déjà dans le from, on la réutilise.
1001
		if ($infos = chercher_champ_dans_tables($champ, $boucle->from, $boucle->sql_serveur, $table)) {
1002
			$par = $infos['alias'] . "." . $champ;
1003
		} elseif (
1004
			$boucle->jointures_explicites
1005
			and $alias = trouver_jointure_champ($champ, $boucle, explode(' ', $boucle->jointures_explicites), false, $table)
1006
		) {
1007
			$par = $alias . "." . $champ;
1008
		} elseif ($alias = trouver_jointure_champ($champ, $boucle, $boucle->jointures, false, $table)) {
1009
			$par = $alias . "." . $champ;
1010
		// en spécifiant directement l'alias {par L2.titre} (situation hasardeuse tout de même)
1011
		} elseif (
1012
			$table_alias
1013
			and isset($boucle->from[$table_alias])
1014
			and $infos = chercher_champ_dans_tables($champ, $boucle->from, $boucle->sql_serveur, $boucle->from[$table_alias])
1015
		) {
1016
			$par = $infos['alias'] . "." . $champ;
1017
		} elseif ($table) {
1018
			// On avait table + champ, mais on ne les a pas trouvés
1019
			return array('zbug_critere_inconnu', array('critere' => $crit->op . " $par"));
1020
		} else {
1021
			// Sinon tant pis, ca doit etre un champ synthetise (cf points)
1022
		}
1023
	}
1024
1025
	return $raw ? $par : "'$par'";
1026
}
1027
1028
/**
1029
 * Retourne un champ de tri en créant une jointure
1030
 * si la table n'est pas présente dans le from de la boucle.
1031
 *
1032
 * @deprecated
1033
 * @param string $table Table du champ désiré
1034
 * @param string $champ Champ désiré
1035
 * @param Boucle $boucle Boucle en cours de compilation
1036
 * @return string Champ pour le compilateur si trouvé, tel que "'alias.champ'", sinon vide.
1037
 */
1038
function critere_par_joint($table, $champ, &$boucle) {
1039
	$t = array_search($table, $boucle->from);
1040
	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...
1041
		$t = trouver_jointure_champ($champ, $boucle);
1042
	}
1043
	return !$t ? '' : ("'" . $t . '.' . $champ . "'");
1044
}
1045
1046
/**
1047
 * Compile le critère `{inverse}` qui inverse l'ordre utilisé par le précédent critère `{par}`
1048
 *
1049
 * Accèpte un paramètre pour déterminer le sens : `{inverse #X}` utilisera un tri croissant (ASC) 
1050
 * si la valeur retournée par `#X` est considérée vrai (`true`),
1051
 * le sens contraire (DESC) sinon.
1052
 * 
1053
 * @example
1054
 *     - `{par date}{inverse}`, équivalent à `{!par date}`
1055
 *     - `{par date}{inverse #ENV{sens}}` utilise la valeur d'environnement sens pour déterminer le sens.
1056
 *
1057
 * @critere
1058
 * @see critere_par_dist() Le critère `{par}`
1059
 * @link https://www.spip.net/5530
1060
 * @uses critere_parinverse()
1061
 *
1062
 * @param string $idb Identifiant de la boucle
1063
 * @param array $boucles AST du squelette
1064
 * @param Critere $crit Paramètres du critère dans cette boucle
1065
 */
1066
function critere_inverse_dist($idb, &$boucles, $crit) {
1067
1068
	$boucle = &$boucles[$idb];
1069
	// Classement par ordre inverse
1070
	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...
1071
		critere_parinverse($idb, $boucles, $crit);
1072
	} else {
1073
		$order = "' DESC'";
1074
		// Classement par ordre inverse fonction eventuelle de #ENV{...}
1075 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...
1076
			$critere = calculer_liste($crit->param[0], $idb, $boucles, $boucles[$idb]->id_parent);
1077
			$order = "(($critere)?' DESC':'')";
1078
		}
1079
1080
		$n = count($boucle->order);
1081
		if (!$n) {
1082
			if (isset($boucle->default_order[0])) {
1083
				$boucle->default_order[0] .= ' . " DESC"';
1084
			} else {
1085
				$boucle->default_order[] = ' DESC';
1086
			}
1087
		} else {
1088
			$t = $boucle->order[$n - 1] . " . $order";
1089
			if (preg_match("/^(.*)'\s*\.\s*'([^']*')$/", $t, $r)) {
1090
				$t = $r[1] . $r[2];
1091
			}
1092
			$boucle->order[$n - 1] = $t;
1093
		}
1094
	}
1095
}
1096
1097
/**
1098
 * {par_ordre_liste champ,#LISTE{...}} pour trier selon une liste
1099
 * @param $idb
1100
 * @param $boucles
1101
 * @param $crit
1102
 * @return array|string
0 ignored issues
show
Documentation introduced by
Should the return type not be array|null? Also, consider making the array more specific, something like array<String>, or 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.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
1103
 */
1104
function critere_par_ordre_liste_dist($idb, &$boucles, $crit){
1105
	$boucle = &$boucles[$idb];
1106
1107
	$sens = $collecte = '';
0 ignored issues
show
Unused Code introduced by
$collecte 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...
1108
	if ($crit->not){
1109
		$sens = " . ' DESC'";
1110
	}
1111
1112
	$crit2 = clone $crit;
1113
	$crit2->not = false;
1114
	$crit2->param = [reset($crit->param)];
1115
	$res = critere_parinverse($idb, $boucles, $crit2);
1116
1117
	// erreur ?
1118
	if (is_array($res)){
1119
		return $res;
1120
	}
1121
1122
	$_order = array_pop($boucle->order);
1123
1124
	$_liste = calculer_liste($crit->param[1], array(), $boucles, $boucles[$idb]->id_parent);
1125
	$boucle->order[] = "'FIELD(' . $_order . ',' . ((\$zl=formate_liste_critere_par_ordre_liste($_liste,'" . $boucle->sql_serveur . "')) ? \$zl : '0').')'$sens";
1126
}
1127
1128
1129
// https://code.spip.net/@critere_agenda_dist
1130
function critere_agenda_dist($idb, &$boucles, $crit) {
1131
	$params = $crit->param;
1132
1133
	if (count($params) < 1) {
1134
		return array('zbug_critere_inconnu', array('critere' => $crit->op . " ?"));
1135
	}
1136
1137
	$boucle = &$boucles[$idb];
1138
	$parent = $boucle->id_parent;
1139
	$fields = $boucle->show['field'];
1140
1141
	$date = array_shift($params);
1142
	$type = array_shift($params);
1143
1144
	// la valeur $type doit etre connue a la compilation
1145
	// donc etre forcement reduite a un litteral unique dans le source
1146
	$type = is_object($type[0]) ? $type[0]->texte : null;
1147
1148
	// La valeur date doit designer un champ de la table SQL.
1149
	// Si c'est un litteral unique dans le source, verifier a la compil,
1150
	// sinon synthetiser le test de verif pour execution ulterieure
1151
	// On prendra arbitrairement le premier champ si test negatif.
1152
	if ((count($date) == 1) and ($date[0]->type == 'texte')) {
1153
		$date = $date[0]->texte;
1154
		if (!isset($fields[$date])) {
1155
			return array('zbug_critere_inconnu', array('critere' => $crit->op . " " . $date));
1156
		}
1157
	} else {
1158
		$a = calculer_liste($date, $idb, $boucles, $parent);
1159
		$noms = array_keys($fields);
1160
		$defaut = $noms[0];
1161
		$noms = join(" ", $noms);
1162
		# bien laisser 2 espaces avant $nom pour que strpos<>0
1163
		$cond = "(\$a=strval($a))AND\nstrpos(\"  $noms \",\" \$a \")";
1164
		$date = "'.(($cond)\n?\$a:\"$defaut\").'";
1165
	}
1166
	$annee = $params ? array_shift($params) : "";
1167
	$annee = "\n" . 'sprintf("%04d", ($x = ' .
1168
		calculer_liste($annee, $idb, $boucles, $parent) .
1169
		') ? $x : date("Y"))';
1170
1171
	$mois = $params ? array_shift($params) : "";
1172
	$mois = "\n" . 'sprintf("%02d", ($x = ' .
1173
		calculer_liste($mois, $idb, $boucles, $parent) .
1174
		') ? $x : date("m"))';
1175
1176
	$jour = $params ? array_shift($params) : "";
1177
	$jour = "\n" . 'sprintf("%02d", ($x = ' .
1178
		calculer_liste($jour, $idb, $boucles, $parent) .
1179
		') ? $x : date("d"))';
1180
1181
	$annee2 = $params ? array_shift($params) : "";
1182
	$annee2 = "\n" . 'sprintf("%04d", ($x = ' .
1183
		calculer_liste($annee2, $idb, $boucles, $parent) .
1184
		') ? $x : date("Y"))';
1185
1186
	$mois2 = $params ? array_shift($params) : "";
1187
	$mois2 = "\n" . 'sprintf("%02d", ($x = ' .
1188
		calculer_liste($mois2, $idb, $boucles, $parent) .
1189
		') ? $x : date("m"))';
1190
1191
	$jour2 = $params ? array_shift($params) : "";
1192
	$jour2 = "\n" . 'sprintf("%02d", ($x = ' .
1193
		calculer_liste($jour2, $idb, $boucles, $parent) .
1194
		') ? $x : date("d"))';
1195
1196
	$date = $boucle->id_table . ".$date";
1197
1198
	$quote_end = ",'" . $boucle->sql_serveur . "','text'";
1199
	if ($type == 'jour') {
1200
		$boucle->where[] = array(
1201
			"'='",
1202
			"'DATE_FORMAT($date, \'%Y%m%d\')'",
1203
			("sql_quote($annee . $mois . $jour$quote_end)")
1204
		);
1205
	} elseif ($type == 'mois') {
1206
		$boucle->where[] = array(
1207
			"'='",
1208
			"'DATE_FORMAT($date, \'%Y%m\')'",
1209
			("sql_quote($annee . $mois$quote_end)")
1210
		);
1211
	} elseif ($type == 'semaine') {
1212
		$boucle->where[] = array(
1213
			"'AND'",
1214
			array(
1215
				"'>='",
1216
				"'DATE_FORMAT($date, \'%Y%m%d\')'",
1217
				("date_debut_semaine($annee, $mois, $jour)")
1218
			),
1219
			array(
1220
				"'<='",
1221
				"'DATE_FORMAT($date, \'%Y%m%d\')'",
1222
				("date_fin_semaine($annee, $mois, $jour)")
1223
			)
1224
		);
1225
	} elseif (count($crit->param) > 2) {
1226
		$boucle->where[] = array(
1227
			"'AND'",
1228
			array(
1229
				"'>='",
1230
				"'DATE_FORMAT($date, \'%Y%m%d\')'",
1231
				("sql_quote($annee . $mois . $jour$quote_end)")
1232
			),
1233
			array("'<='", "'DATE_FORMAT($date, \'%Y%m%d\')'", ("sql_quote($annee2 . $mois2 . $jour2$quote_end)"))
1234
		);
1235
	}
1236
	// sinon on prend tout
1237
}
1238
1239
1240
/**
1241
 * Compile les critères {i,j} et {i/j}
1242
 *
1243
 * Le critère {i,j} limite l'affiche de la boucle en commançant l'itération
1244
 * au i-ème élément, et pour j nombre d'éléments.
1245
 * Le critère {n-i,j} limite en commençant au n moins i-ème élément de boucle
1246
 * Le critère {i,n-j} limite en terminant au n moins j-ème élément de boucle.
1247
 *
1248
 * Le critère {i/j} affiche une part d'éléments de la boucle.
1249
 * Commence à i*n/j élément et boucle n/j éléments. {2/4} affiche le second
1250
 * quart des éléments d'une boucle.
1251
 *
1252
 * Traduit si possible (absence de n dans {i,j}) la demande en une
1253
 * expression LIMIT du gestionnaire SQL
1254
 *
1255
 * @param string $idb Identifiant de la boucle
1256
 * @param array $boucles AST du squelette
1257
 * @param Critere $crit Paramètres du critère dans cette boucle
1258
 * @return void
1259
 **/
1260
function calculer_critere_parties($idb, &$boucles, $crit) {
1261
	$boucle = &$boucles[$idb];
1262
	$a1 = $crit->param[0];
1263
	$a2 = $crit->param[1];
1264
	$op = $crit->op;
1265
1266
	list($a11, $a12) = calculer_critere_parties_aux($idb, $boucles, $a1);
1267
	list($a21, $a22) = calculer_critere_parties_aux($idb, $boucles, $a2);
1268
1269
	if (($op == ',') && (is_numeric($a11) && (is_numeric($a21)))) {
1270
		$boucle->limit = $a11 . ',' . $a21;
1271
	} else {
1272
		// 3 dans {1/3}, {2,3} ou {1,n-3}
1273
		$boucle->total_parties = ($a21 != 'n') ? $a21 : $a22;
1274
		// 2 dans {2/3}, {2,5}, {n-2,1}
1275
		$partie = ($a11 != 'n') ? $a11 : $a12;
1276
		$mode = (($op == '/') ? '/' :
1277
			(($a11 == 'n') ? '-' : '+') . (($a21 == 'n') ? '-' : '+'));
1278
		// cas simple {0,#ENV{truc}} compilons le en LIMIT :
1279
		if ($a11 !== 'n' and $a21 !== 'n' and $mode == "++" and $op == ',') {
1280
			$boucle->limit =
1281
				(is_numeric($a11) ? "'$a11'" : $a11)
1282
				. ".','."
1283
				. (is_numeric($a21) ? "'$a21'" : $a21);
1284
		} else {
1285
			calculer_parties($boucles, $idb, $partie, $mode);
1286
		}
1287
	}
1288
}
1289
1290
/**
1291
 * Compile certains critères {i,j} et {i/j}
1292
 *
1293
 * Calcule une expression déterminant $debut_boucle et $fin_boucle (le
1294
 * début et la fin des éléments de la boucle qui doivent être affichés)
1295
 * et les déclare dans la propriété «mode_partie» de la boucle, qui se
1296
 * charge également de déplacer le pointeur de boucle sur le premier
1297
 * élément à afficher.
1298
 *
1299
 * Place dans la propriété partie un test vérifiant que l'élément de
1300
 * boucle en cours de lecture appartient bien à la plage autorisée.
1301
 * Trop tôt, passe à l'élément suivant, trop tard, sort de l'itération de boucle.
1302
 *
1303
 * @param array $boucles AST du squelette
1304
 * @param string $id_boucle Identifiant de la boucle
1305
 * @param string $debut Valeur ou code pour trouver le début (i dans {i,j})
1306
 * @param string $mode
1307
 *     Mode (++, p+, +- ...) : 2 signes début & fin
1308
 *     - le signe - indique
1309
 *       -- qu'il faut soustraire debut du total {n-3,x}. 3 étant $debut
1310
 *       -- qu'il faut raccourcir la fin {x,n-3} de 3 elements. 3 étant $total_parties
1311
 *     - le signe p indique une pagination
1312
 * @return void
1313
 **/
1314
function calculer_parties(&$boucles, $id_boucle, $debut, $mode) {
1315
	$total_parties = $boucles[$id_boucle]->total_parties;
1316
1317
	preg_match(",([+-/p])([+-/])?,", $mode, $regs);
1318
	list(, $op1, $op2) = array_pad($regs, 3, null);
1319
	$nombre_boucle = "\$Numrows['$id_boucle']['total']";
1320
	// {1/3}
1321
	if ($op1 == '/') {
1322
		$pmoins1 = is_numeric($debut) ? ($debut - 1) : "($debut-1)";
1323
		$totpos = is_numeric($total_parties) ? ($total_parties) :
1324
			"($total_parties ? $total_parties : 1)";
1325
		$fin = "ceil(($nombre_boucle * $debut )/$totpos) - 1";
1326
		$debut = !$pmoins1 ? 0 : "ceil(($nombre_boucle * $pmoins1)/$totpos);";
1327
	} else {
1328
		// cas {n-1,x}
1329
		if ($op1 == '-') {
1330
			$debut = "$nombre_boucle - $debut;";
1331
		}
1332
1333
		// cas {x,n-1}
1334
		if ($op2 == '-') {
1335
			$fin = '$debut_boucle + ' . $nombre_boucle . ' - '
1336
				. (is_numeric($total_parties) ? ($total_parties + 1) :
1337
					($total_parties . ' - 1'));
1338
		} else {
1339
			// {x,1} ou {pagination}
1340
			$fin = '$debut_boucle'
1341
				. (is_numeric($total_parties) ?
1342
					(($total_parties == 1) ? "" : (' + ' . ($total_parties - 1))) :
1343
					('+' . $total_parties . ' - 1'));
1344
		}
1345
1346
		// {pagination}, gerer le debut_xx=-1 pour tout voir
1347
		if ($op1 == 'p') {
1348
			$debut .= ";\n	\$debut_boucle = ((\$tout=(\$debut_boucle == -1))?0:(\$debut_boucle))";
1349
			$debut .= ";\n	\$debut_boucle = max(0,min(\$debut_boucle,floor(($nombre_boucle-1)/($total_parties))*($total_parties)))";
1350
			$fin = "(\$tout ? $nombre_boucle : $fin)";
1351
		}
1352
	}
1353
1354
	// Notes :
1355
	// $debut_boucle et $fin_boucle sont les indices SQL du premier
1356
	// et du dernier demandes dans la boucle : 0 pour le premier,
1357
	// n-1 pour le dernier ; donc total_boucle = 1 + debut - fin
1358
	// Utiliser min pour rabattre $fin_boucle sur total_boucle.
1359
1360
	$boucles[$id_boucle]->mode_partie = "\n\t"
1361
		. '$debut_boucle = ' . $debut . ";\n	"
1362
		. "\$debut_boucle = intval(\$debut_boucle);\n	"
1363
		. '$fin_boucle = min(' . $fin . ", \$Numrows['$id_boucle']['total'] - 1);\n	"
1364
		. '$Numrows[\'' . $id_boucle . "']['grand_total'] = \$Numrows['$id_boucle']['total'];\n	"
1365
		. '$Numrows[\'' . $id_boucle . '\']["total"] = max(0,$fin_boucle - $debut_boucle + 1);'
1366
		. "\n\tif (\$debut_boucle>0"
1367
		. " AND \$debut_boucle < \$Numrows['$id_boucle']['grand_total']"
1368
		. " AND \$iter->seek(\$debut_boucle,'continue'))"
1369
		. "\n\t\t\$Numrows['$id_boucle']['compteur_boucle'] = \$debut_boucle;\n\t";
1370
1371
	$boucles[$id_boucle]->partie = "
1372
		if (\$Numrows['$id_boucle']['compteur_boucle'] <= \$debut_boucle) continue;
1373
		if (\$Numrows['$id_boucle']['compteur_boucle']-1 > \$fin_boucle) break;";
1374
}
1375
1376
/**
1377
 * Analyse un des éléments des critères {a,b} ou {a/b}
1378
 *
1379
 * Pour l'élément demandé (a ou b) retrouve la valeur de l'élément,
1380
 * et de combien il est soustrait si c'est le cas comme dans {a-3,b}
1381
 *
1382
 * @param string $idb Identifiant de la boucle
1383
 * @param array $boucles AST du squelette
1384
 * @param array $param Paramètre à analyser (soit a, soit b dans {a,b} ou {a/b})
1385
 * @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...
1386
 **/
1387
function calculer_critere_parties_aux($idb, &$boucles, $param) {
1388
	if ($param[0]->type != 'texte') {
1389
		$a1 = calculer_liste(array($param[0]), $idb, $boucles, $boucles[$idb]->id_parent);
1390
		if (isset($param[1]->texte)) {
1391
			preg_match(',^ *(-([0-9]+))? *$,', $param[1]->texte, $m);
1392
1393
			return array("intval($a1)", ((isset($m[2]) and $m[2]) ? $m[2] : 0));
1394
		} else {
1395
			return array("intval($a1)", 0);
1396
		}
1397
	} else {
1398
		preg_match(',^ *(([0-9]+)|n) *(- *([0-9]+)? *)?$,', $param[0]->texte, $m);
1399
		$a1 = $m[1];
1400
		if (empty($m[3])) {
1401
			return array($a1, 0);
1402
		} elseif (!empty($m[4])) {
1403
			return array($a1, $m[4]);
1404
		} else {
1405
			return array($a1, calculer_liste(array($param[1]), $idb, $boucles, $boucles[$idb]->id_parent));
1406
		}
1407
	}
1408
}
1409
1410
1411
/**
1412
 * Compile les critères d'une boucle
1413
 *
1414
 * Cette fonction d'aiguillage cherche des fonctions spécifiques déclarées
1415
 * pour chaque critère demandé, dans l'ordre ci-dessous :
1416
 *
1417
 * - critere_{serveur}_{table}_{critere}, sinon avec _dist
1418
 * - critere_{serveur}_{critere}, sinon avec _dist
1419
 * - critere_{table}_{critere}, sinon avec _dist
1420
 * - critere_{critere}, sinon avec _dist
1421
 * - calculer_critere_defaut, sinon avec _dist
1422
 *
1423
 * Émet une erreur de squelette si un critère retourne une erreur.
1424
 *
1425
 * @param string $idb
1426
 *     Identifiant de la boucle
1427
 * @param array $boucles
1428
 *     AST du squelette
1429
 * @return string|array
1430
 *     string : Chaine vide sans erreur
1431
 *     array : Erreur sur un des critères
1432
 **/
1433
function calculer_criteres($idb, &$boucles) {
1434
	$msg = '';
1435
	$boucle = $boucles[$idb];
1436
	$table = strtoupper($boucle->type_requete);
1437
	$serveur = strtolower($boucle->sql_serveur);
1438
1439
	$defaut = charger_fonction('DEFAUT', 'calculer_critere');
1440
	// s'il y avait une erreur de syntaxe, propager cette info
1441
	if (!is_array($boucle->criteres)) {
1442
		return array();
1443
	}
1444
1445
	foreach ($boucle->criteres as $crit) {
1446
		$critere = $crit->op;
1447
		// critere personnalise ?
1448
		if (
1449
			(!$serveur or
1450
				((!function_exists($f = "critere_" . $serveur . "_" . $table . "_" . $critere))
1451
					and (!function_exists($f = $f . "_dist"))
1452
					and (!function_exists($f = "critere_" . $serveur . "_" . $critere))
1453
					and (!function_exists($f = $f . "_dist"))
1454
				)
1455
			)
1456
			and (!function_exists($f = "critere_" . $table . "_" . $critere))
1457
			and (!function_exists($f = $f . "_dist"))
1458
			and (!function_exists($f = "critere_" . $critere))
1459
			and (!function_exists($f = $f . "_dist"))
1460
		) {
1461
			// fonction critere standard
1462
			$f = $defaut;
1463
		}
1464
		// compile le critere
1465
		$res = $f($idb, $boucles, $crit);
1466
1467
		// Gestion centralisee des erreurs pour pouvoir propager
1468
		if (is_array($res)) {
1469
			$msg = $res;
1470
			erreur_squelette($msg, $boucle);
1471
		}
1472
	}
1473
1474
	return $msg;
1475
}
1476
1477
/**
1478
 * Désemberlificote les guillements et échappe (ou fera échapper) le contenu...
1479
 *
1480
 * Madeleine de Proust, revision MIT-1958 sqq, revision CERN-1989
1481
 * hum, c'est kwoi cette fonxion ? on va dire qu'elle desemberlificote les guillemets...
1482
 *
1483
 * https://code.spip.net/@kwote
1484
 *
1485
 * @param string $lisp Code compilé
1486
 * @param string $serveur Connecteur de bdd utilisé
1487
 * @param string $type Type d'échappement (char, int...)
1488
 * @return string         Code compilé rééchappé
1489
 */
1490
function kwote($lisp, $serveur = '', $type = '') {
1491
	if (preg_match(_CODE_QUOTE, $lisp, $r)) {
1492
		return $r[1] . "\"" . sql_quote(str_replace(array("\\'", "\\\\"), array("'", "\\"), $r[2]), $serveur, $type) . "\"";
1493
	} else {
1494
		return "sql_quote($lisp, '$serveur', '" . str_replace("'", "\\'", $type) . "')";
1495
	}
1496
}
1497
1498
1499
/**
1500
 * Compile un critère possédant l'opérateur IN : {xx IN yy}
1501
 *
1502
 * Permet de restreindre un champ sur une liste de valeurs tel que
1503
 * {id_article IN 3,4} {id_article IN #LISTE{3,4}}
1504
 *
1505
 * Si on a une liste de valeurs dans #ENV{x}, utiliser la double etoile
1506
 * pour faire par exemple {id_article IN #ENV**{liste_articles}}
1507
 *
1508
 * @param string $idb Identifiant de la boucle
1509
 * @param array $boucles AST du squelette
1510
 * @param Critere $crit Paramètres du critère dans cette boucle
1511
 * @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...
1512
 **/
1513
function critere_IN_dist($idb, &$boucles, $crit) {
1514
	$r = calculer_critere_infixe($idb, $boucles, $crit);
1515
	if (!$r) {
1516
		return (array('zbug_critere_inconnu', array('critere' => $crit->op . " ?")));
1517
	}
1518
	list($arg, $op, $val, $col, $where_complement) = $r;
1519
1520
	$in = critere_IN_cas($idb, $boucles, $crit->not ? 'NOT' : ($crit->exclus ? 'exclus' : ''), $arg, $op, $val, $col);
1521
1522
	//	inserer la condition; exemple: {id_mot ?IN (66, 62, 64)}
1523
	$where = $in;
1524
	if ($crit->cond) {
1525
		$pred = calculer_argument_precedent($idb, $col, $boucles);
1526
		$where = array("'?'", $pred, $where, "''");
1527
		if ($where_complement) // condition annexe du type "AND (objet='article')"
1528
		{
1529
			$where_complement = array("'?'", $pred, $where_complement, "''");
1530
		}
1531
	}
1532 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...
1533
		if (!preg_match(",^L[0-9]+[.],", $arg)) {
1534
			$where = array("'NOT'", $where);
1535
		} else
1536
			// un not sur un critere de jointure se traduit comme un NOT IN avec une sous requete
1537
			// c'est une sous requete identique a la requete principale sous la forme (SELF,$select,$where) avec $select et $where qui surchargent
1538
		{
1539
			$where = array(
1540
				"'NOT'",
1541
				array(
1542
					"'IN'",
1543
					"'" . $boucles[$idb]->id_table . "." . $boucles[$idb]->primary . "'",
1544
					array("'SELF'", "'" . $boucles[$idb]->id_table . "." . $boucles[$idb]->primary . "'", $where)
1545
				)
1546
			);
1547
		}
1548
	}
1549
1550
	$boucles[$idb]->where[] = $where;
1551
	if ($where_complement) // condition annexe du type "AND (objet='article')"
1552
	{
1553
		$boucles[$idb]->where[] = $where_complement;
1554
	}
1555
}
1556
1557
// https://code.spip.net/@critere_IN_cas
1558
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...
1559
	static $num = array();
1560
	$descr = $boucles[$idb]->descr;
1561
	$cpt = &$num[$descr['nom']][$descr['gram']][$idb];
1562
1563
	$var = '$in' . $cpt++;
1564
	$x = "\n\t$var = array();";
1565
	foreach ($val as $k => $v) {
1566
		if (preg_match(",^(\n//.*\n)?'(.*)'$,", $v, $r)) {
1567
			// optimiser le traitement des constantes
1568
			if (is_numeric($r[2])) {
1569
				$x .= "\n\t$var" . "[]= $r[2];";
1570
			} else {
1571
				$x .= "\n\t$var" . "[]= " . sql_quote($r[2]) . ";";
1572
			}
1573
		} else {
1574
			// Pour permettre de passer des tableaux de valeurs
1575
			// on repere l'utilisation brute de #ENV**{X},
1576
			// c'est-a-dire sa  traduction en ($PILE[0][X]).
1577
			// et on deballe mais en rajoutant l'anti XSS
1578
			$x .= "\n\tif (!(is_array(\$a = ($v))))\n\t\t$var" . "[]= \$a;\n\telse $var = array_merge($var, \$a);";
1579
		}
1580
	}
1581
1582
	$boucles[$idb]->in .= $x;
1583
1584
	// inserer le tri par defaut selon les ordres du IN ...
1585
	// avec une ecriture de type FIELD qui degrade les performances (du meme ordre qu'un regexp)
1586
	// et que l'on limite donc strictement aux cas necessaires :
1587
	// si ce n'est pas un !IN, et si il n'y a pas d'autre order dans la boucle
1588
	if (!$crit2) {
1589
		$boucles[$idb]->default_order[] = "((!\$zqv=sql_quote($var) OR \$zqv===\"''\") ? 0 : ('FIELD($arg,' . \$zqv . ')'))";
1590
	}
1591
1592
	return "sql_in('$arg', $var" . ($crit2 == 'NOT' ? ",'NOT'" : "") . ")";
1593
}
1594
1595
/**
1596
 * Compile le critère {where}
1597
 *
1598
 * Ajoute une contrainte sql WHERE, tout simplement pour faire le pont
1599
 * entre php et squelettes, en utilisant la syntaxe attendue par
1600
 * la propriété $where d'une Boucle.
1601
 *
1602
 * @param string $idb Identifiant de la boucle
1603
 * @param array $boucles AST du squelette
1604
 * @param Critere $crit Paramètres du critère dans cette boucle
1605
 * @return void
1606
 */
1607
function critere_where_dist($idb, &$boucles, $crit) {
1608
	$boucle = &$boucles[$idb];
1609 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...
1610
		$_where = calculer_liste($crit->param[0], $idb, $boucles, $boucle->id_parent);
1611
	} else {
1612
		$_where = 'spip_sanitize_from_request(@$Pile[0]["where"],"where","vide")';
1613
	}
1614
1615
	if ($crit->cond) {
1616
		$_where = "((\$zzw = $_where) ? \$zzw : '')";
1617
	}
1618
1619
	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...
1620
		$_where = "array('NOT',$_where)";
1621
	}
1622
1623
	$boucle->where[] = $_where;
1624
}
1625
1626
/**
1627
 * Compile le critère `{id_?}`
1628
 *
1629
 * Ajoute automatiquement à la boucle des contraintes (nommées sélections conditionnelles)
1630
 * équivalentes à `{id_article ?}{id_rubrique ?}...`, adaptées à la table en cours d’utilisation.
1631
 *
1632
 * Les champs sélectionnés par défaut sont :
1633
 * - chaque champ id_xx de la table en cours d’utilisation (par exemple id_secteur sur la boucle ARTICLES)
1634
 * - un champ 'objet', si cette table en dispose
1635
 * - chaque clé primaire des tables des objets éditoriaux, s’ils sont éditables et liables (par exemple id_mot).
1636
 *
1637
 * @example
1638
 *     ```
1639
 *      <BOUCLE_liste_articles(ARTICLES){id_?}{tout}> ...
1640
 *      Est équivalent (selon les plugins actifs) à :
1641
 *      <BOUCLE_liste_articles(ARTICLES){id_article?}{id_rubrique?}{id_secteur?}{id_trad?}{id_mot?}{id_document?} ... {tout}> ...
1642
 *     ```
1643
 *
1644
 * @uses lister_champs_selection_conditionnelle()
1645
 * @param string $idb Identifiant de la boucle
1646
 * @param array $boucles AST du squelette
1647
 * @param Critere $crit Paramètres du critère dans cette boucle
1648
 * @return void
1649
 */
1650
function critere_id__dist($idb, &$boucles, $crit) {
1651
	/** @var Boucle $boucle */
1652
	$boucle = $boucles[$idb];
1653
1654
	$champs = lister_champs_id_conditionnel(
1655
		$boucle->show['table'],
1656
		$boucle->show,
1657
		$boucle->sql_serveur
1658
	);
1659
1660
	// ne pas tenir compte des critères identiques déjà présents.
1661
	if (!empty($boucle->modificateur['criteres'])) {
1662
		$champs = array_diff($champs, array_keys($boucle->modificateur['criteres']));
1663
	}
1664
	// nous aider en mode debug.
1665
	$boucle->debug[] = "id_ : " . implode(', ', $champs);
1666
	$boucle->modificateur['id_'] = $champs;
1667
1668
	// créer un critère {id_xxx?} de chaque champ retenu
1669
	foreach ($champs as $champ) {
1670
		$critere_id_table = new Critere;
1671
		$critere_id_table->op = $champ;
1672
		$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...
1673
		$critere_id_table->ligne = $crit->ligne;
1674
		calculer_critere_DEFAUT_dist($idb, $boucles, $critere_id_table);
1675
	}
1676
}
1677
1678
/**
1679
 * Liste les champs qui peuvent servir de selection conditionnelle à une table SQL
1680
 *
1681
 * Retourne, pour la table demandée :
1682
 * - chaque champ id_xx de la table en cours d’utilisation (par exemple id_secteur sur la boucle ARTICLES)
1683
 * - un champ 'objet' si la table le contient (pour les tables avec objet / id_objet par exemple)
1684
 * - chaque clé primaire des tables des objets éditoriaux qui peuvent se lier facilement à cette table,
1685
 * -- soit parce que sa clé primaire de la table demandée est un champ dans la table principale
1686
 * -- soit parce qu’une table de liaison existe, d’un côté ou de l’autre
1687
 *
1688
 * @pipeline_appel lister_champs_selection_conditionnelle
1689
 * @param string $table Nom de la table SQL
1690
 * @param array|null $desc Description de la table SQL, si connu
1691
 * @param string $serveur Connecteur sql a utiliser
1692
 * @return array Liste de nom de champs (tel que id_article, id_mot, id_parent ...)
1693
 */
1694
function lister_champs_id_conditionnel($table, $desc = null, $serveur = '') {
1695
	// calculer la description de la table
1696
	if (!is_array($desc)) {
1697
		$desc = description_table($table, $serveur);
1698
	}
1699
	if (!$desc) {
1700
		return [];
1701
	}
1702
1703
	// Les champs id_xx de la table demandée
1704
	$champs = array_filter(
1705
		array_keys($desc['field']),
1706
		function($champ){
1707
			return
1708
				strpos($champ, 'id_') === 0
1709
				or (in_array($champ, array('objet')));
1710
		}
1711
	);
1712
1713
	// Si le champ id_rubrique appartient à la liste et si id_secteur n'est pas inclus on le rajoute.
1714
	if (
1715
		in_array('id_rubrique', $champs)
1716
		and !in_array('id_secteur', $champs)
1717
	) {
1718
		$champs[] = 'id_secteur';
1719
	}
1720
1721
	// On ne fera pas mieux pour les tables d’un autre serveur
1722
	if ($serveur) {
1723
		return $champs;
1724
	}
1725
1726
	$primary = false;
1727
	$associable = false;
1728
	include_spip('action/editer_liens');
1729
1730
	if (isset($desc['type'])) {
1731
		$primary = id_table_objet($desc['type']);
1732
		$associable = objet_associable($desc['type']);
1733
	}
1734
	if (isset($desc['field']['id_objet']) and isset($desc['field']['objet'])) {
1735
		$associable = true;
1736
	}
1737
1738
	// liste de toutes les tables principales, sauf la notre
1739
	$tables = lister_tables_objets_sql();
1740
	unset($tables[$table]);
1741
1742
	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...
1743
		if (
1744
			$associable
1745
			or ($primary and in_array($primary, array_keys($_desc['field'])))
1746
			or objet_associable($_desc['type'])
1747
		) {
1748
			$champs[] = id_table_objet($_table);
1749
		}
1750
	}
1751
	$champs = array_values(array_unique($champs));
1752
1753
	// Exclusions de certains id
1754
	$exclusions = pipeline(
1755
		'exclure_id_conditionnel',
1756
		array(
1757
			'args' => array(
1758
				'table' => $table,
1759
				'id_table_objet' => $primary,
1760
				'associable' => $associable,
1761
			),
1762
			'data' => array(),
1763
		)
1764
	);
1765
	$champs = array_diff($champs, $exclusions);
1766
1767
	return $champs;
1768
}
1769
1770
/**
1771
 * Compile le critère `{tri}` permettant le tri dynamique d'un champ
1772
 *
1773
 * Le critère `{tri}` gère un champ de tri  qui peut être modifié dynamiquement par la balise `#TRI`.
1774
 * Il s'utilise donc conjointement avec la balise `#TRI` dans la même boucle
1775
 * pour génerér les liens qui permettent de changer le critère de tri et le sens du tri
1776
 *
1777
 * @syntaxe `{tri [champ_par_defaut][,sens_par_defaut][,nom_variable]}`
1778
 *
1779
 * - champ_par_defaut : un champ de la table sql
1780
 * - sens_par_defaut : -1 ou inverse pour décroissant, 1 ou direct pour croissant
1781
 *     peut être un tableau pour préciser des sens par défaut associés à chaque champ
1782
 *     exemple : `array('titre' => 1, 'date' => -1)` pour trier par défaut
1783
 *     les titre croissants et les dates décroissantes
1784
 *     dans ce cas, quand un champ est utilisé pour le tri et n'est pas présent dans le tableau
1785
 *     c'est la première valeur qui est utilisée
1786
 * - nom_variable : nom de la variable utilisée (par defaut `tri_{nomboucle}`)
1787
 *
1788
 *     {tri titre}
1789
 *     {tri titre,inverse}
1790
 *     {tri titre,-1}
1791
 *     {tri titre,-1,truc}
1792
 *
1793
 * Exemple d'utilisation :
1794
 *
1795
 *     <B_articles>
1796
 *     <p>#TRI{titre,'Trier par titre'} | #TRI{date,'Trier par date'}</p>
1797
 *     <ul>
1798
 *     <BOUCLE_articles(ARTICLES){tri titre}>
1799
 *      <li>#TITRE - [(#DATE|affdate_jourcourt)]</li>
1800
 *     </BOUCLE_articles>
1801
 *     </ul>
1802
 *     </B_articles>
1803
 *
1804
 * @note
1805
 *     Contraitement à `{par ...}`, `{tri}` ne peut prendre qu'un seul champ,
1806
 *     mais il peut être complété avec `{par ...}` pour indiquer des criteres secondaires
1807
 *
1808
 *     Exemble :
1809
 *     `{tri num titre}{par titre}` permet de faire un tri sur le rang (modifiable dynamiquement)
1810
 *     avec un second critère sur le titre en cas d'égalité des rangs
1811
 *
1812
 * @link https://www.spip.net/5429
1813
 * @see critere_par_dist() Le critère `{par ...}`
1814
 * @see balise_TRI_dist() La balise `#TRI`
1815
 *
1816
 * @param string $idb Identifiant de la boucle
1817
 * @param array $boucles AST du squelette
1818
 * @param Critere $crit Paramètres du critère dans cette boucle
1819
 * @return void
1820
 */
1821
function critere_tri_dist($idb, &$boucles, $crit) {
1822
	$boucle = &$boucles[$idb];
1823
1824
	// definition du champ par defaut
1825
	$_champ_defaut = !isset($crit->param[0][0]) ? "''"
1826
		: calculer_liste(array($crit->param[0][0]), $idb, $boucles, $boucle->id_parent);
1827
	$_sens_defaut = !isset($crit->param[1][0]) ? "1"
1828
		: calculer_liste(array($crit->param[1][0]), $idb, $boucles, $boucle->id_parent);
1829
	$_variable = !isset($crit->param[2][0]) ? "'$idb'"
1830
		: calculer_liste(array($crit->param[2][0]), $idb, $boucles, $boucle->id_parent);
1831
1832
	$_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):'')";
1833
1834
	$_sens_defaut = "(is_array(\$s=$_sens_defaut)?(isset(\$s[\$st=$_tri])?\$s[\$st]:reset(\$s)):\$s)";
1835
	$_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)";
1836
1837
	$boucle->modificateur['tri_champ'] = $_tri;
1838
	$boucle->modificateur['tri_sens'] = $_sens;
1839
	$boucle->modificateur['tri_nom'] = $_variable;
1840
	// faut il inserer un test sur l'existence de $tri parmi les champs de la table ?
1841
	// evite des erreurs sql, mais peut empecher des tri sur jointure ...
1842
	$boucle->hash .= "
1843
	\$senstri = '';
1844
	\$tri = $_tri;
1845
	if (\$tri){
1846
		\$senstri = $_sens;
1847
		\$senstri = (\$senstri<0)?' DESC':'';
1848
	};
1849
	";
1850
	$boucle->select[] = "\".tri_champ_select(\$tri).\"";
1851
	$boucle->order[] = "tri_champ_order(\$tri,\$command['from']).\$senstri";
1852
}
1853
1854
# Criteres de comparaison
1855
1856
/**
1857
 * Compile un critère non déclaré explicitement
1858
 *
1859
 * Compile les critères non déclarés, ainsi que les parties de boucles
1860
 * avec les critères {0,1} ou {1/2}
1861
 *
1862
 * @param string $idb Identifiant de la boucle
1863
 * @param array $boucles AST du squelette
1864
 * @param Critere $crit Paramètres du critère dans cette boucle
1865
 * @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...
1866
 **/
1867
function calculer_critere_DEFAUT_dist($idb, &$boucles, $crit) {
1868
	// double cas particulier {0,1} et {1/2} repere a l'analyse lexicale
1869
	if (($crit->op == ",") or ($crit->op == '/')) {
1870
		return calculer_critere_parties($idb, $boucles, $crit);
1871
	}
1872
1873
	$r = calculer_critere_infixe($idb, $boucles, $crit);
1874
	if (!$r) {
1875
		#	// on produit une erreur seulement si le critere n'a pas de '?'
1876
		#	if (!$crit->cond) {
1877
		return (array('zbug_critere_inconnu', array('critere' => $crit->op)));
1878
		#	}
1879
	} else {
1880
		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 1873 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...
1881
	}
1882
}
1883
1884
1885
/**
1886
 * Compile un critère non déclaré explicitement, dont on reçoit une analyse
1887
 *
1888
 * Ajoute en fonction des arguments trouvés par calculer_critere_infixe()
1889
 * les conditions WHERE à appliquer sur la boucle.
1890
 *
1891
 * @see calculer_critere_infixe()
1892
 *
1893
 * @param string $idb Identifiant de la boucle
1894
 * @param array $boucles AST du squelette
1895
 * @param Critere $crit Paramètres du critère dans cette boucle
1896
 * @param array $args Description du critère
1897
 *                        Cf. retour de calculer_critere_infixe()
1898
 * @return void
1899
 **/
1900
function calculer_critere_DEFAUT_args($idb, &$boucles, $crit, $args) {
1901
	list($arg, $op, $val, $col, $where_complement) = $args;
1902
1903
	$where = array("'$op'", "'$arg'", $val[0]);
1904
1905
	// inserer la negation (cf !...)
1906
1907
	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...
1908
		$where = array("'NOT'", $where);
1909
	}
1910 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...
1911
		if (!preg_match(",^L[0-9]+[.],", $arg)) {
1912
			$where = array("'NOT'", $where);
1913
		} else
1914
			// un not sur un critere de jointure se traduit comme un NOT IN avec une sous requete
1915
			// c'est une sous requete identique a la requete principale sous la forme (SELF,$select,$where) avec $select et $where qui surchargent
1916
		{
1917
			$where = array(
1918
				"'NOT'",
1919
				array(
1920
					"'IN'",
1921
					"'" . $boucles[$idb]->id_table . "." . $boucles[$idb]->primary . "'",
1922
					array("'SELF'", "'" . $boucles[$idb]->id_table . "." . $boucles[$idb]->primary . "'", $where)
1923
				)
1924
			);
1925
		}
1926
	}
1927
1928
	// inserer la condition (cf {lang?})
1929
	// traiter a part la date, elle est mise d'office par SPIP,
1930
	if ($crit->cond) {
1931
		$pred = calculer_argument_precedent($idb, $col, $boucles);
1932
		if ($col == "date" or $col == "date_redac") {
1933
			if ($pred == "\$Pile[0]['" . $col . "']") {
1934
				$pred = "(\$Pile[0]['{$col}_default']?'':$pred)";
1935
			}
1936
		}
1937
1938
		if ($op == '=' and !$crit->not) {
1939
			$where = array(
1940
				"'?'",
1941
				"(is_array($pred))",
1942
				critere_IN_cas($idb, $boucles, 'COND', $arg, $op, array($pred), $col),
1943
				$where
1944
			);
1945
		}
1946
		$where = array("'?'", "!(is_array($pred)?count($pred):strlen($pred))", "''", $where);
1947
		if ($where_complement) // condition annexe du type "AND (objet='article')"
1948
		{
1949
			$where_complement = array("'?'", "!(is_array($pred)?count($pred):strlen($pred))", "''", $where_complement);
1950
		}
1951
	}
1952
1953
	$boucles[$idb]->where[] = $where;
1954
	if ($where_complement) // condition annexe du type "AND (objet='article')"
1955
	{
1956
		$boucles[$idb]->where[] = $where_complement;
1957
	}
1958
}
1959
1960
1961
/**
1962
 * Décrit un critère non déclaré explicitement
1963
 *
1964
 * Décrit un critère non déclaré comme {id_article} {id_article>3} en
1965
 * retournant un tableau de l'analyse si la colonne (ou l'alias) existe vraiment.
1966
 *
1967
 * Ajoute au passage pour chaque colonne utilisée (alias et colonne véritable)
1968
 * un modificateur['criteres'][colonne].
1969
 *
1970
 * S'occupe de rechercher des exceptions, tel que
1971
 * - les id_parent, id_enfant, id_secteur,
1972
 * - des colonnes avec des exceptions déclarées,
1973
 * - des critères de date (jour_relatif, ...),
1974
 * - des critères sur tables jointes explicites (mots.titre),
1975
 * - des critères sur tables de jointure non explicite (id_mot sur une boucle articles...)
1976
 *
1977
 *
1978
 * @param string $idb Identifiant de la boucle
1979
 * @param array $boucles AST du squelette
1980
 * @param Critere $crit Paramètres du critère dans cette boucle
1981
 * @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...
1982
 *     Liste si on trouve le champ :
1983
 *     - string $arg
1984
 *         Opérande avant l'opérateur : souvent la colonne d'application du critère, parfois un calcul
1985
 *         plus complexe dans le cas des dates.
1986
 *     - string $op
1987
 *         L'opérateur utilisé, tel que '='
1988
 *     - string[] $val
1989
 *         Liste de codes PHP obtenant les valeurs des comparaisons (ex: id_article sur la boucle parente)
1990
 *         Souvent (toujours ?) un tableau d'un seul élément.
1991
 *     - $col_alias
1992
 *     - $where_complement
1993
 *
1994
 *     Chaîne vide si on ne trouve pas le champ...
1995
 **/
1996
function calculer_critere_infixe($idb, &$boucles, $crit) {
1997
1998
	$boucle = &$boucles[$idb];
1999
	$type = $boucle->type_requete;
2000
	$table = $boucle->id_table;
2001
	$desc = $boucle->show;
2002
	$col_vraie = null;
2003
2004
	list($fct, $col, $op, $val, $args_sql) =
2005
		calculer_critere_infixe_ops($idb, $boucles, $crit);
2006
2007
	$col_alias = $col;
2008
	$where_complement = false;
2009
2010
	// Cas particulier : id_enfant => utiliser la colonne id_objet
2011
	if ($col == 'id_enfant') {
2012
		$col = $boucle->primary;
2013
	}
2014
2015
	// Cas particulier : id_parent => verifier les exceptions de tables
2016
	if ((in_array($col, array('id_parent', 'id_secteur')) and isset($GLOBALS['exceptions_des_tables'][$table][$col]))
2017
		or (isset($GLOBALS['exceptions_des_tables'][$table][$col]) and is_string($GLOBALS['exceptions_des_tables'][$table][$col]))
2018
	) {
2019
		$col = $GLOBALS['exceptions_des_tables'][$table][$col];
2020
	} // et possibilite de gerer un critere secteur sur des tables de plugins (ie forums)
2021
	else {
2022
		if (($col == 'id_secteur') and ($critere_secteur = charger_fonction("critere_secteur_$type", "public", true))) {
2023
			$table = $critere_secteur($idb, $boucles, $val, $crit);
2024
		}
2025
2026
		// cas id_article=xx qui se mappe en id_objet=xx AND objet=article
2027
		// sauf si exception declaree : sauter cette etape
2028
		else {
2029
			if (
2030
				!isset($GLOBALS['exceptions_des_jointures'][table_objet_sql($table)][$col])
2031
				and !isset($GLOBALS['exceptions_des_jointures'][$col])
2032
				and count(trouver_champs_decomposes($col, $desc)) > 1
2033
			) {
2034
				$e = decompose_champ_id_objet($col);
2035
				$col = array_shift($e);
2036
				$where_complement = primary_doublee($e, $table);
2037
			} // Cas particulier : expressions de date
2038
			else {
2039
				if ($c = calculer_critere_infixe_date($idb, $boucles, $col)) {
2040
					list($col, $col_vraie) = $c;
2041
					$table = '';
2042
				} // table explicitée {mots.titre}
2043
				else {
2044
					if (preg_match('/^(.*)\.(.*)$/', $col, $r)) {
2045
						list(, $table, $col) = $r;
2046
						$col_alias = $col;
2047
2048
						$trouver_table = charger_fonction('trouver_table', 'base');
2049
						if ($desc = $trouver_table($table, $boucle->sql_serveur)
2050
							and isset($desc['field'][$col])
2051
							and $cle = array_search($desc['table'], $boucle->from)
2052
						) {
2053
							$table = $cle;
2054
						} else {
2055
							$table = trouver_jointure_champ($col, $boucle, array($table), ($crit->cond or $op != '='));
2056
						}
2057
						#$table = calculer_critere_externe_init($boucle, array($table), $col, $desc, ($crit->cond OR $op!='='), true);
2058
						if (!$table) {
2059
							return '';
2060
						}
2061
					}
2062
					// si le champ n'est pas trouvé dans la table,
2063
					// on cherche si une jointure peut l'obtenir
2064
					elseif (@!array_key_exists($col, $desc['field'])) {
2065
						// Champ joker * des iterateurs DATA qui accepte tout
2066
						if (@array_key_exists('*', $desc['field'])) {
2067
							$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
2068
						}
2069
						else {
2070
							$r = calculer_critere_infixe_externe($boucle, $crit, $op, $desc, $col, $col_alias, $table);
2071
							if (!$r) {
2072
								return '';
2073
							}
2074
							list($col, $col_alias, $table, $where_complement, $desc) = $r;
2075
						}
2076
					}
2077
				}
2078
			}
2079
		}
2080
	}
2081
2082
	$col_vraie = ($col_vraie ? $col_vraie : $col);
2083
	// Dans tous les cas,
2084
	// virer les guillemets eventuels autour d'un int (qui sont refuses par certains SQL)
2085
	// et passer dans sql_quote avec le type si connu
2086
	// et int sinon si la valeur est numerique
2087
	// sinon introduire le vrai type du champ si connu dans le sql_quote (ou int NOT NULL sinon)
2088
	// Ne pas utiliser intval, PHP tronquant les Bigint de SQL
2089
	if ($op == '=' or in_array($op, $GLOBALS['table_criteres_infixes'])) {
2090
		$type_cast_quote = (isset($desc['field'][$col_vraie]) ? $desc['field'][$col_vraie] : 'int NOT NULL');
2091
		// defaire le quote des int et les passer dans sql_quote avec le bon type de champ si on le connait, int sinon
2092
		// prendre en compte le debug ou la valeur arrive avec un commentaire PHP en debut
2093
		if (preg_match(",^\\A(\s*//.*?$\s*)?\"'(-?\d+)'\"\\z,ms", $val[0], $r)) {
2094
			$val[0] = $r[1] . '"' . sql_quote($r[2], $boucle->sql_serveur, $type_cast_quote) . '"';
2095
		}
2096
		// sinon expliciter les
2097
		// sql_quote(truc) en sql_quote(truc,'',type)
2098
		// sql_quote(truc,serveur) en sql_quote(truc,serveur,type)
2099
		// sql_quote(truc,serveur,'') en sql_quote(truc,serveur,type)
2100
		// sans toucher aux
2101
		// sql_quote(truc,'','varchar(10) DEFAULT \'oui\' COLLATE NOCASE')
2102
		// sql_quote(truc,'','varchar')
2103
		elseif (preg_match('/\Asql_quote[(](.*?)(,[^)]*?)?(,[^)]*(?:\(\d+\)[^)]*)?)?[)]\s*\z/ms', $val[0], $r)
2104
			// si pas deja un type
2105
			and (!isset($r[3]) or !$r[3] or !trim($r[3],", '"))
2106
		) {
2107
			$r = $r[1]
2108
				. ((isset($r[2]) and $r[2]) ? $r[2] : ",''")
2109
				. ",'" . addslashes($type_cast_quote) . "'";
2110
			$val[0] = "sql_quote($r)";
2111
		}
2112
		elseif(strpos($val[0], '@@defaultcast@@') !== false
2113
		  and preg_match("/'@@defaultcast@@'\s*\)\s*\z/ms", $val[0], $r)) {
2114
			$val[0] = substr($val[0], 0, -strlen($r[0])) . "'" . addslashes($type_cast_quote) . "')";
2115
		}
2116
	}
2117
2118
	if(strpos($val[0], '@@defaultcast@@') !== false
2119
	  and preg_match("/'@@defaultcast@@'\s*\)\s*\z/ms", $val[0], $r)) {
2120
		$val[0] = substr($val[0], 0, -strlen($r[0])) . "'char')";
2121
	}
2122
2123
	// Indicateur pour permettre aux fonctionx boucle_X de modifier
2124
	// leurs requetes par defaut, notamment le champ statut
2125
	// Ne pas confondre champs de la table principale et des jointures
2126
	if ($table === $boucle->id_table) {
2127
		$boucles[$idb]->modificateur['criteres'][$col_vraie] = true;
2128
		if ($col_alias != $col_vraie) {
2129
			$boucles[$idb]->modificateur['criteres'][$col_alias] = true;
2130
		}
2131
	}
2132
2133
	// ajout pour le cas special d'une condition sur le champ statut:
2134
	// il faut alors interdire a la fonction de boucle
2135
	// de mettre ses propres criteres de statut
2136
	// https://www.spip.net/@statut (a documenter)
2137
	// garde pour compatibilite avec code des plugins anterieurs, mais redondant avec la ligne precedente
2138
	if ($col == 'statut') {
2139
		$boucles[$idb]->statut = true;
2140
	}
2141
2142
	// inserer le nom de la table SQL devant le nom du champ
2143
	if ($table) {
2144
		if ($col[0] == "`") {
2145
			$arg = "$table." . substr($col, 1, -1);
2146
		} else {
2147
			$arg = "$table.$col";
2148
		}
2149
	} else {
2150
		$arg = $col;
2151
	}
2152
2153
	// inserer la fonction SQL
2154
	if ($fct) {
2155
		$arg = "$fct($arg$args_sql)";
2156
	}
2157
2158
	return array($arg, $op, $val, $col_alias, $where_complement);
2159
}
2160
2161
2162
/**
2163
 * Décrit un critère non déclaré explicitement, sur un champ externe à la table
2164
 *
2165
 * Décrit un critère non déclaré comme {id_article} {id_article>3} qui correspond
2166
 * à un champ non présent dans la table, et donc à retrouver par jointure si possible.
2167
 *
2168
 * @param Boucle $boucle Description de la boucle
2169
 * @param Critere $crit Paramètres du critère dans cette boucle
2170
 * @param string $op L'opérateur utilisé, tel que '='
2171
 * @param array $desc Description de la table
2172
 * @param string $col Nom de la colonne à trouver (la véritable)
2173
 * @param string $col_alias Alias de la colonne éventuel utilisé dans le critère ex: id_enfant
2174
 * @param string $table Nom de la table SQL de la boucle
2175
 * @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...
2176
 *     Liste si jointure possible :
2177
 *     - string $col
2178
 *     - string $col_alias
2179
 *     - string $table
2180
 *     - array $where
2181
 *     - array $desc
2182
 *
2183
 *     Chaîne vide si on ne trouve pas le champ par jointure...
2184
 **/
2185
function calculer_critere_infixe_externe($boucle, $crit, $op, $desc, $col, $col_alias, $table) {
2186
2187
	$where = '';
2188
2189
	$calculer_critere_externe = 'calculer_critere_externe_init';
2190
	// gestion par les plugins des jointures tordues
2191
	// pas automatiques mais necessaires
2192
	$table_sql = table_objet_sql($table);
2193
	if (isset($GLOBALS['exceptions_des_jointures'][$table_sql])
2194
		and is_array($GLOBALS['exceptions_des_jointures'][$table_sql])
2195
		and
2196
		(
2197
			isset($GLOBALS['exceptions_des_jointures'][$table_sql][$col])
2198
			or
2199
			isset($GLOBALS['exceptions_des_jointures'][$table_sql][''])
2200
		)
2201
	) {
2202
		$t = $GLOBALS['exceptions_des_jointures'][$table_sql];
2203
		$index = isset($t[$col])
2204
			? $t[$col] : (isset($t['']) ? $t[''] : array());
2205
2206
		if (count($index) == 3) {
2207
			list($t, $col, $calculer_critere_externe) = $index;
2208
		} elseif (count($index) == 2) {
2209
			list($t, $col) = $t[$col];
2210
		} elseif (count($index) == 1) {
2211
			list($calculer_critere_externe) = $index;
2212
			$t = $table;
2213
		} else {
2214
			$t = '';
2215
		} // jointure non declaree. La trouver.
2216
	} elseif (isset($GLOBALS['exceptions_des_jointures'][$col])) {
2217
		list($t, $col) = $GLOBALS['exceptions_des_jointures'][$col];
2218
	} else {
2219
		$t = '';
2220
	} // jointure non declaree. La trouver.
2221
2222
	// ici on construit le from pour fournir $col en piochant dans les jointures
2223
2224
	// si des jointures explicites sont fournies, on cherche d'abord dans celles ci
2225
	// permet de forcer une table de lien quand il y a ambiguite
2226
	// <BOUCLE_(DOCUMENTS documents_liens){id_mot}>
2227
	// alors que <BOUCLE_(DOCUMENTS){id_mot}> produit la meme chose que <BOUCLE_(DOCUMENTS mots_liens){id_mot}>
2228
	$table = "";
2229
	if ($boucle->jointures_explicites) {
2230
		$jointures_explicites = explode(' ', $boucle->jointures_explicites);
2231
		$table = $calculer_critere_externe($boucle, $jointures_explicites, $col, $desc, ($crit->cond or $op != '='), $t);
2232
	}
2233
2234
	// et sinon on cherche parmi toutes les jointures declarees
2235
	if (!$table) {
2236
		$table = $calculer_critere_externe($boucle, $boucle->jointures, $col, $desc, ($crit->cond or $op != '='), $t);
2237
	}
2238
2239
	if (!$table) {
2240
		return '';
2241
	}
2242
2243
	// il ne reste plus qu'a trouver le champ dans les from
2244
	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...
2245
2246
	if (count($cle) > 1 or reset($cle) !== $col) {
2247
		$col_alias = $col; // id_article devient juste le nom d'origine
2248
		if (count($cle) > 1 and reset($cle) == 'id_objet') {
2249
			$e = decompose_champ_id_objet($col);
2250
			$col = array_shift($e);
2251
			$where = primary_doublee($e, $table);
2252
		} else {
2253
			$col = reset($cle);
2254
		}
2255
	}
2256
2257
	return array($col, $col_alias, $table, $where, $desc);
2258
}
2259
2260
2261
/**
2262
 * Calcule une condition WHERE entre un nom du champ et une valeur
2263
 *
2264
 * Ne pas appliquer sql_quote lors de la compilation,
2265
 * car on ne connait pas le serveur SQL
2266
 *
2267
 * @todo Ce nom de fonction n'est pas très clair ?
2268
 *
2269
 * @param array $decompose Liste nom du champ, code PHP pour obtenir la valeur
2270
 * @param string $table Nom de la table
2271
 * @return string[]
2272
 *     Liste de 3 éléments pour une description where du compilateur :
2273
 *     - operateur (=),
2274
 *     - table.champ,
2275
 *     - valeur
2276
 **/
2277
function primary_doublee($decompose, $table) {
2278
	$e1 = reset($decompose);
2279
	$e2 = "sql_quote('" . end($decompose) . "')";
2280
2281
	return array("'='", "'$table." . $e1 . "'", $e2);
2282
}
2283
2284
/**
2285
 * Champ hors table, ça ne peut être qu'une jointure.
2286
 *
2287
 * On cherche la table du champ et on regarde si elle est déjà jointe
2288
 * Si oui et qu'on y cherche un champ nouveau, pas de jointure supplementaire
2289
 * Exemple: criteres {titre_mot=...}{type_mot=...}
2290
 * Dans les 2 autres cas ==> jointure
2291
 * (Exemple: criteres {type_mot=...}{type_mot=...} donne 2 jointures
2292
 * pour selectioner ce qui a exactement ces 2 mots-cles.
2293
 *
2294
 * @param Boucle $boucle
2295
 *     Description de la boucle
2296
 * @param array $joints
2297
 *     Liste de jointures possibles (ex: $boucle->jointures ou $boucle->jointures_explicites)
2298
 * @param string $col
2299
 *     Colonne cible de la jointure
2300
 * @param array $desc
2301
 *     Description de la table
2302
 * @param bool $cond
2303
 *     Flag pour savoir si le critère est conditionnel ou non
2304
 * @param bool|string $checkarrivee
2305
 *     string : nom de la table jointe où on veut trouver le champ.
2306
 *     n'a normalement pas d'appel sans $checkarrivee.
2307
 * @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...
2308
 *     Alias de la table de jointure (Lx)
2309
 *     Vide sinon.
2310
 */
2311
function calculer_critere_externe_init(&$boucle, $joints, $col, $desc, $cond, $checkarrivee = false) {
2312
	// si on demande un truc du genre spip_mots
2313
	// avec aussi spip_mots_liens dans les jointures dispo
2314
	// et qu'on est la
2315
	// il faut privilegier la jointure directe en 2 etapes spip_mots_liens, spip_mots
2316
	if ($checkarrivee
2317
		and is_string($checkarrivee)
2318
		and $a = table_objet($checkarrivee)
2319
		and in_array($a . '_liens', $joints)
2320
	) {
2321
		if ($res = calculer_lien_externe_init($boucle, $joints, $col, $desc, $cond, $checkarrivee)) {
2322
			return $res;
2323
		}
2324
	}
2325
	foreach ($joints as $joint) {
2326
		if ($arrivee = trouver_champ_exterieur($col, array($joint), $boucle, $checkarrivee)) {
2327
			// alias de table dans le from
2328
			$t = array_search($arrivee[0], $boucle->from);
2329
			// recuperer la cle id_xx eventuellement decomposee en (id_objet,objet)
2330
			$cols = $arrivee[2];
2331
			// mais on ignore la 3eme cle si presente qui correspond alors au point de depart
2332
			if (count($cols) > 2) {
2333
				array_pop($cols);
2334
			}
2335
			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...
2336
				// la table est déjà dans le FROM, on vérifie si le champ est utilisé.
2337
				$joindre = false;
2338
				foreach ($cols as $col) {
2339
					$c = '/\b' . $t . ".$col" . '\b/';
2340
					if (trouver_champ($c, $boucle->where)) {
2341
						$joindre = true;
2342
					} else {
2343
						// mais ca peut etre dans le FIELD pour le Having
2344
						$c = "/FIELD.$t" . ".$col,/";
2345
						if (trouver_champ($c, $boucle->select)) {
2346
							$joindre = true;
2347
						}
2348
					}
2349
				}
2350
				if (!$joindre) {
2351
					return $t;
2352
				}
2353
			}
2354
			array_pop($arrivee);
2355
			if ($res = calculer_jointure($boucle, array($boucle->id_table, $desc), $arrivee, $cols, $cond, 1)) {
2356
				return $res;
2357
			}
2358
		}
2359
	}
2360
2361
	return '';
2362
2363
}
2364
2365
/**
2366
 * Générer directement une jointure via une table de lien spip_xxx_liens
2367
 * pour un critère {id_xxx}
2368
 *
2369
 * @todo $checkarrivee doit être obligatoire ici ?
2370
 *
2371
 * @param Boucle $boucle
2372
 *     Description de la boucle
2373
 * @param array $joints
2374
 *     Liste de jointures possibles (ex: $boucle->jointures ou $boucle->jointures_explicites)
2375
 * @param string $col
2376
 *     Colonne cible de la jointure
2377
 * @param array $desc
2378
 *     Description de la table
2379
 * @param bool $cond
2380
 *     Flag pour savoir si le critère est conditionnel ou non
2381
 * @param bool|string $checkarrivee
2382
 *     string : nom de la table jointe où on veut trouver le champ.
2383
 *     n'a normalement pas d'appel sans $checkarrivee.
2384
 * @return string
2385
 *     Alias de la table de jointure (Lx)
2386
 */
2387
function calculer_lien_externe_init(&$boucle, $joints, $col, $desc, $cond, $checkarrivee = false) {
2388
	$primary_arrivee = id_table_objet($checkarrivee);
0 ignored issues
show
Bug introduced by
It seems like $checkarrivee defined by parameter $checkarrivee on line 2387 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...
2389
2390
	// [FIXME] $checkarrivee peut-il arriver avec false ????
2391
	$intermediaire = trouver_champ_exterieur($primary_arrivee, $joints, $boucle, $checkarrivee . "_liens");
2392
	$arrivee = trouver_champ_exterieur($col, $joints, $boucle, $checkarrivee);
2393
2394
	if (!$intermediaire or !$arrivee) {
2395
		return '';
2396
	}
2397
	array_pop($intermediaire); // enlever la cle en 3eme argument
2398
	array_pop($arrivee); // enlever la cle en 3eme argument
2399
2400
	$res = fabrique_jointures($boucle,
2401
		array(
2402
			array(
2403
				$boucle->id_table,
2404
				$intermediaire,
2405
				array(id_table_objet($desc['table_objet']), 'id_objet', 'objet', $desc['type'])
2406
			),
2407
			array(reset($intermediaire), $arrivee, $primary_arrivee)
2408
		), $cond, $desc, $boucle->id_table, array($col));
2409
2410
	return $res;
2411
}
2412
2413
2414
/**
2415
 * Recherche la présence d'un champ dans une valeur de tableau
2416
 *
2417
 * @param string $champ
2418
 *     Expression régulière pour trouver un champ donné.
2419
 *     Exemple : /\barticles.titre\b/
2420
 * @param array $where
2421
 *     Tableau de valeurs dans lesquels chercher le champ.
2422
 * @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...
2423
 *     true si le champ est trouvé quelque part dans $where
2424
 *     false sinon.
2425
 **/
2426
function trouver_champ($champ, $where) {
2427
	if (!is_array($where)) {
2428
		return preg_match($champ, $where);
2429
	} else {
2430
		foreach ($where as $clause) {
2431
			if (trouver_champ($champ, $clause)) {
2432
				return true;
2433
			}
2434
		}
2435
2436
		return false;
2437
	}
2438
}
2439
2440
2441
/**
2442
 * Détermine l'operateur et les opérandes d'un critère non déclaré
2443
 *
2444
 * Lorsque l'opérateur n'est pas explicite comme sur {id_article>0} c'est
2445
 * l'opérateur '=' qui est utilisé.
2446
 *
2447
 * Traite les cas particuliers id_parent, id_enfant, date, lang
2448
 *
2449
 * @param string $idb Identifiant de la boucle
2450
 * @param array $boucles AST du squelette
2451
 * @param Critere $crit Paramètres du critère dans cette boucle
2452
 * @return array
2453
 *     Liste :
2454
 *     - string $fct       Nom d'une fonction SQL sur le champ ou vide (ex: SUM)
2455
 *     - string $col       Nom de la colonne SQL utilisée
2456
 *     - string $op        Opérateur
2457
 *     - string[] $val
2458
 *         Liste de codes PHP obtenant les valeurs des comparaisons (ex: id_article sur la boucle parente)
2459
 *         Souvent un tableau d'un seul élément.
2460
 *     - string $args_sql  Suite des arguments du critère. ?
2461
 **/
2462
function calculer_critere_infixe_ops($idb, &$boucles, $crit) {
2463
	// cas d'une valeur comparee a elle-meme ou son referent
2464
	if (count($crit->param) == 0) {
2465
		$op = '=';
2466
		$col = $val = $crit->op;
2467
		if (preg_match('/^(.*)\.(.*)$/', $col, $r)) {
2468
			$val = $r[2];
2469
		}
2470
		// Cas special {lang} : aller chercher $GLOBALS['spip_lang']
2471
		if ($val == 'lang') {
2472
			$val = array(kwote('$GLOBALS[\'spip_lang\']'));
2473
		} else {
2474
			$defaut = null;
2475
			if ($val == 'id_parent') {
2476
				// Si id_parent, comparer l'id_parent avec l'id_objet
2477
				// de la boucle superieure.... faudrait verifier qu'il existe
2478
				// pour eviter l'erreur SQL
2479
				$val = $boucles[$idb]->primary;
2480
				// mais si pas de boucle superieure, prendre id_parent dans l'env
2481
				$defaut = "@\$Pile[0]['id_parent']";
2482
			} elseif ($val == 'id_enfant') {
2483
				// Si id_enfant, comparer l'id_objet avec l'id_parent
2484
				// de la boucle superieure
2485
				$val = 'id_parent';
2486
			} elseif ($crit->cond and ($col == "date" or $col == "date_redac")) {
2487
				// un critere conditionnel sur date est traite a part
2488
				// car la date est mise d'office par SPIP,
2489
				$defaut = "(\$Pile[0]['{$col}_default']?'':\$Pile[0]['" . $col . "'])";
2490
			}
2491
2492
			$val = calculer_argument_precedent($idb, $val, $boucles, $defaut);
2493
			$val = array(kwote($val));
2494
		}
2495
	} else {
2496
		// comparaison explicite
2497
		// le phraseur impose que le premier param soit du texte
2498
		$params = $crit->param;
2499
		$op = $crit->op;
2500
		if ($op == '==') {
2501
			$op = 'REGEXP';
2502
		}
2503
		$col = array_shift($params);
2504
		$col = $col[0]->texte;
2505
2506
		$val = array();
2507
		$parent = $boucles[$idb]->id_parent;
2508
2509
		// Dans le cas {x=='#DATE'} etc, defaire le travail du phraseur,
2510
		// celui ne sachant pas ce qu'est un critere infixe
2511
		// et a fortiori son 2e operande qu'entoure " ou '
2512
		if (count($params) == 1
2513
			and count($params[0]) == 3
2514
			and $params[0][0]->type == 'texte'
2515
			and $params[0][2]->type == 'texte'
2516
			and ($p = $params[0][0]->texte) == $params[0][2]->texte
2517
			and (($p == "'") or ($p == '"'))
2518
			and $params[0][1]->type == 'champ'
2519
		) {
2520
			$val[] = "$p\\$p#" . $params[0][1]->nom_champ . "\\$p$p";
2521
		} else {
2522
			foreach ((($op != 'IN') ? $params : calculer_vieux_in($params)) as $p) {
2523
				$a = calculer_liste($p, $idb, $boucles, $parent);
2524
				if (strcasecmp($op, 'IN') == 0) {
2525
					$val[] = $a;
2526
				} else {
2527
					$val[] = kwote($a, $boucles[$idb]->sql_serveur, '@@defaultcast@@');
2528
				} // toujours quoter en char ici
2529
			}
2530
		}
2531
	}
2532
2533
	$fct = $args_sql = '';
2534
	// fonction SQL ?
2535
	// chercher FONCTION(champ) tel que CONCAT(titre,descriptif)
2536
	if (preg_match('/^(.*)' . SQL_ARGS . '$/', $col, $m)) {
2537
		$fct = $m[1];
2538
		preg_match('/^\(([^,]*)(.*)\)$/', $m[2], $a);
2539
		$col = $a[1];
2540
		if (preg_match('/^(\S*)(\s+AS\s+.*)$/i', $col, $m)) {
2541
			$col = $m[1];
2542
			$args_sql = $m[2];
2543
		}
2544
		$args_sql .= $a[2];
2545
	}
2546
2547
	return array($fct, $col, $op, $val, $args_sql);
2548
}
2549
2550
// compatibilite ancienne version
2551
2552
// https://code.spip.net/@calculer_vieux_in
2553
function calculer_vieux_in($params) {
2554
	$deb = $params[0][0];
2555
	$k = count($params) - 1;
2556
	$last = $params[$k];
2557
	$j = count($last) - 1;
2558
	$last = $last[$j];
2559
	$n = isset($last->texte) ? strlen($last->texte) : 0;
2560
2561
	if (!((isset($deb->texte[0]) and $deb->texte[0] == '(')
2562
		&& (isset($last->texte[$n - 1]) and $last->texte[$n - 1] == ')'))
2563
	) {
2564
		return $params;
2565
	}
2566
	$params[0][0]->texte = substr($deb->texte, 1);
2567
	// attention, on peut avoir k=0,j=0 ==> recalculer
2568
	$last = $params[$k][$j];
2569
	$n = strlen($last->texte);
2570
	$params[$k][$j]->texte = substr($last->texte, 0, $n - 1);
2571
	$newp = array();
2572
	foreach ($params as $v) {
2573
		if ($v[0]->type != 'texte') {
2574
			$newp[] = $v;
2575
		} else {
2576
			foreach (explode(',', $v[0]->texte) as $x) {
2577
				$t = new Texte;
2578
				$t->texte = $x;
2579
				$newp[] = array($t);
2580
			}
2581
		}
2582
	}
2583
2584
	return $newp;
2585
}
2586
2587
/**
2588
 * Calcule les cas particuliers de critères de date
2589
 *
2590
 * Lorsque la colonne correspond à un critère de date, tel que
2591
 * jour, jour_relatif, jour_x, age, age_relatif, age_x...
2592
 *
2593
 * @param string $idb Identifiant de la boucle
2594
 * @param array $boucles AST du squelette
2595
 * @param string $col Nom du champ demandé
2596
 * @return string|array
2597
 *     chaine vide si ne correspond pas à une date,
2598
 *     sinon liste
2599
 *     - expression SQL de calcul de la date,
2600
 *     - nom de la colonne de date (si le calcul n'est pas relatif)
2601
 **/
2602
function calculer_critere_infixe_date($idb, &$boucles, $col) {
2603
	if (!preg_match(",^((age|jour|mois|annee)_relatif|date|mois|annee|jour|heure|age)(_[a-z_]+)?$,", $col, $regs)) {
2604
		return '';
2605
	}
2606
2607
	$boucle = $boucles[$idb];
2608
	$table = $boucle->show;
2609
2610
	// si c'est une colonne de la table, ne rien faire
2611
	if (isset($table['field'][$col])) {
2612
		return '';
2613
	}
2614
2615
	if (!$table['date'] && !isset($GLOBALS['table_date'][$table['id_table']])) {
2616
		return '';
2617
	}
2618
	$pred = $date_orig = isset($GLOBALS['table_date'][$table['id_table']]) ? $GLOBALS['table_date'][$table['id_table']] : $table['date'];
2619
2620
	$col = $regs[1];
2621
	if (isset($regs[3]) and $suite = $regs[3]) {
2622
		# Recherche de l'existence du champ date_xxxx,
2623
		# si oui choisir ce champ, sinon choisir xxxx
2624
2625
		if (isset($table['field']["date$suite"])) {
2626
			$date_orig = 'date' . $suite;
2627
		} else {
2628
			$date_orig = substr($suite, 1);
2629
		}
2630
		$pred = $date_orig;
2631
	} else {
2632
		if (isset($regs[2]) and $rel = $regs[2]) {
2633
			$pred = 'date';
2634
		}
2635
	}
2636
2637
	$date_compare = "\"' . normaliser_date(" .
2638
		calculer_argument_precedent($idb, $pred, $boucles) .
2639
		") . '\"";
2640
2641
	$col_vraie = $date_orig;
2642
	$date_orig = $boucle->id_table . '.' . $date_orig;
2643
2644
	switch ($col) {
2645
		case 'date':
2646
			$col = $date_orig;
2647
			break;
2648
		case 'jour':
2649
			$col = "DAYOFMONTH($date_orig)";
2650
			break;
2651
		case 'mois':
2652
			$col = "MONTH($date_orig)";
2653
			break;
2654
		case 'annee':
2655
			$col = "YEAR($date_orig)";
2656
			break;
2657
		case 'heure':
2658
			$col = "DATE_FORMAT($date_orig, \\'%H:%i\\')";
2659
			break;
2660
		case 'age':
2661
			$col = calculer_param_date("\'' . date('Y-m-d H:i:00') . '\'", $date_orig);
2662
			$col_vraie = "";// comparer a un int (par defaut)
2663
			break;
2664
		case 'age_relatif':
2665
			$col = calculer_param_date($date_compare, $date_orig);
2666
			$col_vraie = "";// comparer a un int (par defaut)
2667
			break;
2668
		case 'jour_relatif':
2669
			$col = "(TO_DAYS(" . $date_compare . ")-TO_DAYS(" . $date_orig . "))";
2670
			$col_vraie = "";// comparer a un int (par defaut)
2671
			break;
2672
		case 'mois_relatif':
2673
			$col = "MONTH(" . $date_compare . ")-MONTH(" .
2674
				$date_orig . ")+12*(YEAR(" . $date_compare .
2675
				")-YEAR(" . $date_orig . "))";
2676
			$col_vraie = "";// comparer a un int (par defaut)
2677
			break;
2678
		case 'annee_relatif':
2679
			$col = "YEAR(" . $date_compare . ")-YEAR(" .
2680
				$date_orig . ")";
2681
			$col_vraie = "";// comparer a un int (par defaut)
2682
			break;
2683
	}
2684
2685
	return array($col, $col_vraie);
2686
}
2687
2688
/**
2689
 * Calcule l'expression SQL permettant de trouver un nombre de jours écoulés.
2690
 *
2691
 * Le calcul SQL retournera un nombre de jours écoulés entre la date comparée
2692
 * et la colonne SQL indiquée
2693
 *
2694
 * @param string $date_compare
2695
 *     Code PHP permettant d'obtenir le timestamp référent.
2696
 *     C'est à partir de lui que l'on compte les jours
2697
 * @param string $date_orig
2698
 *     Nom de la colonne SQL qui possède la date
2699
 * @return string
2700
 *     Expression SQL calculant le nombre de jours écoulé entre une valeur
2701
 *     de colonne SQL et une date.
2702
 **/
2703
function calculer_param_date($date_compare, $date_orig) {
2704
	if (preg_match(",'\" *\.(.*)\. *\"',", $date_compare, $r)) {
2705
		$init = "'\" . (\$x = $r[1]) . \"'";
2706
		$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...
2707
	} else {
2708
		$init = $date_compare;
2709
	}
2710
2711
	return
2712
		// optimisation : mais prevoir le support SQLite avant
2713
		"TIMESTAMPDIFF(HOUR,$date_orig,$init)/24";
2714
}
2715
2716
/**
2717
 * Compile le critère {source} d'une boucle DATA
2718
 *
2719
 * Permet de déclarer le mode d'obtention des données dans une boucle
2720
 * DATA (premier argument) et les données (la suite).
2721
 *
2722
 * @example
2723
 *     (DATA){source mode, "xxxxxx", arg, arg, arg}
2724
 *     (DATA){source tableau, #LISTE{un,deux,trois}}
2725
 *
2726
 * @param string $idb Identifiant de la boucle
2727
 * @param array $boucles AST du squelette
2728
 * @param Critere $crit Paramètres du critère dans cette boucle
2729
 */
2730
function critere_DATA_source_dist($idb, &$boucles, $crit) {
2731
	$boucle = &$boucles[$idb];
2732
2733
	$args = array();
2734
	foreach ($crit->param as &$param) {
2735
		array_push($args,
2736
			calculer_liste($param, $idb, $boucles, $boucles[$idb]->id_parent));
2737
	}
2738
2739
	$boucle->hash .= '
2740
	$command[\'sourcemode\'] = ' . array_shift($args) . ";\n";
2741
2742
	$boucle->hash .= '
2743
	$command[\'source\'] = array(' . join(', ', $args) . ");\n";
2744
}
2745
2746
2747
/**
2748
 * Compile le critère {datasource} d'une boucle DATA
2749
 *
2750
 * Permet de déclarer le mode d'obtention des données dans une boucle DATA
2751
 *
2752
 * @deprecated Utiliser directement le critère {source}
2753
 *
2754
 * @param string $idb Identifiant de la boucle
2755
 * @param array $boucles AST du squelette
2756
 * @param Critere $crit Paramètres du critère dans cette boucle
2757
 */
2758
function critere_DATA_datasource_dist($idb, &$boucles, $crit) {
2759
	$boucle = &$boucles[$idb];
2760
	$boucle->hash .= '
2761
	$command[\'source\'] = array(' . calculer_liste($crit->param[0], $idb, $boucles, $boucles[$idb]->id_parent) . ');
2762
	$command[\'sourcemode\'] = ' . calculer_liste($crit->param[1], $idb, $boucles, $boucles[$idb]->id_parent) . ';';
2763
}
2764
2765
2766
/**
2767
 * Compile le critère {datacache} d'une boucle DATA
2768
 *
2769
 * Permet de transmettre une durée de cache (time to live) utilisée
2770
 * pour certaines sources d'obtention des données (par exemple RSS),
2771
 * indiquant alors au bout de combien de temps la donnée est à réobtenir.
2772
 *
2773
 * La durée par défaut est 1 journée.
2774
 *
2775
 * @param string $idb Identifiant de la boucle
2776
 * @param array $boucles AST du squelette
2777
 * @param Critere $crit Paramètres du critère dans cette boucle
2778
 */
2779
function critere_DATA_datacache_dist($idb, &$boucles, $crit) {
2780
	$boucle = &$boucles[$idb];
2781
	$boucle->hash .= '
2782
	$command[\'datacache\'] = ' . calculer_liste($crit->param[0], $idb, $boucles, $boucles[$idb]->id_parent) . ';';
2783
}
2784
2785
2786
/**
2787
 * Compile le critère {args} d'une boucle PHP
2788
 *
2789
 * Permet de passer des arguments à un iterateur non-spip
2790
 * (PHP:xxxIterator){args argument1, argument2, argument3}
2791
 *
2792
 * @param string $idb Identifiant de la boucle
2793
 * @param array $boucles AST du squelette
2794
 * @param Critere $crit Paramètres du critère dans cette boucle
2795
 */
2796 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...
2797
	$boucle = &$boucles[$idb];
2798
	$boucle->hash .= '$command[\'args\']=array();';
2799
	foreach ($crit->param as $param) {
2800
		$boucle->hash .= '
2801
			$command[\'args\'][] = ' . calculer_liste($param, $idb, $boucles, $boucles[$idb]->id_parent) . ';';
2802
	}
2803
}
2804
2805
/**
2806
 * Compile le critère {liste} d'une boucle DATA
2807
 *
2808
 * Passe une liste de données à l'itérateur DATA
2809
 *
2810
 * @example
2811
 *     (DATA){liste X1, X2, X3}
2812
 *     équivalent à (DATA){source tableau,#LISTE{X1, X2, X3}}
2813
 *
2814
 * @param string $idb Identifiant de la boucle
2815
 * @param array $boucles AST du squelette
2816
 * @param Critere $crit Paramètres du critère dans cette boucle
2817
 */
2818 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...
2819
	$boucle = &$boucles[$idb];
2820
	$boucle->hash .= "\n\t" . '$command[\'liste\'] = array();' . "\n";
2821
	foreach ($crit->param as $param) {
2822
		$boucle->hash .= "\t" . '$command[\'liste\'][] = ' . calculer_liste($param, $idb, $boucles,
2823
				$boucles[$idb]->id_parent) . ";\n";
2824
	}
2825
}
2826
2827
/**
2828
 * Compile le critère {enum} d'une boucle DATA
2829
 *
2830
 * Passe les valeurs de début et de fin d'une énumération, qui seront
2831
 * vues comme une liste d'autant d'éléments à parcourir pour aller du
2832
 * début à la fin.
2833
 *
2834
 * Cela utilisera la fonction range() de PHP.
2835
 *
2836
 * @example
2837
 *     (DATA){enum Xdebut, Xfin}
2838
 *     (DATA){enum a,z}
2839
 *     (DATA){enum z,a}
2840
 *     (DATA){enum 1.0,9.2}
2841
 *
2842
 * @link http://php.net/manual/fr/function.range.php
2843
 *
2844
 * @param string $idb Identifiant de la boucle
2845
 * @param array $boucles AST du squelette
2846
 * @param Critere $crit Paramètres du critère dans cette boucle
2847
 */
2848 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...
2849
	$boucle = &$boucles[$idb];
2850
	$boucle->hash .= "\n\t" . '$command[\'enum\'] = array();' . "\n";
2851
	foreach ($crit->param as $param) {
2852
		$boucle->hash .= "\t" . '$command[\'enum\'][] = ' . calculer_liste($param, $idb, $boucles,
2853
				$boucles[$idb]->id_parent) . ";\n";
2854
	}
2855
}
2856
2857
/**
2858
 * Compile le critère {datapath} d'une boucle DATA
2859
 *
2860
 * Extrait un chemin d'un tableau de données
2861
 *
2862
 * (DATA){datapath query.results}
2863
 *
2864
 * @param string $idb Identifiant de la boucle
2865
 * @param array $boucles AST du squelette
2866
 * @param Critere $crit Paramètres du critère dans cette boucle
2867
 */
2868 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...
2869
	$boucle = &$boucles[$idb];
2870
	foreach ($crit->param as $param) {
2871
		$boucle->hash .= '
2872
			$command[\'datapath\'][] = ' . calculer_liste($param, $idb, $boucles, $boucles[$idb]->id_parent) . ';';
2873
	}
2874
}
2875
2876
2877
/**
2878
 * Compile le critère {si}
2879
 *
2880
 * Le critère {si condition} est applicable à toutes les boucles et conditionne
2881
 * l'exécution de la boucle au résultat de la condition. La partie alternative
2882
 * de la boucle est alors affichée si une condition n'est pas remplie (comme
2883
 * lorsque la boucle ne ramène pas de résultat).
2884
 * La différence étant que si la boucle devait réaliser une requête SQL
2885
 * (par exemple une boucle ARTICLES), celle ci n'est pas réalisée si la
2886
 * condition n'est pas remplie.
2887
 *
2888
 * Les valeurs de la condition sont forcément extérieures à cette boucle
2889
 * (sinon il faudrait l'exécuter pour connaître le résultat, qui doit tester
2890
 * si on exécute la boucle !)
2891
 *
2892
 * Si plusieurs critères {si} sont présents, ils sont cumulés :
2893
 * si une seule des conditions n'est pas vérifiée, la boucle n'est pas exécutée.
2894
 *
2895
 * @example
2896
 *     {si #ENV{exec}|=={article}}
2897
 *     {si (#_contenu:GRAND_TOTAL|>{10})}
2898
 *     {si #AUTORISER{voir,articles}}
2899
 *
2900
 * @param string $idb Identifiant de la boucle
2901
 * @param array $boucles AST du squelette
2902
 * @param Critere $crit Paramètres du critère dans cette boucle
2903
 */
2904
function critere_si_dist($idb, &$boucles, $crit) {
2905
	$boucle = &$boucles[$idb];
2906
	// il faut initialiser 1 fois le tableau a chaque appel de la boucle
2907
	// (par exemple lorsque notre boucle est appelee dans une autre boucle)
2908
	// mais ne pas l'initialiser n fois si il y a n criteres {si } dans la boucle !
2909
	$boucle->hash .= "\n\tif (!isset(\$si_init)) { \$command['si'] = array(); \$si_init = true; }\n";
2910
	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...
2911
		foreach ($crit->param as $param) {
2912
			$boucle->hash .= "\t\$command['si'][] = "
2913
				. calculer_liste($param, $idb, $boucles, $boucles[$idb]->id_parent) . ";\n";
2914
		}
2915
		// interdire {si 0} aussi !
2916
	} else {
2917
		$boucle->hash .= '$command[\'si\'][] = 0;';
2918
	}
2919
}
2920
2921
/**
2922
 * Compile le critère {tableau} d'une boucle POUR
2923
 *
2924
 * {tableau #XX} pour compatibilite ascendante boucle POUR
2925
 * ... préférer la notation (DATA){source tableau,#XX}
2926
 *
2927
 * @deprecated Utiliser une boucle (DATA){source tableau,#XX}
2928
 *
2929
 * @param string $idb Identifiant de la boucle
2930
 * @param array $boucles AST du squelette
2931
 * @param Critere $crit Paramètres du critère dans cette boucle
2932
 */
2933
function critere_POUR_tableau_dist($idb, &$boucles, $crit) {
2934
	$boucle = &$boucles[$idb];
2935
	$boucle->hash .= '
2936
	$command[\'source\'] = array(' . calculer_liste($crit->param[0], $idb, $boucles, $boucles[$idb]->id_parent) . ');
2937
	$command[\'sourcemode\'] = \'table\';';
2938
}
2939
2940
2941
/**
2942
 * Compile le critère {noeud}
2943
 *
2944
 * Trouver tous les objets qui ont des enfants (les noeuds de l'arbre)
2945
 * {noeud}
2946
 * {!noeud} retourne les feuilles
2947
 *
2948
 * @global array $exceptions_des_tables
2949
 *
2950
 * @param string $idb Identifiant de la boucle
2951
 * @param array $boucles AST du squelette
2952
 * @param Critere $crit Paramètres du critère dans cette boucle
2953
 */
2954
function critere_noeud_dist($idb, &$boucles, $crit) {
2955
2956
	$not = $crit->not;
2957
	$boucle = &$boucles[$idb];
2958
	$primary = $boucle->primary;
2959
2960
	if (!$primary or strpos($primary, ',')) {
2961
		erreur_squelette(_T('zbug_doublon_sur_table_sans_cle_primaire'), $boucle);
2962
2963
		return;
2964
	}
2965
	$table = $boucle->type_requete;
2966
	$table_sql = table_objet_sql(objet_type($table));
2967
2968
	$id_parent = isset($GLOBALS['exceptions_des_tables'][$boucle->id_table]['id_parent']) ?
2969
		$GLOBALS['exceptions_des_tables'][$boucle->id_table]['id_parent'] :
2970
		'id_parent';
2971
2972
	$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...
2973
	$where = array("'IN'", "'$boucle->id_table." . "$primary'", "'('.sql_get_select('$id_parent', '$table_sql').')'");
2974
	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...
2975
		$where = array("'NOT'", $where);
2976
	}
2977
2978
	$boucle->where[] = $where;
2979
}
2980
2981
/**
2982
 * Compile le critère {feuille}
2983
 *
2984
 * Trouver tous les objets qui n'ont pas d'enfants (les feuilles de l'arbre)
2985
 * {feuille}
2986
 * {!feuille} retourne les noeuds
2987
 *
2988
 * @global array $exceptions_des_tables
2989
 * @param string $idb Identifiant de la boucle
2990
 * @param array $boucles AST du squelette
2991
 * @param Critere $crit Paramètres du critère dans cette boucle
2992
 */
2993
function critere_feuille_dist($idb, &$boucles, $crit) {
2994
	$not = $crit->not;
2995
	$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...
2996
	critere_noeud_dist($idb, $boucles, $crit);
2997
	$crit->not = $not;
2998
}
2999