Completed
Push — master ( d1d834...c4f810 )
by cam
04:11
created

references.php ➔ champs_traitements()   F

Complexity

Conditions 18
Paths 297

Size

Total Lines 69

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 18
nc 297
nop 1
dl 0
loc 69
rs 2.8208
c 0
b 0
f 0

How to fix   Long Method    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_requete = $GLOBALS['table_des_tables'][$type_requete];
737
		}
738
739
		// le traitement peut n'etre defini que pour une table en particulier "spip_articles"
740
		if ($table_sql and isset($ps[$table_sql])) {
741
			$ps = $ps[$table_sql];
742
		} // ou pour une boucle en particulier "DATA","articles"
743
		elseif ($type_requete and isset($ps[$type_requete])) {
744
			$ps = $ps[$type_requete];
745
		} // ou pour indifféremment quelle que soit la boucle
746
		elseif (isset($ps[0])) {
747
			$ps = $ps[0];
748
		} else {
749
			$ps = false;
750
		}
751
	}
752
753
	if (!$ps) {
754
		return $p->code;
755
	}
756
757
	// Si une boucle DOCUMENTS{doublons} est presente dans le squelette,
758
	// ou si in INCLURE contient {doublons}
759
	// on insere une fonction de remplissage du tableau des doublons 
760
	// dans les filtres propre() ou typo()
761
	// (qui traitent les raccourcis <docXX> referencant les docs)
762
763
	if (isset($p->descr['documents'])
764
		and
765
		$p->descr['documents']
766
		and (
767
			(strpos($ps, 'propre') !== false)
768
			or
769
			(strpos($ps, 'typo') !== false)
770
		)
771
	) {
772
		$ps = 'traiter_doublons_documents($doublons, ' . $ps . ')';
773
	}
774
775
	// La protection des champs par |safehtml est assuree par les extensions
776
	// dans la declaration des traitements des champs sensibles
777
778
	// Remplacer enfin le placeholder %s par le vrai code de la balise
779
	return str_replace('%s', $p->code, $ps);
780
}
781
782
783
//
784
// Appliquer les filtres a un champ [(#CHAMP|filtre1|filtre2)]
785
// retourne un code php compile exprimant ce champ filtre et securise
786
//  - une etoile => pas de processeurs standards
787
//  - deux etoiles => pas de securite non plus !
788
//
789
// https://code.spip.net/@applique_filtres
790
function applique_filtres($p) {
791
792
	// Traitements standards (cf. supra)
793
	if ($p->etoile == '') {
794
		$code = champs_traitements($p);
795
	} else {
796
		$code = $p->code;
797
	}
798
799
	// Appliquer les filtres perso
800
	if ($p->param) {
801
		$code = compose_filtres($p, $code);
802
	}
803
804
	// S'il y a un lien avec la session, ajouter un code qui levera
805
	// un drapeau dans la structure d'invalidation $Cache
806
	if (isset($p->descr['session'])) {
807
		$code = "invalideur_session(\$Cache, $code)";
808
	}
809
810
	$code = sandbox_composer_interdire_scripts($code, $p);
811
812
	return $code;
813
}
814
815
// Cf. function pipeline dans ecrire/inc_utils.php
816
// https://code.spip.net/@compose_filtres
817
function compose_filtres(&$p, $code) {
818
819
	$image_miette = false;
820
	foreach ($p->param as $filtre) {
821
		$fonc = array_shift($filtre);
822
		if (!$fonc) {
823
			continue;
824
		} // normalement qu'au premier tour.
825
		$is_filtre_image = ((substr($fonc, 0, 6) == 'image_') and $fonc != 'image_graver');
826
		if ($image_miette and !$is_filtre_image) {
827
			// il faut graver maintenant car apres le filtre en cours
828
			// on est pas sur d'avoir encore le nom du fichier dans le pipe
829
			$code = "filtrer('image_graver', $code)";
830
			$image_miette = false;
831
		}
832
		// recuperer les arguments du filtre, 
833
		// a separer par "," ou ":" dans le cas du filtre "?{a,b}"
834
		if ($fonc !== '?') {
835
			$sep = ',';
836
		} else {
837
			$sep = ':';
838
			// |?{a,b} *doit* avoir exactement 2 arguments ; on les force
839
			if (count($filtre) != 2) {
840
				$filtre = array(isset($filtre[0]) ? $filtre[0] : "", isset($filtre[1]) ? $filtre[1] : "");
841
			}
842
		}
843
		$arglist = compose_filtres_args($p, $filtre, $sep);
844
		$logique = filtre_logique($fonc, $code, substr($arglist, 1));
845
		if ($logique) {
846
			$code = $logique;
847
		} else {
848
			$code = sandbox_composer_filtre($fonc, $code, $arglist, $p);
849
			if ($is_filtre_image) {
850
				$image_miette = true;
851
			}
852
		}
853
	}
854
	// ramasser les images intermediaires inutiles et graver l'image finale
855
	if ($image_miette) {
856
		$code = "filtrer('image_graver',$code)";
857
	}
858
859
	return $code;
860
}
861
862
// Filtres et,ou,oui,non,sinon,xou,xor,and,or,not,yes
863
// et comparateurs
864
function filtre_logique($fonc, $code, $arg) {
865
866
	switch (true) {
867
		case in_array($fonc, $GLOBALS['table_criteres_infixes']):
868
			return "($code $fonc $arg)";
869
		case ($fonc == 'and') or ($fonc == 'et'):
870
			return "((($code) AND ($arg)) ?' ' :'')";
871
		case ($fonc == 'or') or ($fonc == 'ou'):
872
			return "((($code) OR ($arg)) ?' ' :'')";
873
		case ($fonc == 'xor') or ($fonc == 'xou'):
874
			return "((($code) XOR ($arg)) ?' ' :'')";
875
		case ($fonc == 'sinon'):
876
			return "(((\$a = $code) OR (is_string(\$a) AND strlen(\$a))) ? \$a : $arg)";
877
		case ($fonc == 'not') or ($fonc == 'non'):
878
			return "(($code) ?'' :' ')";
879
		case ($fonc == 'yes') or ($fonc == 'oui'):
880
			return "(($code) ?' ' :'')";
881
	}
882
883
	return '';
884
}
885
886
// https://code.spip.net/@compose_filtres_args
887
function compose_filtres_args($p, $args, $sep) {
888
	$arglist = "";
889
	foreach ($args as $arg) {
890
		$arglist .= $sep .
891
			calculer_liste($arg, $p->descr, $p->boucles, $p->id_boucle);
892
	}
893
894
	return $arglist;
895
}
896
897
898
/**
899
 * Réserve les champs necessaires à la comparaison avec le contexte donné par
900
 * la boucle parente.
901
 *
902
 * Attention en recursif il faut les réserver chez soi-même ET chez sa maman
903
 *
904
 * @param string $idb Identifiant de la boucle
905
 * @param string $nom_champ
906
 * @param array $boucles AST du squelette
907
 * @param null|string $defaut
908
 * @return
909
 **/
910
function calculer_argument_precedent($idb, $nom_champ, &$boucles, $defaut = null) {
911
912
	// si recursif, forcer l'extraction du champ SQL mais ignorer le code
913
	if ($boucles[$idb]->externe) {
914
		index_pile($idb, $nom_champ, $boucles, '', $defaut);
915
		// retourner $Pile[$SP] et pas $Pile[0] si recursion en 1ere boucle
916
		// on ignore le defaut fourni dans ce cas
917
		$defaut = "@\$Pile[\$SP]['$nom_champ']";
918
	}
919
920
	return index_pile($boucles[$idb]->id_parent, $nom_champ, $boucles, '', $defaut);
921
}
922
923
//
924
// Rechercher dans la pile des boucles actives celle ayant un critere
925
// comportant un certain $motif, et construire alors une reference
926
// a l'environnement de cette boucle, qu'on indexe avec $champ.
927
// Sert a referencer une cellule non declaree dans la table et pourtant la.
928
// Par exemple pour la balise #POINTS on produit $Pile[$SP-n]['points']
929
// si la n-ieme boucle a un critere "recherche", car on sait qu'il a produit
930
// "SELECT XXXX AS points"
931
//
932
933
// https://code.spip.net/@rindex_pile
934
function rindex_pile($p, $champ, $motif) {
935
	$n = 0;
936
	$b = $p->id_boucle;
937
	$p->code = '';
938
	while ($b != '') {
939
		foreach ($p->boucles[$b]->criteres as $critere) {
940
			if ($critere->op == $motif) {
941
				$p->code = '$Pile[$SP' . (($n == 0) ? "" : "-$n") .
942
					"]['$champ']";
943
				$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...
944
				break 2;
945
			}
946
		}
947
		$n++;
948
		$b = $p->boucles[$b]->id_parent;
949
	}
950
951
	// si on est hors d'une boucle de {recherche}, cette balise est vide
952
	if (!$p->code) {
953
		$p->code = "''";
954
	}
955
956
	$p->interdire_scripts = false;
957
958
	return $p;
959
}
960
961
/** 
962
 * Retourne le nom de la balise indiquée pour les messages d’erreurs
963
 * @param Pile $p Description de la balise
964
 * @param string $champ Nom du champ
965
 * @return string Nom de la balise, avec indication de boucle explicite si présent.
966
 */
967
function zbug_presenter_champ($p, $champ = "") {
968
	$balise = $champ ? $champ : $p->nom_champ;
969
	$explicite = $explicite = $p->nom_boucle ? $p->nom_boucle . ':' : '';
970
	return "#{$explicite}{$balise}";
971
}