Completed
Push — master ( 7f3c39...d78940 )
by cam
04:49
created

references.php ➔ compose_filtres()   B

Complexity

Conditions 11
Paths 76

Size

Total Lines 47

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 11
nc 76
nop 2
dl 0
loc 47
rs 7.3166
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
 * Fonctions de recherche et de reservation dans l'arborescence des boucles
15
 *
16
 * @package SPIP\Core\Compilateur\References
17
 **/
18
if (!defined('_ECRIRE_INC_VERSION')) {
19
	return;
20
}
21
22
/**
23
 * Retrouver l'index de la boucle d'une balise
24
 *
25
 * Retrouve à quelle boucle appartient une balise, utile dans le cas
26
 * où une référence explicite est demandée
27
 *
28
 * - `#MABALISE` : l'index est celui de la première boucle englobante
29
 * - `#_autreboucle:MABALISE` : l'index est celui de la boucle _autreboucle
30
 *
31
 * @example
32
 *     Dans une balise dynamique ou calculée :
33
 *     ```
34
 *     $idb = index_boucle($p);
35
 *     ```
36
 *
37
 * @param Champ $p AST au niveau de la balise
38
 * @return string
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...
39
 *
40
 *     - Identifiant de la boucle possédant ce champ.
41
 *     - '' si une référence explicite incorrecte est envoyée
42
 */
43
function index_boucle($p) {
44 View Code Duplication
	if (strlen($p->nom_boucle)) {
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...
45
		// retourne l’index explicite demandé s’il existe
46
		if (!empty($p->boucles[$p->nom_boucle])) {
47
			return $p->nom_boucle;
48
		}
49
		return '';
50
	} 
51
	return $p->id_boucle;
52
}
53
54
55
/**
56
 * Retrouve la boucle mère d’une balise, sauf si son nom est explicité
57
 *
58
 * - `#MABALISE` : l'index sera celui de la boucle parente
59
 * - `#_autreboucle:MABALISE` : l'index est celui de la boucle _autreboucle, si elle existe
60
 * 
61
 * @example
62
 *     Dans une balise dynamique ou calculée :
63
 *     ```
64
 *     $idb = index_boucle_mere($p);
65
 *     ```
66
 *
67
 * @param Champ $p AST au niveau de la balise
68
 * @return string
69
 *
70
 *     - Identifiant de la boucle parente possédant ce champ, ou '' si pas de parent.
71
 *     - '' si une référence explicite incorrecte est envoyée
72
 */
73
function index_boucle_mere($p) {
74 View Code Duplication
	if (strlen($p->nom_boucle)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
75
		// retourne l’index explicite demandé s’il existe
76
		if (!empty($p->boucles[$p->nom_boucle])) {
77
			return $p->nom_boucle;
78
		}
79
		return '';
80
	} 
81
	if (!empty($p->descr['id_mere'])) {
82
		return $p->descr['id_mere'];
83
	}
84
	return '';
85
}
86
87
/**
88
 * Retourne la position dans la pile d'un champ SQL
89
 *
90
 * Retourne le code PHP permettant de récupérer un champ SQL dans
91
 * une boucle parente, en prenant la boucle la plus proche du sommet de pile
92
 * (indiqué par $idb).
93
 *
94
 * Si on ne trouve rien, on considère que ça doit provenir du contexte
95
 * (par l'URL ou l'include) qui a été recopié dans Pile[0]
96
 * (un essai d'affinage a débouché sur un bug vicieux)
97
 *
98
 * Si ca référence un champ SQL, on le mémorise dans la structure $boucles
99
 * afin de construire un requête SQL minimale (plutôt qu'un brutal 'SELECT *')
100
 *
101
 * @param string $idb Identifiant de la boucle
102
 * @param string $nom_champ Nom du champ SQL cherché
103
 * @param array $boucles AST du squelette
104
 * @param string $explicite
105
 *     Indique que le nom de la boucle est explicite dans la balise #_nomboucletruc:CHAMP
106
 * @param null|string $defaut
107
 *     Code par defaut si le champ n'est pas trouvé dans l'index.
108
 *     Utilise @$Pile[0][$nom_champ] si non fourni
109
 * @param bool $remonte_pile
110
 *     Permettre de remonter la pile des boucles ou non (dans ce cas on
111
 *     ne cherche que danss la 1ère boucle englobante)
112
 * @param bool $select
113
 *     Pour ajouter au select de la boucle, par defaut true
114
 * @return string
115
 *     Code PHP pour obtenir le champ SQL
116
 */
117
function index_pile(
118
	$idb,
119
	$nom_champ,
120
	&$boucles,
121
	$explicite = '',
122
	$defaut = null,
123
	$remonte_pile = true,
124
	$select = true
125
) {
126
	if (!is_string($defaut)) {
127
		$defaut = '@$Pile[0][\'' . strtolower($nom_champ) . '\']';
128
	}
129
130
	$i = 0;
131
	if (strlen($explicite)) {
132
		// Recherche d'un champ dans un etage superieur
133
		while (($idb !== $explicite) && ($idb !== '')) {
134
			#	spip_log("Cherchexpl: $nom_champ '$explicite' '$idb' '$i'");
135
			$i++;
136
			$idb = $boucles[$idb]->id_parent;
137
		}
138
	}
139
140
	#	spip_log("Cherche: $nom_champ a partir de '$idb'");
141
	$nom_champ = strtolower($nom_champ);
142
	$conditionnel = array();
143
	// attention: entre la boucle nommee 0, "" et le tableau vide,
144
	// il y a incoherences qu'il vaut mieux eviter
145
	while (isset($boucles[$idb])) {
146
		$joker = true;
147
		// modifie $joker si tous les champs sont autorisés.
148
		// $t = le select pour le champ, si on l'a trouvé (ou si joker)
149
		// $c = le nom du champ demandé
150
		list($t, $c) = index_tables_en_pile($idb, $nom_champ, $boucles, $joker);
0 ignored issues
show
Bug introduced by
It seems like $boucles can also be of type array; however, index_tables_en_pile() does only seem to accept object<Boucle>, 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...
151
		if ($t) {
152 View Code Duplication
			if ($select and !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...
153
				$boucles[$idb]->select[] = $t;
154
			}
155
			$champ = '$Pile[$SP' . ($i ? "-$i" : "") . '][\'' . $c . '\']';
156
			if (!$joker) {
157
				return index_compose($conditionnel, $champ);
158
			}
159
160
			// tant que l'on trouve des tables avec joker, on continue
161
			// avec la boucle parente et on conditionne à l'exécution
162
			// la présence du champ. Si le champ existe à l'exécution
163
			// dans une boucle, il est pris, sinon on le cherche dans le parent...
164
			$conditionnel[] = "isset($champ)?$champ";
165
		}
166
167
		if ($remonte_pile) {
168
			#	spip_log("On remonte vers $i");
169
			// Sinon on remonte d'un cran
170
			$idb = $boucles[$idb]->id_parent;
171
			$i++;
172
		} else {
173
			$idb = null;
174
		}
175
	}
176
177
	#	spip_log("Pas vu $nom_champ");
178
	// esperons qu'il y sera
179
	// ou qu'on a fourni une valeur par "defaut" plus pertinent
180
	return index_compose($conditionnel, $defaut);
181
}
182
183
/**
184
 * Reconstuire la cascade de condition de recherche d'un champ
185
 *
186
 * On ajoute la valeur finale par défaut pour les balises dont on ne saura
187
 * qu'à l'exécution si elles sont definies ou non (boucle DATA)
188
 *
189
 * @param array $conditionnel Liste de codes PHP pour retrouver un champ
190
 * @param string $defaut Valeur par défaut si aucun des moyens ne l'a trouvé
191
 * @return string              Code PHP complet de recherche d'un champ
192
 */
193
function index_compose($conditionnel, $defaut) {
194
	while ($c = array_pop($conditionnel)) {
195
		// si on passe defaut = '', ne pas générer d'erreur de compilation.
196
		$defaut = "($c:(" . ($defaut ? $defaut : "''") . "))";
197
	}
198
199
	return $defaut;
200
}
201
202
/**
203
 * Cherche un champ dans une boucle
204
 *
205
 * Le champ peut être :
206
 *
207
 * - un alias d'un autre : il faut alors le calculer, éventuellement en
208
 *   construisant une jointure.
209
 * - présent dans la table : on l'utilise
210
 * - absent, mais le type de boucle l'autorise (joker des itérateurs DATA) :
211
 *   on l'utilise et lève le drapeau joker
212
 * - absent, on cherche une jointure et on l'utilise si on en trouve.
213
 *
214
 * @todo
215
 *     Ici la recherche de jointure sur l'absence d'un champ ne cherche
216
 *     une jointure que si des jointures explicites sont demandées,
217
 *     et non comme à d'autres endroits sur toutes les jointures possibles.
218
 *     Il faut homogénéiser cela.
219
 *
220
 *
221
 * @param string $idb Identifiant de la boucle
222
 * @param string $nom_champ Nom du champ SQL cherché
223
 * @param Boucle $boucles AST du squelette
224
 * @param bool $joker
225
 *     Le champ peut-il être inconnu à la compilation ?
226
 *     Ce drapeau sera levé si c'est le cas.
227
 * @return array
228
 *     Liste (Nom du champ véritable, nom du champ demandé).
229
 *     Le nom du champ véritable est une expression pour le SELECT de
230
 *     la boucle tel que "rubriques.titre" ou "mots.titre AS titre_mot".
231
 *     Les éléments de la liste sont vides si on ne trouve rien.
232
 **/
233
function index_tables_en_pile($idb, $nom_champ, &$boucles, &$joker) {
234
235
	$r = $boucles[$idb]->type_requete;
236
	// boucle recursive, c'est foutu...
237
	if ($r == TYPE_RECURSIF) {
238
		return array();
239
	}
240
	if (!$r) {
241
		$joker = false; // indiquer a l'appelant
242
		# continuer pour chercher l'erreur suivante
243
		return array("'#" . $r . ':' . $nom_champ . "'", '');
244
	}
245
246
	$desc = $boucles[$idb]->show;
247
	// le nom du champ est il une exception de la table ? un alias ?
248
	$excep = isset($GLOBALS['exceptions_des_tables'][$r]) ? $GLOBALS['exceptions_des_tables'][$r] : '';
249
	if ($excep) {
250
		$excep = isset($excep[$nom_champ]) ? $excep[$nom_champ] : '';
251
	}
252
	if ($excep) {
253
		$joker = false; // indiquer a l'appelant
254
		return index_exception($boucles[$idb], $desc, $nom_champ, $excep);
255
	} // pas d'alias. Le champ existe t'il ?
256
	else {
257
		// le champ est réellement présent, on le prend.
258
		if (isset($desc['field'][$nom_champ])) {
259
			$t = $boucles[$idb]->id_table;
260
			$joker = false; // indiquer a l'appelant
261
			return array("$t.$nom_champ", $nom_champ);
262
		}
263
		// Tous les champs sont-ils acceptés ?
264
		// Si oui, on retourne le champ, et on lève le flag joker
265
		// C'est le cas des itérateurs DATA qui acceptent tout
266
		// et testent la présence du champ à l'exécution et non à la compilation
267
		// car ils ne connaissent pas ici leurs contenus.
268
		elseif (/*$joker AND */
269
		isset($desc['field']['*'])
270
		) {
271
			$joker = true; // indiquer a l'appelant
272
			return array($nom_champ, $nom_champ);
273
		}
274
		// pas d'alias, pas de champ, pas de joker...
275
		// tenter via une jointure...
276
		else {
277
			$joker = false; // indiquer a l'appelant
278
			// regarder si le champ est deja dans une jointure existante
279
			// sinon, si il y a des joitures explicites, la construire
280
			if (!$t = trouver_champ_exterieur($nom_champ, $boucles[$idb]->from, $boucles[$idb])) {
281
				if ($boucles[$idb]->jointures_explicites) {
282
					// [todo] Ne pas lancer que lorsque il y a des jointures explicites !!!!
283
					// fonctionnel, il suffit d'utiliser $boucles[$idb]->jointures au lieu de jointures_explicites
284
					// mais est-ce ce qu'on veut ?
285
					$jointures = preg_split("/\s+/", $boucles[$idb]->jointures_explicites);
286
					if ($cle = trouver_jointure_champ($nom_champ, $boucles[$idb], $jointures)) {
0 ignored issues
show
Unused Code introduced by
$cle 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...
287
						$t = trouver_champ_exterieur($nom_champ, $boucles[$idb]->from, $boucles[$idb]);
288
					}
289
				}
290
			}
291
			if ($t) {
292
				// si on a trouvé une jointure possible, on fait comme
293
				// si c'était une exception pour le champ demandé
294
				return index_exception($boucles[$idb],
295
					$desc,
296
					$nom_champ,
297
					array($t[1]['id_table'], reset($t[2])));
298
			}
299
300
			return array('', '');
301
		}
302
	}
303
}
304
305
306
/**
307
 * Retrouve un alias d'un champ dans une boucle
308
 *
309
 * Référence à une entite SPIP alias d'un champ SQL.
310
 * Ça peut même être d'un champ dans une jointure qu'il faut provoquer
311
 * si ce n'est fait
312
 *
313
 * @param Boucle $boucle Boucle dont on prend un alias de champ
314
 * @param array $desc Description de la table SQL de la boucle
315
 * @param string $nom_champ Nom du champ original demandé
316
 * @param array $excep
317
 *     Description de l'exception pour ce champ. Peut être :
318
 *
319
 *     - string : nom du champ véritable dans la table
320
 *     - array :
321
 *         - liste (table, champ) indique que le véritable champ
322
 *           est dans une autre table et construit la jointure dessus
323
 *         - liste (table, champ, fonction) idem, mais en passant un
324
 *           nom de fonction qui s'occupera de créer la jointure.
325
 * @return array
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use string[].

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...
326
 *     Liste (nom du champ alias, nom du champ). Le nom du champ alias
327
 *     est une expression pour le SELECT de la boucle du style "mots.titre AS titre_mot"
328
 **/
329
function index_exception(&$boucle, $desc, $nom_champ, $excep) {
330
	static $trouver_table;
331
	if (!$trouver_table) {
332
		$trouver_table = charger_fonction('trouver_table', 'base');
333
	}
334
335
	if (is_array($excep)) {
336
		// permettre aux plugins de gerer eux meme des jointures derogatoire ingerables
337
		$t = null;
338
		if (count($excep) == 3) {
339
			$index_exception_derogatoire = array_pop($excep);
340
			$t = $index_exception_derogatoire($boucle, $desc, $nom_champ, $excep);
341
		}
342
		if ($t == null) {
343
			list($e, $x) = $excep;  #PHP4 affecte de gauche a droite
344
			$excep = $x;    #PHP5 de droite a gauche !
345
			$j = $trouver_table($e, $boucle->sql_serveur);
346
			if (!$j) {
347
				return array('', '');
348
			}
349
			$e = $j['table'];
350
			if (!$t = array_search($e, $boucle->from)) {
351
				$k = $j['key']['PRIMARY KEY'];
352
				if (strpos($k, ',')) {
353
					$l = (preg_split('/\s*,\s*/', $k));
354
					$k = $desc['key']['PRIMARY KEY'];
355
					if (!in_array($k, $l)) {
356
						spip_log("jointure impossible $e " . join(',', $l));
357
358
						return array('', '');
359
					}
360
				}
361
				$k = array($boucle->id_table, array($e), $k);
362
				fabrique_jointures($boucle, array($k));
363
				$t = array_search($e, $boucle->from);
364
			}
365
		}
366
	} else {
367
		$t = $boucle->id_table;
368
	}
369
	// demander a SQL de gerer le synonyme
370
	// ca permet que excep soit dynamique (Cedric, 2/3/06)
371
	if ($excep != $nom_champ) {
372
		$excep .= ' AS ' . $nom_champ;
373
	}
374
375
	return array("$t.$excep", $nom_champ);
376
}
377
378
/**
379
 * Demande le champ '$champ' dans la pile
380
 *
381
 * Le champ est cherché dans l'empilement de boucles, sinon dans la valeur
382
 * par défaut (qui est l'environnement du squelette si on ne la précise pas).
383
 *
384
 * @api
385
 * @param string $champ
386
 *     Champ recherché
387
 * @param Champ $p
388
 *     AST au niveau de la balise
389
 * @param null|string $defaut
390
 *     Code de la valeur par défaut si on ne trouve pas le champ dans une
391
 *     des boucles parentes. Sans précision, il sera pris dans l'environnement
392
 *     du squelette.
393
 *     Passer $defaut = '' pour ne pas prendre l'environnement.
394
 * @param bool $remonte_pile
395
 *     Permettre de remonter dans la pile des boucles pour trouver le champ
396
 * @return string
397
 *     Code PHP pour retrouver le champ
398
 */
399
function champ_sql($champ, $p, $defaut = null, $remonte_pile = true) {
400
	return index_pile($p->id_boucle, $champ, $p->boucles, $p->nom_boucle, $defaut, $remonte_pile);
401
}
402
403
404
/**
405
 * Calcule et retourne le code PHP d'exécution d'une balise SPIP et des ses filtres
406
 *
407
 * Cette fonction qui sert d'API au compilateur demande à calculer
408
 * le code PHP d'une balise, puis lui applique les filtres (automatiques
409
 * et décrits dans le squelette)
410
 *
411
 * @uses calculer_balise()
412
 * @uses applique_filtres()
413
 *
414
 * @param Champ $p
415
 *     AST au niveau de la balise
416
 * @return string
417
 *     Code PHP pour d'exécution de la balise et de ses filtres
418
 **/
419
function calculer_champ($p) {
420
	$p = calculer_balise($p->nom_champ, $p);
421
422
	return applique_filtres($p);
423
}
424
425
426
/**
427
 * Calcule et retourne le code PHP d'exécution d'une balise SPIP
428
 *
429
 * Cette fonction qui sert d'API au compilateur demande à calculer
430
 * le code PHP d'une balise (cette fonction ne calcule pas les éventuels
431
 * filtres de la balise).
432
 *
433
 * Pour une balise nommmée `NOM`, elle demande à `charger_fonction()` de chercher
434
 * s'il existe une fonction `balise_NOM` ou `balise_NOM_dist`
435
 * éventuellement en chargeant le fichier `balise/NOM.php.`
436
 *
437
 * Si la balise est de la forme `PREFIXE_SUFFIXE` (cf `LOGO_*` et `URL_*`)
438
 * elle fait de même avec juste le `PREFIXE`.
439
 *
440
 * S'il n'y a pas de fonction trouvée, on considère la balise comme une référence
441
 * à une colonne de table SQL connue, sinon à l'environnement (cf. `calculer_balise_DEFAUT_dist()`).
442
 *
443
 * Les surcharges des colonnes SQL via charger_fonction sont donc possibles.
444
 *
445
 * @uses calculer_balise_DEFAUT_dist()
446
 *     Lorsqu'aucune fonction spécifique n'est trouvée.
447
 * @see  charger_fonction()
448
 *     Pour la recherche des fonctions de balises
449
 *
450
 * @param string $nom
451
 *     Nom de la balise
452
 * @param Champ $p
453
 *     AST au niveau de la balise
454
 * @return Champ
455
 *     Pile complétée par le code PHP pour l'exécution de la balise et de ses filtres
456
 **/
457
function calculer_balise($nom, $p) {
458
459
	// S'agit-t-il d'une balise_XXXX[_dist]() ?
460
	if ($f = charger_fonction($nom, 'balise', true)) {
461
		$p->balise_calculee = true;
462
		$res = $f($p);
463
		if ($res !== null and is_object($res)) {
464
			return $res;
465
		}
466
	}
467
468
	// Certaines des balises comportant un _ sont generiques
469
	if ($balise_generique = chercher_balise_generique($nom)) {
470
		$res = $balise_generique['fonction_generique']($p);
471
		if ($res !== null and is_object($res)) {
472
			return $res;
473
		}
474
	}
475
476
	$f = charger_fonction('DEFAUT', 'calculer_balise');
477
478
	return $f($nom, $p);
479
}
480
481
482
/**
483
 * Calcule et retourne le code PHP d'exécution d'une balise SPIP non déclarée
484
 *
485
 * Cette fonction demande à calculer le code PHP d'une balise qui
486
 * n'a pas de fonction spécifique.
487
 *
488
 * On considère la balise comme une référence à une colonne de table SQL
489
 * connue, sinon à l'environnement.
490
 *
491
 * @uses index_pile()
492
 *     Pour la recherche de la balise comme colonne SQL ou comme environnement
493
 * @note
494
 *     Le texte de la balise est retourné si il ressemble à une couleur
495
 *     et qu'aucun champ correspondant n'a été trouvé, comme `#CCAABB`
496
 *
497
 * @param string $nom
498
 *     Nom de la balise
499
 * @param Champ $p
500
 *     AST au niveau de la balise
501
 * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be Champ?

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...
502
 *     Code PHP pour d'exécution de la balise et de ses filtres
503
 **/
504
function calculer_balise_DEFAUT_dist($nom, $p) {
505
506
	// ca pourrait etre un champ SQL homonyme,
507
	$p->code = index_pile($p->id_boucle, $nom, $p->boucles, $p->nom_boucle);
508
509
	// compatibilite: depuis qu'on accepte #BALISE{ses_args} sans [(...)] autour
510
	// il faut recracher {...} quand ce n'est finalement pas des args
511
	if ($p->fonctions and (!$p->fonctions[0][0]) and $p->fonctions[0][1]) {
512
		$code = addslashes($p->fonctions[0][1]);
513
		$p->code .= " . '$code'";
514
	}
515
516
	// ne pas passer le filtre securite sur les id_xxx
517
	if (strpos($nom, 'ID_') === 0) {
518
		$p->interdire_scripts = false;
519
	}
520
521
	// Compatibilite ascendante avec les couleurs html (#FEFEFE) :
522
	// SI le champ SQL n'est pas trouve
523
	// ET si la balise a une forme de couleur
524
	// ET s'il n'y a ni filtre ni etoile
525
	// ALORS retourner la couleur.
526
	// Ca permet si l'on veut vraiment de recuperer [(#ACCEDE*)]
527
	if (preg_match("/^[A-F]{1,6}$/i", $nom)
528
		and !$p->etoile
529
		and !$p->fonctions
530
	) {
531
		$p->code = "'#$nom'";
532
		$p->interdire_scripts = false;
533
	}
534
535
	return $p;
536
}
537
538
539
/** Code PHP d'exécution d'une balise dynamique */
540
define('CODE_EXECUTER_BALISE', "executer_balise_dynamique('%s',
541
	array(%s%s),
542
	array(%s%s))");
543
544
545
/**
546
 * Calcule le code PHP d'exécution d'une balise SPIP dynamique
547
 *
548
 * Calcule les balises dynamiques, notamment les `formulaire_*`.
549
 *
550
 * Inclut le fichier associé à son nom, qui contient la fonction homonyme
551
 * donnant les arguments à chercher dans la pile, et qui sont donc compilés.
552
 *
553
 * On leur adjoint les arguments explicites de la balise (cf `#LOGIN{url}`)
554
 * et d'éventuelles valeurs transmises d'autorité par la balise.
555
 * (cf https://core.spip.net/issues/1728)
556
 *
557
 * La fonction `executer_balise_dynamique()` définie par la
558
 * constante `CODE_EXECUTER_BALISE` recevra à l'exécution la valeur de tout ca.
559
 *
560
 * @uses collecter_balise_dynamique()
561
 *     Qui calcule le code d'exécution de chaque argument de la balise
562
 * @see  executer_balise_dynamique()
563
 *     Code PHP produit qui chargera les fonctions de la balise dynamique à l'exécution,
564
 *     appelée avec les arguments calculés.
565
 * @param Champ $p
566
 *     AST au niveau de la balise
567
 * @param string $nom
568
 *     Nom de la balise dynamique
569
 * @param array $l
570
 *     Liste des noms d'arguments (balises) à collecter
571
 * @param array $supp
572
 *     Liste de données supplémentaires à transmettre au code d'exécution.
573
 * @return Champ
574
 *     Balise complétée de son code d'exécution
575
 **/
576
function calculer_balise_dynamique($p, $nom, $l, $supp = array()) {
577
578
	if (!balise_distante_interdite($p)) {
579
		$p->code = "''";
580
581
		return $p;
582
	}
583
	// compatibilite: depuis qu'on accepte #BALISE{ses_args} sans [(...)] autour
584
	// il faut recracher {...} quand ce n'est finalement pas des args
585
	if ($p->fonctions and (!$p->fonctions[0][0]) and $p->fonctions[0][1]) {
586
		$p->fonctions = null;
587
	}
588
589
	if ($p->param and ($c = $p->param[0])) {
590
		// liste d'arguments commence toujours par la chaine vide
591
		array_shift($c);
592
		// construire la liste d'arguments comme pour un filtre
593
		$param = compose_filtres_args($p, $c, ',');
594
	} else {
595
		$param = "";
596
	}
597
	$collecte = collecter_balise_dynamique($l, $p, $nom);
598
599
	$p->code = sprintf(CODE_EXECUTER_BALISE, $nom,
600
		join(',', $collecte),
601
		($collecte ? $param : substr($param, 1)), # virer la virgule
602
		memoriser_contexte_compil($p),
603
		(!$supp ? '' : (', ' . join(',', $supp))));
604
605
	$p->interdire_scripts = false;
606
607
	return $p;
608
}
609
610
611
/**
612
 * Construction du tableau des arguments d'une balise dynamique.
613
 *
614
 * Pour chaque argument (un nom de balise), crée le code PHP qui le calculera.
615
 *
616
 * @note
617
 *     Ces arguments peuvent être eux-même des balises (cf FORMULAIRE_SIGNATURE)
618
 *     mais gare au bouclage (on peut s'aider de `$nom` pour le réperer au besoin)
619
 *
620
 *     En revanche ils n'ont pas de filtres, donc on appelle `calculer_balise()` qui
621
 *     ne s'occupe pas de ce qu'il y a dans `$p` (mais qui va y ecrire le code)
622
 *
623
 * @uses calculer_balise()
624
 *     Pour obtenir le code d'éxécution de chaque argument.
625
 *
626
 * @param array $l
627
 *     Liste des noms d'arguments (balises) à collecter (chaque argument
628
 *     de la balise dynamique est considéré comme étant un nom de balise)
629
 * @param Champ $p
630
 *     AST au niveau de la balise
631
 * @param string $nom
632
 *     Nom de la balise
633
 * @return array
634
 *     Liste des codes PHP d'éxecution des balises collectées
635
 **/
636
function collecter_balise_dynamique($l, &$p, $nom) {
0 ignored issues
show
Unused Code introduced by
The parameter $nom 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...
637
	$args = array();
638
	foreach ($l as $c) {
639
		$x = calculer_balise($c, $p);
640
		$args[] = $x->code;
641
	}
642
643
	return $args;
644
}
645
646
647
/**
648
 * Récuperer le nom du serveur
649
 *
650
 * Mais pas si c'est un serveur spécifique dérogatoire
651
 *
652
 * @param Champ $p
653
 *     AST positionné sur la balise
654
 * @return string
655
 *     Nom de la connexion
656
 **/
657
function trouver_nom_serveur_distant($p) {
658
	$nom = $p->id_boucle;
659
	if ($nom
660
		and isset($p->boucles[$nom])
661
	) {
662
		$s = $p->boucles[$nom]->sql_serveur;
663
		if (strlen($s)
664
			and strlen($serveur = strtolower($s))
665
			and !in_array($serveur, $GLOBALS['exception_des_connect'])
666
		) {
667
			return $serveur;
668
		}
669
	}
670
671
	return "";
672
}
673
674
675
/**
676
 * Teste si une balise est appliquée sur une base distante
677
 *
678
 * La fonction loge une erreur si la balise est utilisée sur une
679
 * base distante et retourne false dans ce cas.
680
 *
681
 * @note
682
 *     Il faudrait savoir traiter les formulaires en local
683
 *     tout en appelant le serveur SQL distant.
684
 *     En attendant, cette fonction permet de refuser une authentification
685
 *     sur quelque-chose qui n'a rien a voir.
686
 *
687
 * @param Champ $p
688
 *     AST positionné sur la balise
689
 * @return bool
690
 *
691
 *     - true : La balise est autorisée
692
 *     - false : La balise est interdite car le serveur est distant
693
 **/
694
function balise_distante_interdite($p) {
695
	$nom = $p->id_boucle;
696
697
	if ($nom and trouver_nom_serveur_distant($p)) {
698
		spip_log($nom . ':' . $p->nom_champ . ' ' . _T('zbug_distant_interdit'));
699
700
		return false;
701
	}
702
703
	return true;
704
}
705
706
707
//
708
// Traitements standard de divers champs
709
// definis par $table_des_traitements, cf. ecrire/public/interfaces
710
//
711
// https://code.spip.net/@champs_traitements
712
function champs_traitements($p) {
713
714
	if (isset($GLOBALS['table_des_traitements'][$p->nom_champ])) {
715
		$ps = $GLOBALS['table_des_traitements'][$p->nom_champ];
716
	} else {
717
		// quand on utilise un traitement catch-all *
718
		// celui-ci ne s'applique pas sur les balises calculees qui peuvent gerer
719
		// leur propre securite
720
		if (!$p->balise_calculee) {
721
			$ps = $GLOBALS['table_des_traitements']['*'];
722
		} else {
723
			$ps = false;
724
		}
725
	}
726
727
	if (is_array($ps)) {
728
		// Recuperer le type de boucle (articles, DATA) et la table SQL sur laquelle elle porte
729
		$idb = index_boucle($p);
730
		// mais on peut aussi etre hors boucle. Se mefier.
731
		$type_requete = isset($p->boucles[$idb]->type_requete) ? $p->boucles[$idb]->type_requete : false;
732
		$table_sql = isset($p->boucles[$idb]->show['table_sql']) ? $p->boucles[$idb]->show['table_sql'] : false;
733
734
		// bien prendre en compte les alias de boucles (hierarchie => rubrique, syndication => syncdic, etc.)
735
		if ($type_requete and isset($GLOBALS['table_des_tables'][$type_requete])) {
736
			$type_alias = $type_requete;
737
			$type_requete = $GLOBALS['table_des_tables'][$type_requete];
738
		} else {
739
			$type_alias = false;
740
		}
741
742
		// le traitement peut n'etre defini que pour une table en particulier "spip_articles"
743
		if ($table_sql and isset($ps[$table_sql])) {
744
			$ps = $ps[$table_sql];
745
		} // ou pour une boucle en particulier "DATA","articles"
746
		elseif ($type_requete and isset($ps[$type_requete])) {
747
			$ps = $ps[$type_requete];
748
		} // ou pour une boucle utilisant un alias ("hierarchie")
749
		elseif ($type_alias and isset($ps[$type_alias])) {
750
			$ps = $ps[$type_alias];
751
		} // ou pour indifféremment quelle que soit la boucle
752
		elseif (isset($ps[0])) {
753
			$ps = $ps[0];
754
		} else {
755
			$ps = false;
756
		}
757
	}
758
759
	if (!$ps) {
760
		return $p->code;
761
	}
762
763
	// Si une boucle DOCUMENTS{doublons} est presente dans le squelette,
764
	// ou si in INCLURE contient {doublons}
765
	// on insere une fonction de remplissage du tableau des doublons 
766
	// dans les filtres propre() ou typo()
767
	// (qui traitent les raccourcis <docXX> referencant les docs)
768
769
	if (isset($p->descr['documents'])
770
		and
771
		$p->descr['documents']
772
		and (
773
			(strpos($ps, 'propre') !== false)
774
			or
775
			(strpos($ps, 'typo') !== false)
776
		)
777
	) {
778
		$ps = 'traiter_doublons_documents($doublons, ' . $ps . ')';
779
	}
780
781
	// La protection des champs par |safehtml est assuree par les extensions
782
	// dans la declaration des traitements des champs sensibles
783
784
	// Remplacer enfin le placeholder %s par le vrai code de la balise
785
	return str_replace('%s', $p->code, $ps);
786
}
787
788
789
//
790
// Appliquer les filtres a un champ [(#CHAMP|filtre1|filtre2)]
791
// retourne un code php compile exprimant ce champ filtre et securise
792
//  - une etoile => pas de processeurs standards
793
//  - deux etoiles => pas de securite non plus !
794
//
795
// https://code.spip.net/@applique_filtres
796
function applique_filtres($p) {
797
798
	// Traitements standards (cf. supra)
799
	if ($p->etoile == '') {
800
		$code = champs_traitements($p);
801
	} else {
802
		$code = $p->code;
803
	}
804
805
	// Appliquer les filtres perso
806
	if ($p->param) {
807
		$code = compose_filtres($p, $code);
808
	}
809
810
	// S'il y a un lien avec la session, ajouter un code qui levera
811
	// un drapeau dans la structure d'invalidation $Cache
812
	if (isset($p->descr['session'])) {
813
		$code = "invalideur_session(\$Cache, $code)";
814
	}
815
816
	$code = sandbox_composer_interdire_scripts($code, $p);
817
818
	return $code;
819
}
820
821
// Cf. function pipeline dans ecrire/inc_utils.php
822
// https://code.spip.net/@compose_filtres
823
function compose_filtres(&$p, $code) {
824
825
	$image_miette = false;
826
	foreach ($p->param as $filtre) {
827
		$fonc = array_shift($filtre);
828
		if (!$fonc) {
829
			continue;
830
		} // normalement qu'au premier tour.
831
		$is_filtre_image = ((substr($fonc, 0, 6) == 'image_') and $fonc != 'image_graver');
832
		if ($image_miette and !$is_filtre_image) {
833
			// il faut graver maintenant car apres le filtre en cours
834
			// on est pas sur d'avoir encore le nom du fichier dans le pipe
835
			$code = "filtrer('image_graver', $code)";
836
			$image_miette = false;
837
		}
838
839
		// recuperer les arguments du filtre, 
840
		// a separer par "," ou ":" dans le cas du filtre "?{a,b}"
841
		$countfiltre = count($filtre);
842
		if ($fonc !== '?') {
843
			$sep = ',';
844
		} else {
845
			$sep = ':';
846
			// |?{a,b} *doit* avoir exactement 2 arguments ; on les force
847
			if ($countfiltre != 2) {
848
				$filtre = array($filtre[0] ?? '', $filtre[1] ?? '');
849
				$countfiltre = 2;
850
			}
851
		}
852
		$arglist = compose_filtres_args($p, $filtre, $sep);
853
		$logique = filtre_logique($fonc, $code, substr($arglist, 1));
854
		if ($logique) {
855
			$code = $logique;
856
		} else {
857
			$code = sandbox_composer_filtre($fonc, $code, $arglist, $p, $countfiltre);
858
			if ($is_filtre_image) {
859
				$image_miette = true;
860
			}
861
		}
862
	}
863
	// ramasser les images intermediaires inutiles et graver l'image finale
864
	if ($image_miette) {
865
		$code = "filtrer('image_graver',$code)";
866
	}
867
868
	return $code;
869
}
870
871
// Filtres et,ou,oui,non,sinon,xou,xor,and,or,not,yes
872
// et comparateurs
873
function filtre_logique($fonc, $code, $arg) {
874
875
	switch (true) {
876
		case in_array($fonc, $GLOBALS['table_criteres_infixes']):
877
			return "($code $fonc $arg)";
878
		case ($fonc == 'and') or ($fonc == 'et'):
879
			return "((($code) AND ($arg)) ?' ' :'')";
880
		case ($fonc == 'or') or ($fonc == 'ou'):
881
			return "((($code) OR ($arg)) ?' ' :'')";
882
		case ($fonc == 'xor') or ($fonc == 'xou'):
883
			return "((($code) XOR ($arg)) ?' ' :'')";
884
		case ($fonc == 'sinon'):
885
			return "(((\$a = $code) OR (is_string(\$a) AND strlen(\$a))) ? \$a : $arg)";
886
		case ($fonc == 'not') or ($fonc == 'non'):
887
			return "(($code) ?'' :' ')";
888
		case ($fonc == 'yes') or ($fonc == 'oui'):
889
			return "(($code) ?' ' :'')";
890
	}
891
892
	return '';
893
}
894
895
// https://code.spip.net/@compose_filtres_args
896
function compose_filtres_args($p, $args, $sep) {
897
	$arglist = "";
898
	foreach ($args as $arg) {
899
		$arglist .= $sep .
900
			calculer_liste($arg, $p->descr, $p->boucles, $p->id_boucle);
901
	}
902
903
	return $arglist;
904
}
905
906
907
/**
908
 * Réserve les champs necessaires à la comparaison avec le contexte donné par
909
 * la boucle parente.
910
 *
911
 * Attention en recursif il faut les réserver chez soi-même ET chez sa maman
912
 *
913
 * @param string $idb Identifiant de la boucle
914
 * @param string $nom_champ
915
 * @param array $boucles AST du squelette
916
 * @param null|string $defaut
917
 * @return
918
 **/
919
function calculer_argument_precedent($idb, $nom_champ, &$boucles, $defaut = null) {
920
921
	// si recursif, forcer l'extraction du champ SQL mais ignorer le code
922
	if ($boucles[$idb]->externe) {
923
		index_pile($idb, $nom_champ, $boucles, '', $defaut);
924
		// retourner $Pile[$SP] et pas $Pile[0] si recursion en 1ere boucle
925
		// on ignore le defaut fourni dans ce cas
926
		$defaut = "@\$Pile[\$SP]['$nom_champ']";
927
	}
928
929
	return index_pile($boucles[$idb]->id_parent, $nom_champ, $boucles, '', $defaut);
930
}
931
932
//
933
// Rechercher dans la pile des boucles actives celle ayant un critere
934
// comportant un certain $motif, et construire alors une reference
935
// a l'environnement de cette boucle, qu'on indexe avec $champ.
936
// Sert a referencer une cellule non declaree dans la table et pourtant la.
937
// Par exemple pour la balise #POINTS on produit $Pile[$SP-n]['points']
938
// si la n-ieme boucle a un critere "recherche", car on sait qu'il a produit
939
// "SELECT XXXX AS points"
940
//
941
942
// https://code.spip.net/@rindex_pile
943
function rindex_pile($p, $champ, $motif) {
944
	$n = 0;
945
	$b = $p->id_boucle;
946
	$p->code = '';
947
	while ($b != '') {
948
		foreach ($p->boucles[$b]->criteres as $critere) {
949
			if ($critere->op == $motif) {
950
				$p->code = '$Pile[$SP' . (($n == 0) ? "" : "-$n") .
951
					"]['$champ']";
952
				$b = '';
0 ignored issues
show
Unused Code introduced by
$b 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...
953
				break 2;
954
			}
955
		}
956
		$n++;
957
		$b = $p->boucles[$b]->id_parent;
958
	}
959
960
	// si on est hors d'une boucle de {recherche}, cette balise est vide
961
	if (!$p->code) {
962
		$p->code = "''";
963
	}
964
965
	$p->interdire_scripts = false;
966
967
	return $p;
968
}
969
970
/** 
971
 * Retourne le nom de la balise indiquée pour les messages d’erreurs
972
 * @param Pile $p Description de la balise
973
 * @param string $champ Nom du champ
974
 * @return string Nom de la balise, avec indication de boucle explicite si présent.
975
 */
976
function zbug_presenter_champ($p, $champ = "") {
977
	$balise = $champ ? $champ : $p->nom_champ;
978
	$explicite = $explicite = $p->nom_boucle ? $p->nom_boucle . ':' : '';
979
	return "#{$explicite}{$balise}";
980
}