Completed
Push — master ( d39161...71a7d1 )
by cam
10:25 queued 05:02
created

phraser_html.php ➔ public_trouver_premiere_boucle()   D

Complexity

Conditions 18
Paths 69

Size

Total Lines 78

Duplication

Lines 6
Ratio 7.69 %

Importance

Changes 0
Metric Value
cc 18
nc 69
nop 4
dl 6
loc 78
rs 4.8666
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
 * Phraseur d'un squelette ayant une syntaxe SPIP/HTML
15
 *
16
 * Ce fichier transforme un squelette en un tableau d'objets de classe Boucle
17
 * il est chargé par un include calculé pour permettre différentes syntaxes en entrée
18
 *
19
 * @package SPIP\Core\Compilateur\Phraseur
20
 **/
21
22
if (!defined('_ECRIRE_INC_VERSION')) {
23
	return;
24
}
25
26
/** Début de la partie principale d'une boucle */
27
define('BALISE_BOUCLE', '<BOUCLE');
28
/** Fin de la partie principale d'une boucle */
29
define('BALISE_FIN_BOUCLE', '</BOUCLE');
30
/** Début de la partie avant non optionnelle d'une boucle (toujours affichee)*/
31
define('BALISE_PREAFF_BOUCLE', '<BB');
32
/** Début de la partie optionnelle avant d'une boucle */
33
define('BALISE_PRECOND_BOUCLE', '<B');
34
/** Fin de la partie optionnelle après d'une boucle */
35
define('BALISE_POSTCOND_BOUCLE', '</B');
36
/** Fin de la partie après non optionnelle d'une boucle (toujours affichee) */
37
define('BALISE_POSTAFF_BOUCLE', '</BB');
38
/** Fin de la partie alternative après d'une boucle */
39
define('BALISE_ALT_BOUCLE', '<//B');
40
41
/** Indique un début de boucle récursive */
42
define('TYPE_RECURSIF', 'boucle');
43
/** Expression pour trouver le type de boucle (TABLE autre_table ?) */
44
define('SPEC_BOUCLE', '/\s*\(\s*([^\s?)]+)(\s*[^)?]*)([?]?)\)/');
45
/** Expression pour trouver un identifiant de boucle */
46
define('NOM_DE_BOUCLE', "[0-9]+|[-_][-_.a-zA-Z0-9]*");
47
/**
48
 * Nom d'une balise #TOTO
49
 *
50
 * Écriture alambiquée pour rester compatible avec les hexadecimaux des vieux squelettes */
51
define('NOM_DE_CHAMP', "#((" . NOM_DE_BOUCLE . "):)?(([A-F]*[G-Z_][A-Z_0-9]*)|[A-Z_]+)\b(\*{0,2})");
52
/** Balise complète [...(#TOTO) ... ] */
53
define('CHAMP_ETENDU', '/\[([^]\[]*)\(' . NOM_DE_CHAMP . '([^[)]*\)[^]\[]*)\]/S');
54
55
define('BALISE_INCLURE', '/<INCLU[DR]E[[:space:]]*(\(([^)]*)\))?/S');
56
define('BALISE_POLYGLOTTE', ',<multi>(.*)</multi>,Uims');
57
define('BALISE_IDIOMES', ',<:(([a-z0-9_]+):)?([a-z0-9_]*)({([^\|=>]*=[^\|>]*)})?((\|[^>]*)?:/?>),iS');
58
define('BALISE_IDIOMES_ARGS', '@^\s*([^= ]*)\s*=\s*((' . NOM_DE_CHAMP . '[{][^}]*})?[^,]*)\s*,?\s*@s');
59
60
/** Champ sql dans parenthèse ex: (id_article) */
61
define('SQL_ARGS', '(\([^)]*\))');
62
/** Fonction SQL sur un champ ex: SUM(visites) */
63
define('CHAMP_SQL_PLUS_FONC', '`?([A-Z_\/][A-Z_\/0-9.]*)' . SQL_ARGS . '?`?');
64
65
// https://code.spip.net/@phraser_inclure
66
function phraser_inclure($texte, $ligne, $result) {
67
68
	while (preg_match(BALISE_INCLURE, $texte, $match)) {
69
		$match = array_pad($match, 3, null);
70
		$p = strpos($texte, $match[0]);
71
		$debut = substr($texte, 0, $p);
72
		if ($p) {
73
			$result = phraser_idiomes($debut, $ligne, $result);
74
		}
75
		$ligne += substr_count($debut, "\n");
76
		$champ = new Inclure;
77
		$champ->ligne = $ligne;
78
		$ligne += substr_count($match[0], "\n");
79
		$fichier = $match[2];
80
		# assurer ici la migration .php3 => .php
81
		# et de l'ancienne syntaxe INCLURE(page.php3) devenue surperflue
82
		if ($fichier and preg_match(',^(.*[.]php)3$,', $fichier, $r)) {
83
			$fichier = $r[1];
84
		}
85
		$champ->texte = ($fichier !== 'page.php') ? $fichier : '';
86
		$texte = substr($texte, $p + strlen($match[0]));
87
		// on assimile {var=val} a une liste de un argument sans fonction
88
		$pos_apres = 0;
89
		phraser_args($texte, "/>", "", $result, $champ, $pos_apres);
90
		if (!$champ->texte or count($champ->param) > 1) {
91
			if (!function_exists('normaliser_inclure')) {
92
				include_spip('public/normaliser');
93
			}
94
			normaliser_inclure($champ);
95
		}
96
		$texte = substr($texte, strpos($texte, '>', $pos_apres) + 1);
97
		$texte = preg_replace(',^</INCLU[DR]E>,', '', $texte);
98
		$result[] = $champ;
99
	}
100
101
	return (($texte === "") ? $result : phraser_idiomes($texte, $ligne, $result));
102
}
103
104
// https://code.spip.net/@phraser_polyglotte
105
function phraser_polyglotte($texte, $ligne, $result) {
106
107
	if (preg_match_all(BALISE_POLYGLOTTE, $texte, $m, PREG_SET_ORDER)) {
108
		foreach ($m as $match) {
109
			$p = strpos($texte, $match[0]);
110
			$debut = substr($texte, 0, $p);
111
			if ($p) {
112
				$champ = new Texte;
113
				$champ->texte = $debut;
114
				$champ->ligne = $ligne;
115
				$result[] = $champ;
116
				$ligne += substr_count($champ->texte, "\n");
117
			}
118
119
			$champ = new Polyglotte;
120
			$champ->ligne = $ligne;
121
			$ligne += substr_count($match[0], "\n");
122
			$lang = '';
123
			$bloc = $match[1];
124
			$texte = substr($texte, $p + strlen($match[0]));
125
			while (preg_match("/^[[:space:]]*([^[{]*)[[:space:]]*[[{]([a-z_]+)[]}](.*)$/si", $bloc, $regs)) {
126
				$trad = $regs[1];
127
				if ($trad or $lang) {
128
					$champ->traductions[$lang] = $trad;
129
				}
130
				$lang = $regs[2];
131
				$bloc = $regs[3];
132
			}
133
			$champ->traductions[$lang] = $bloc;
134
			$result[] = $champ;
135
		}
136
	}
137
	if ($texte !== "") {
138
		$champ = new Texte;
139
		$champ->texte = $texte;
140
		$champ->ligne = $ligne;
141
		$result[] = $champ;
142
	}
143
144
	return $result;
145
}
146
147
148
/**
149
 * Repérer les balises de traduction (idiomes)
150
 *
151
 * Phrase les idiomes tel que
152
 * - `<:chaine:>`
153
 * - `<:module:chaine:>`
154
 * - `<:module:chaine{arg1=texte1,arg2=#BALISE}|filtre1{texte2,#BALISE}|filtre2:>`
155
 *
156
 * @note
157
 *    `chaine` peut etre vide si `=texte1` est present et `arg1` est vide
158
 *    sinon ce n'est pas un idiome
159
 *
160
 * @param string $texte
161
 * @param int $ligne
162
 * @param array $result
163
 * @return array
164
 **/
165
function phraser_idiomes($texte, $ligne, $result) {
166
	while (preg_match(BALISE_IDIOMES, $texte, $match)) {
167
		$match = array_pad($match, 8, null);
168
		$p = strpos($texte, $match[0]);
169
		$ko = (!$match[3] && ($match[5][0] !== '='));
170
		$debut = substr($texte, 0, $p + ($ko ? strlen($match[0]) : 0));
171
		if ($debut) {
172
			$result = phraser_champs($debut, $ligne, $result);
173
		}
174
		$texte = substr($texte, $p + strlen($match[0]));
175
		$ligne += substr_count($debut, "\n");
176
		if ($ko) {
177
			continue;
178
		} // faux idiome
179
		$champ = new Idiome;
180
		$champ->ligne = $ligne;
181
		$ligne += substr_count($match[0], "\n");
182
		// Stocker les arguments de la balise de traduction
183
		$args = array();
184
		$largs = $match[5];
185
		while (preg_match(BALISE_IDIOMES_ARGS, $largs, $r)) {
186
			$args[$r[1]] = phraser_champs($r[2], 0, array());
187
			$largs = substr($largs, strlen($r[0]));
188
		}
189
		$champ->arg = $args;
190
		$champ->nom_champ = strtolower($match[3]);
191
		$champ->module = $match[2];
192
		// pas d'imbrication pour les filtres sur langue
193
		$pos_apres = 0;
194
		phraser_args($match[7], ":", '', array(), $champ, $pos_apres);
195
		$champ->apres = substr($match[7], $pos_apres);
196
		$result[] = $champ;
197
	}
198
	if ($texte !== "") {
199
		$result = phraser_champs($texte, $ligne, $result);
200
	}
201
202
	return $result;
203
}
204
205
/**
206
 * Repère et phrase les balises SPIP tel que `#NOM` dans un texte
207
 *
208
 * Phrase également ses arguments si la balise en a (`#NOM{arg, ...}`)
209
 *
210
 * @uses phraser_polyglotte()
211
 * @uses phraser_args()
212
 * @uses phraser_vieux()
213
 *
214
 * @param string $texte
215
 * @param int $ligne
216
 * @param array $result
217
 * @return array
218
 **/
219
function phraser_champs($texte, $ligne, $result) {
220
	while (preg_match("/" . NOM_DE_CHAMP . "/S", $texte, $match)) {
221
		$p = strpos($texte, $match[0]);
222
		// texte après la balise
223
		$suite = substr($texte, $p + strlen($match[0]));
224
225
		$debut = substr($texte, 0, $p);
226
		if ($p) {
227
			$result = phraser_polyglotte($debut, $ligne, $result);
228
		}
229
		$ligne += substr_count($debut, "\n");
230
		$champ = new Champ;
231
		$champ->ligne = $ligne;
232
		$ligne += substr_count($match[0], "\n");
233
		$champ->nom_boucle = $match[2];
234
		$champ->nom_champ = $match[3];
235
		$champ->etoile = $match[5];
236
237
		if ($suite and $suite[0] == '{') {
238
			phraser_arg($suite, '', array(), $champ);
239
			// ce ltrim est une ereur de conception
240
			// mais on le conserve par souci de compatibilite
241
			$texte = ltrim($suite);
242
			// Il faudrait le normaliser dans l'arbre de syntaxe abstraite
243
			// pour faire sauter ce cas particulier a la decompilation.
244
			/* Ce qui suit est malheureusement incomplet pour cela:
245
			if ($n = (strlen($suite) - strlen($texte))) {
246
				$champ->apres = array(new Texte);
247
				$champ->apres[0]->texte = substr($suite,0,$n);
248
			}
249
			*/
250
		} else {
251
			$texte = $suite;
252
		}
253
		phraser_vieux($champ);
254
		$result[] = $champ;
255
	}
256
	if ($texte !== "") {
257
		$result = phraser_polyglotte($texte, $ligne, $result);
258
	}
259
260
	return $result;
261
}
262
263
// Gestion des imbrications:
264
// on cherche les [..] les plus internes et on les remplace par une chaine
265
// %###N@ ou N indexe un tableau comportant le resultat de leur analyse
266
// on recommence tant qu'il y a des [...] en substituant a l'appel suivant
267
268
// https://code.spip.net/@phraser_champs_etendus
269
function phraser_champs_etendus($texte, $ligne, $result) {
270
	if ($texte === "") {
271
		return $result;
272
	}
273
	$sep = '##';
274
	while (strpos($texte, $sep) !== false) {
275
		$sep .= '#';
276
	}
277
278
	return array_merge($result, phraser_champs_interieurs($texte, $ligne, $sep, array()));
279
}
280
281
/**
282
 * Analyse les filtres d'un champ etendu et affecte le resultat
283
 * renvoie la liste des lexemes d'origine augmentee
284
 * de ceux trouves dans les arguments des filtres (rare)
285
 * sert aussi aux arguments des includes et aux criteres de boucles
286
 * Tres chevelu
287
 *
288
 * https://code.spip.net/@phraser_args
289
 *
290
 * @param string $texte
291
 * @param string $fin
292
 * @param string $sep
293
 * @param $result
294
 * @param $pointeur_champ
295
 * @param int $pos_debut
296
 * @return array
297
 */
298
function phraser_args($texte, $fin, $sep, $result, &$pointeur_champ, &$pos_debut) {
299
	$length = strlen($texte);
300
	while ($pos_debut < $length and trim($texte[$pos_debut]) === '') {
301
		$pos_debut++;
302
	}
303
	while (($pos_debut < $length) && strpos($fin, $texte[$pos_debut]) === false) {
304
		// phraser_arg modifie directement le $texte, on fait donc avec ici en passant par une sous chaine
305
		$st = substr($texte, $pos_debut);
306
		$result = phraser_arg($st, $sep, $result, $pointeur_champ);
307
		$pos_debut = $length - strlen($st);
308
		while ($pos_debut < $length and trim($texte[$pos_debut]) === '') {
309
			$pos_debut++;
310
		}
311
	}
312
313
	return $result;
314
}
315
316
// https://code.spip.net/@phraser_arg
317
function phraser_arg(&$texte, $sep, $result, &$pointeur_champ) {
318
	preg_match(",^(\|?[^}{)|]*)(.*)$,ms", $texte, $match);
319
	$suite = ltrim($match[2]);
320
	$fonc = trim($match[1]);
321
	if ($fonc && $fonc[0] == "|") {
322
		$fonc = ltrim(substr($fonc, 1));
323
	}
324
	$res = array($fonc);
325
	$err_f = '';
326
	// cas du filtre sans argument ou du critere /
327
	if (($suite && ($suite[0] != '{')) || ($fonc && $fonc[0] == '/')) {
328
		// si pas d'argument, alors il faut une fonction ou un double |
329
		if (!$match[1]) {
330
			$err_f = array('zbug_erreur_filtre', array('filtre' => $texte));
331
			erreur_squelette($err_f, $pointeur_champ);
332
			$texte = '';
333
		} else {
334
			$texte = $suite;
335
		}
336 View Code Duplication
		if ($err_f) {
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...
337
			$pointeur_champ->param = false;
338
		} elseif ($fonc !== '') {
339
			$pointeur_champ->param[] = $res;
340
		}
341
		// pour les balises avec faux filtres qui boudent ce dur larbeur
342
		$pointeur_champ->fonctions[] = array($fonc, '');
343
344
		return $result;
345
	}
346
	$args = ltrim(substr($suite, 1)); // virer le '(' initial
347
	$collecte = array();
348
	while ($args && $args[0] != '}') {
349
		if ($args[0] == '"') {
350
			preg_match('/^(")([^"]*)(")(.*)$/ms', $args, $regs);
351
		} elseif ($args[0] == "'") {
352
			preg_match("/^(')([^']*)(')(.*)$/ms", $args, $regs);
353
		} else {
354
			preg_match("/^([[:space:]]*)([^,([{}]*([(\[{][^])}]*[])}])?[^,}]*)([,}].*)$/ms", $args, $regs);
355
			if (!strlen($regs[2])) {
356
				$err_f = array('zbug_erreur_filtre', array('filtre' => $args));
357
				erreur_squelette($err_f, $pointeur_champ);
358
				$champ = new Texte;
359
				$champ->apres = $champ->avant = $args = "";
360
				break;
361
			}
362
		}
363
		$arg = $regs[2];
364
		if (trim($regs[1])) {
365
			$champ = new Texte;
366
			$champ->texte = $arg;
367
			$champ->apres = $champ->avant = $regs[1];
368
			$result[] = $champ;
369
			$collecte[] = $champ;
370
			$args = ltrim($regs[count($regs) - 1]);
371
		} else {
372
			if (!preg_match("/" . NOM_DE_CHAMP . "([{|])/", $arg, $r)) {
373
				// 0 est un aveu d'impuissance. A completer
374
				$arg = phraser_champs_exterieurs($arg, 0, $sep, $result);
375
376
				$args = ltrim($regs[count($regs) - 1]);
377
				$collecte = array_merge($collecte, $arg);
378
				$result = array_merge($result, $arg);
379
			} else {
380
				$n = strpos($args, $r[0]);
381
				$pred = substr($args, 0, $n);
382
				$par = ',}';
383
				if (preg_match('/^(.*)\($/', $pred, $m)) {
384
					$pred = $m[1];
385
					$par = ')';
386
				}
387
				if ($pred) {
388
					$champ = new Texte;
389
					$champ->texte = $pred;
390
					$champ->apres = $champ->avant = "";
391
					$result[] = $champ;
392
					$collecte[] = $champ;
393
				}
394
				$rec = substr($args, $n + strlen($r[0]) - 1);
395
				$champ = new Champ;
396
				$champ->nom_boucle = $r[2];
397
				$champ->nom_champ = $r[3];
398
				$champ->etoile = $r[5];
399
				$next = $r[6];
400
				while ($next == '{') {
401
					phraser_arg($rec, $sep, array(), $champ);
402
					$args = ltrim($rec);
403
					$next = isset($args[0]) ? $args[0] : '';
404
				}
405
				while ($next == '|') {
406
					$pos_apres = 0;
407
					phraser_args($rec, $par, $sep, array(), $champ, $pos_apres);
408
					$args = substr($rec, $pos_apres);
409
					$next = isset($args[0]) ? $args[0] : '';
410
				}
411
				// Si erreur de syntaxe dans un sous-argument, propager.
412
				if ($champ->param === false) {
413
					$err_f = true;
414
				} else {
415
					phraser_vieux($champ);
416
				}
417
				if ($par == ')') {
418
					$args = substr($args, 1);
419
				}
420
				$collecte[] = $champ;
421
				$result[] = $champ;
422
			}
423
		}
424
		if (isset($args[0]) and $args[0] == ',') {
425
			$args = ltrim(substr($args, 1));
426
			if ($collecte) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $collecte 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...
427
				$res[] = $collecte;
428
				$collecte = array();
429
			}
430
		}
431
	}
432
	if ($collecte) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $collecte 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...
433
		$res[] = $collecte;
434
		$collecte = array();
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...
435
	}
436
	$texte = substr($args, 1);
437
	$source = substr($suite, 0, strlen($suite) - strlen($texte));
438
	// propager les erreurs, et ignorer les param vides
439
	if ($pointeur_champ->param !== false) {
440 View Code Duplication
		if ($err_f) {
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...
441
			$pointeur_champ->param = false;
442
		} elseif ($fonc !== '' || count($res) > 1) {
443
			$pointeur_champ->param[] = $res;
444
		}
445
	}
446
	// pour les balises avec faux filtres qui boudent ce dur larbeur
447
	$pointeur_champ->fonctions[] = array($fonc, $source);
448
449
	return $result;
450
}
451
452
453
// https://code.spip.net/@phraser_champs_exterieurs
454
function phraser_champs_exterieurs($texte, $ligne, $sep, $nested) {
455
	$res = array();
456
	while (($p = strpos($texte, "%$sep")) !== false) {
457
		if (!preg_match(',^%' . preg_quote($sep) . '([0-9]+)@,', substr($texte, $p), $m)) {
458
			break;
459
		}
460
		$debut = substr($texte, 0, $p);
461
		$texte = substr($texte, $p + strlen($m[0]));
462
		if ($p) {
463
			$res = phraser_inclure($debut, $ligne, $res);
464
		}
465
		$ligne += substr_count($debut, "\n");
466
		$res[] = $nested[$m[1]];
467
	}
468
469
	return (($texte === '') ? $res : phraser_inclure($texte, $ligne, $res));
470
}
471
472
// https://code.spip.net/@phraser_champs_interieurs
473
function phraser_champs_interieurs($texte, $ligne, $sep, $result) {
474
	$i = 0; // en fait count($result)
475
	$x = "";
476
477
	while (true) {
478
		$j = $i;
479
		$n = $ligne;
480
		while (preg_match(CHAMP_ETENDU, $texte, $match)) {
481
			$p = strpos($texte, $match[0]);
482
			$debut = substr($texte, 0, $p);
483
			if ($p) {
484
				$result[$i] = $debut;
485
				$i++;
486
			}
487
			$nom = $match[4];
488
			$champ = new Champ;
489
			// ca ne marche pas encore en cas de champ imbrique
490
			$champ->ligne = $x ? 0 : ($n + substr_count($debut, "\n"));
491
			$champ->nom_boucle = $match[3];
492
			$champ->nom_champ = $nom;
493
			$champ->etoile = $match[6];
494
			// phraser_args indiquera ou commence apres
495
			$pos_apres = 0;
496
			$result = phraser_args($match[7], ")", $sep, $result, $champ, $pos_apres);
497
			phraser_vieux($champ);
498
			$champ->avant =	phraser_champs_exterieurs($match[1], $n, $sep, $result);
499
			$debut = substr($match[7], $pos_apres + 1);
500
			if (!empty($debut)) {
501
				$n += substr_count(substr($texte, 0, strpos($texte, $debut)), "\n");
502
			}
503
			$champ->apres = phraser_champs_exterieurs($debut, $n, $sep, $result);
504
505
			// reinjecter la boucle si c'en est une
506
			phraser_boucle_placeholder($champ);
507
508
			$result[$i] = $champ;
509
			$i++;
510
			$texte = substr($texte, $p + strlen($match[0]));
511
		}
512
		if ($texte !== "") {
513
			$result[$i] = $texte;
514
			$i++;
515
		}
516
		$x = '';
517
518
		while ($j < $i) {
519
			$z = $result[$j];
520
			// j'aurais besoin de connaitre le nombre de lignes...
521
			if (is_object($z)) {
522
				$x .= "%$sep$j@";
523
			} else {
524
				$x .= $z;
525
			}
526
			$j++;
527
		}
528
		if (preg_match(CHAMP_ETENDU, $x)) {
529
			$texte = $x;
530
		} else {
531
			return phraser_champs_exterieurs($x, $ligne, $sep, $result);
532
		}
533
	}
534
}
535
536
function phraser_vieux(&$champ) {
537
	$nom = $champ->nom_champ;
538
	if ($nom == 'EMBED_DOCUMENT') {
539
		if (!function_exists('phraser_vieux_emb')) {
540
			include_spip('public/normaliser');
541
		}
542
		phraser_vieux_emb($champ);
543
	} elseif ($nom == 'EXPOSER') {
544
		if (!function_exists('phraser_vieux_exposer')) {
545
			include_spip('public/normaliser');
546
		}
547
		phraser_vieux_exposer($champ);
548
	} elseif ($champ->param) {
549
		if ($nom == 'FORMULAIRE_RECHERCHE') {
550
			if (!function_exists('phraser_vieux_recherche')) {
551
				include_spip('public/normaliser');
552
			}
553
			phraser_vieux_recherche($champ);
554
		} elseif (preg_match(",^LOGO_[A-Z]+,", $nom)) {
555
			if (!function_exists('phraser_vieux_logos')) {
556
				include_spip('public/normaliser');
557
			}
558
			phraser_vieux_logos($champ);
559
		} elseif ($nom == 'MODELE') {
560
			if (!function_exists('phraser_vieux_modele')) {
561
				include_spip('public/normaliser');
562
			}
563
			phraser_vieux_modele($champ);
564
		} elseif ($nom == 'INCLURE' or $nom == 'INCLUDE') {
565
			if (!function_exists('phraser_vieux_inclu')) {
566
				include_spip('public/normaliser');
567
			}
568
			phraser_vieux_inclu($champ);
569
		}
570
	}
571
}
572
573
574
/**
575
 * Analyse les critères de boucle
576
 *
577
 * Chaque paramètre de la boucle (tel que {id_article>3}) est analysé
578
 * pour construire un critère (objet Critere) de boucle.
579
 *
580
 * Un critère a une description plus fine que le paramètre original
581
 * car on en extrait certaines informations tel que la négation et l'opérateur
582
 * utilisé s'il y a.
583
 *
584
 * La fonction en profite pour déclarer des modificateurs de boucles
585
 * en présence de certains critères (tout, plat) ou initialiser des
586
 * variables de compilation (doublons)...
587
 *
588
 * @param array $params
589
 *     Tableau de description des paramètres passés à la boucle.
590
 *     Chaque paramètre deviendra un critère
591
 * @param Boucle $result
592
 *     Description de la boucle
593
 *     Elle sera complété de la liste de ses critères
594
 * @return void
595
 **/
596
function phraser_criteres($params, &$result) {
597
598
	$err_ci = ''; // indiquera s'il y a eu une erreur
599
	$args = array();
600
	$type = $result->type_requete;
601
	$doublons = array();
602
	foreach ($params as $v) {
603
		$var = $v[1][0];
604
		$param = ($var->type != 'texte') ? "" : $var->texte;
605
		if ((count($v) > 2) && (!preg_match(",[^A-Za-z]IN[^A-Za-z],i", $param))) {
606
			// plus d'un argument et pas le critere IN:
607
			// detecter comme on peut si c'est le critere implicite LIMIT debut, fin
608
			if ($var->type != 'texte'
609
				or preg_match("/^(n|n-|(n-)?\d+)$/S", $param)
610
			) {
611
				$op = ',';
612
				$not = "";
613
				$cond = false;
614
			} else {
615
				// Le debut du premier argument est l'operateur
616
				preg_match("/^([!]?)([a-zA-Z][a-zA-Z0-9_]*)[[:space:]]*(\??)[[:space:]]*(.*)$/ms", $param, $m);
617
				$op = $m[2];
618
				$not = $m[1];
619
				$cond = $m[3];
620
				// virer le premier argument,
621
				// et mettre son reliquat eventuel
622
				// Recopier pour ne pas alterer le texte source
623
				// utile au debusqueur
624
				if ($m[4]) {
625
					// une maniere tres sale de supprimer les "' autour de {critere "xxx","yyy"}
626
					if (preg_match(',^(["\'])(.*)\1$,', $m[4])) {
627
						$c = null;
628
						eval('$c = ' . $m[4] . ';');
629
						if (isset($c)) {
630
							$m[4] = $c;
631
						}
632
					}
633
					$texte = new Texte;
634
					$texte->texte = $m[4];
635
					$v[1][0] = $texte;
636
				} else {
637
					array_shift($v[1]);
638
				}
639
			}
640
			array_shift($v); // $v[O] est vide
641
			$crit = new Critere;
642
			$crit->op = $op;
643
			$crit->not = $not;
644
			$crit->cond = $cond;
0 ignored issues
show
Documentation Bug introduced by
It seems like $cond can also be of type string. However, the property $cond is declared as type boolean. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
645
			$crit->exclus = "";
646
			$crit->param = $v;
647
			$args[] = $crit;
648
		} else {
649
			if ($var->type != 'texte') {
650
				// cas 1 seul arg ne commencant pas par du texte brut: 
651
				// erreur ou critere infixe "/"
652
				if (($v[1][1]->type != 'texte') || (trim($v[1][1]->texte) != '/')) {
653
					$err_ci = array(
654
						'zbug_critere_inconnu',
655
						array('critere' => $var->nom_champ)
656
					);
657
					erreur_squelette($err_ci, $result);
658
				} else {
659
					$crit = new Critere;
660
					$crit->op = '/';
661
					$crit->not = "";
662
					$crit->exclus = "";
663
					$crit->param = array(array($v[1][0]), array($v[1][2]));
664
					$args[] = $crit;
665
				}
666
			} else {
667
				// traiter qq lexemes particuliers pour faciliter la suite
668
				// les separateurs
669
				if ($var->apres) {
670
					$result->separateur[] = $param;
671
				} elseif (($param == 'tout') or ($param == 'tous')) {
672
					$result->modificateur['tout'] = true;
673
				} elseif ($param == 'plat') {
674
					$result->modificateur['plat'] = true;
675
				}
676
677
				// Boucle hierarchie, analyser le critere id_rubrique
678
				// et les autres critères {id_x} pour forcer {tout} sur
679
				// ceux-ci pour avoir la rubrique mere...
680
				// Les autres critères de la boucle hierarchie doivent être
681
				// traités normalement.
682
				elseif (strcasecmp($type, 'hierarchie') == 0
683
					and !preg_match(",^id_rubrique\b,", $param)
684
					and preg_match(",^id_\w+\s*$,", $param)
685
				) {
686
					$result->modificateur['tout'] = true;
687
				} elseif (strcasecmp($type, 'hierarchie') == 0 and $param == "id_rubrique") {
688
					// rien a faire sur {id_rubrique} tout seul
689
				} else {
690
					// pas d'emplacement statique, faut un dynamique
691
					// mais il y a 2 cas qui ont les 2 !
692
					if (($param == 'unique') || (preg_match(',^!?doublons *,', $param))) {
693
						// cette variable sera inseree dans le code
694
						// et son nom sert d'indicateur des maintenant
695
						$result->doublons = '$doublons_index';
696
						if ($param == 'unique') {
697
							$param = 'doublons';
698
						}
699
					} elseif ($param == 'recherche') {
700
						// meme chose (a cause de #nom_de_boucle:URL_*)
701
						$result->hash = ' ';
702
					}
703
704
					if (preg_match(',^ *([0-9-]+) *(/) *(.+) *$,', $param, $m)) {
705
						$crit = phraser_critere_infixe($m[1], $m[3], $v, '/', '', '');
706
					} elseif (preg_match(',^([!]?)(' . CHAMP_SQL_PLUS_FONC .
707
						')[[:space:]]*(\??)(!?)(<=?|>=?|==?|\b(?:IN|LIKE)\b)(.*)$,is', $param, $m)) {
708
						$a2 = trim($m[8]);
709
						if ($a2 and ($a2[0] == "'" or $a2[0] == '"') and ($a2[0] == substr($a2, -1))) {
710
							$a2 = substr($a2, 1, -1);
711
						}
712
						$crit = phraser_critere_infixe($m[2], $a2, $v,
713
							(($m[2] == 'lang_select') ? $m[2] : $m[7]),
714
							$m[6], $m[5]);
715
						$crit->exclus = $m[1];
716
					} elseif (preg_match("/^([!]?)\s*(" .
717
						CHAMP_SQL_PLUS_FONC .
718
						")\s*(\??)(.*)$/is", $param, $m)) {
719
						// contient aussi les comparaisons implicites !
720
						// Comme ci-dessus: 
721
						// le premier arg contient l'operateur
722
						array_shift($v);
723
						if ($m[6]) {
724
							$v[0][0] = new Texte;
725
							$v[0][0]->texte = $m[6];
726
						} else {
727
							array_shift($v[0]);
728
							if (!$v[0]) {
729
								array_shift($v);
730
							}
731
						}
732
						$crit = new Critere;
733
						$crit->op = $m[2];
734
						$crit->param = $v;
735
						$crit->not = $m[1];
736
						$crit->cond = $m[5];
0 ignored issues
show
Documentation Bug introduced by
The property $cond was declared of type boolean, but $m[5] 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...
737
					} else {
738
						$err_ci = array(
739
							'zbug_critere_inconnu',
740
							array('critere' => $param)
741
						);
742
						erreur_squelette($err_ci, $result);
743
					}
744
745
					if ((!preg_match(',^!?doublons *,', $param)) || $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...
746
						$args[] = $crit;
0 ignored issues
show
Bug introduced by
The variable $crit does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
747
					} else {
748
						$doublons[] = $crit;
749
					}
750
				}
751
			}
752
		}
753
	}
754
755
	// les doublons non nies doivent etre le dernier critere
756
	// pour que la variable $doublon_index ait la bonne valeur
757
	// cf critere_doublon
758
	if ($doublons) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $doublons 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...
759
		$args = array_merge($args, $doublons);
760
	}
761
762
	// Si erreur, laisser la chaine dans ce champ pour le HTTP 503
763
	if (!$err_ci) {
764
		$result->criteres = $args;
0 ignored issues
show
Documentation Bug introduced by
It seems like $args of type array is incompatible with the declared type array<integer,object<Critere>> of property $criteres.

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...
765
	}
766
}
767
768
// https://code.spip.net/@phraser_critere_infixe
769
function phraser_critere_infixe($arg1, $arg2, $args, $op, $not, $cond) {
770
	$args[0] = new Texte;
771
	$args[0]->texte = $arg1;
772
	$args[0] = array($args[0]);
773
	$args[1][0] = new Texte;
774
	$args[1][0]->texte = $arg2;
775
	$crit = new Critere;
776
	$crit->op = $op;
777
	$crit->not = $not;
778
	$crit->cond = $cond;
779
	$crit->param = $args;
780
781
	return $crit;
782
}
783
784
/**
785
 * Compter le nombre de lignes dans une partie texte
786
 * @param $texte
787
 * @param int $debut
788
 * @param null $fin
789
 * @return int
790
 */
791
function public_compte_ligne($texte, $debut = 0, $fin = null) {
792
	if (is_null($fin)) {
793
		return substr_count($texte, "\n", $debut);
794
	}
795
	else {
796
		return substr_count($texte, "\n", $debut, $fin - $debut);
797
	}
798
}
799
800
801
/**
802
 * Trouver la boucle qui commence en premier dans un texte
803
 * On repere les boucles via <BOUCLE_xxx(
804
 * et ensuite on regarde son vrai debut soit <B_xxx> soit <BB_xxx>
805
 *
806
 * @param $texte
807
 * @param $id_parent
808
 * @param $descr
809
 * @param int $pos_debut_texte
810
 * @return array|null
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use null|array<string,string|integer|false>.

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...
811
 */
812
function public_trouver_premiere_boucle($texte, $id_parent, $descr, $pos_debut_texte = 0) {
813
	$premiere_boucle = null;
814
	$pos_derniere_boucle_anonyme = $pos_debut_texte;
815
816
	$current_pos = $pos_debut_texte;
817
	while (($pos_boucle = strpos($texte, BALISE_BOUCLE, $current_pos)) !== false) {
818
		$current_pos = $pos_boucle + 1;
819
		$pos_parent = strpos($texte,'(', $pos_boucle);
820
821
		$id_boucle = '';
822
		if ($pos_parent !== false) {
823
			$id_boucle = trim(substr($texte,$pos_boucle + strlen(BALISE_BOUCLE), $pos_parent - $pos_boucle - strlen(BALISE_BOUCLE)));
824
		}
825
		if ($pos_parent === false
826
		  or (strlen($id_boucle) and !(is_numeric($id_boucle) or strpos($id_boucle, '_') === 0))) {
827
828
			$result = new Boucle;
829
			$result->id_parent = $id_parent;
830
			$result->descr = $descr;
831
832
			// un id_boucle pour l'affichage de l'erreur
833 View Code Duplication
			if (!strlen($id_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...
834
				$id_boucle = substr($texte,$pos_boucle + strlen(BALISE_BOUCLE), 15);
835
			}
836
			$result->id_boucle = $id_boucle;
837
			$err_b = array('zbug_erreur_boucle_syntaxe', array('id' => $id_boucle));
838
			erreur_squelette($err_b, $result);
839
840
			continue;
841
		}
842
		else {
843
			$boucle = [
844
				'id_boucle' => $id_boucle,
845
				'id_boucle_err' => $id_boucle,
846
				'debut_boucle' => $pos_boucle,
847
				'pos_boucle' => $pos_boucle,
848
				'pos_parent' => $pos_parent,
849
				'pos_precond' => false,
850
				'pos_precond_inside' => false,
851
				'pos_preaff' => false,
852
				'pos_preaff_inside' => false,
853
			];
854
855
			// un id_boucle pour l'affichage de l'erreur sur les boucle anonymes
856 View Code Duplication
			if (!strlen($id_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...
857
				$boucle['id_boucle_err'] = substr($texte,$pos_boucle + strlen(BALISE_BOUCLE), 15);
858
			}
859
860
			// trouver sa position de depart reelle : au <Bxx> ou au <BBxx>
861
			$precond_boucle = BALISE_PRECOND_BOUCLE . $id_boucle . '>';
862
			$pos_precond = strpos($texte, $precond_boucle, $id_boucle ? $pos_debut_texte : $pos_derniere_boucle_anonyme);
863
			if ($pos_precond !== false
864
				and $pos_precond < $boucle['debut_boucle']) {
865
				$boucle['debut_boucle'] = $pos_precond;
866
				$boucle['pos_precond'] = $pos_precond;
867
				$boucle['pos_precond_inside'] = $pos_precond + strlen($precond_boucle);
868
			}
869
870
			$preaff_boucle = BALISE_PREAFF_BOUCLE . $id_boucle . '>';
871
			$pos_preaff = strpos($texte, $preaff_boucle, $id_boucle ? $pos_debut_texte : $pos_derniere_boucle_anonyme);
872
			if ($pos_preaff !== false
873
				and $pos_preaff < $boucle['debut_boucle']) {
874
				$boucle['debut_boucle'] = $pos_preaff;
875
				$boucle['pos_preaff'] = $pos_preaff;
876
				$boucle['pos_preaff_inside'] = $pos_preaff + strlen($preaff_boucle);
877
			}
878
			if (!strlen($id_boucle)) {
879
				$pos_derniere_boucle_anonyme = $pos_boucle;
880
			}
881
882
			if (is_null($premiere_boucle) or $premiere_boucle['debut_boucle'] > $boucle['debut_boucle']) {
883
				$premiere_boucle = $boucle;
884
			}
885
		}
886
	}
887
888
	return $premiere_boucle;
889
}
890
891
/**
892
 * Trouver la fin de la  boucle (balises </B <//B </BB)
893
 * en faisant attention aux boucles anonymes qui ne peuvent etre imbriquees
894
 *
895
 * @param $texte
896
 * @param $id_parent
897
 * @param $boucle
898
 * @param $pos_debut_texte
899
 * @param $result
900
 * @return mixed
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array<string,false|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...
901
 */
902
function public_trouver_fin_boucle($texte, $id_parent, $boucle, $pos_debut_texte, $result) {
0 ignored issues
show
Unused Code introduced by
The parameter $id_parent 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 $result 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...
903
	$id_boucle = $boucle['id_boucle'];
904
	$pos_courante = $pos_debut_texte;
905
906
	$boucle['pos_postcond'] = false;
907
	$boucle['pos_postcond_inside'] = false;
908
	$boucle['pos_altern'] = false;
909
	$boucle['pos_altern_inside'] = false;
910
	$boucle['pos_postaff'] = false;
911
	$boucle['pos_postaff_inside'] = false;
912
913
	$pos_anonyme_next = null;
914
	// si c'est une boucle anonyme, chercher la position de la prochaine boucle anonyme
915
	if (!strlen($id_boucle)) {
916
		$pos_anonyme_next = strpos($texte, BALISE_BOUCLE . '(', $pos_courante);
917
	}
918
919
	//
920
	// 1. Recuperer la partie conditionnelle apres
921
	//
922
	$apres_boucle = BALISE_POSTCOND_BOUCLE . $id_boucle . ">";
923
	$pos_apres = strpos($texte, $apres_boucle, $pos_courante);
924
	if ($pos_apres !== false
925
		and (!$pos_anonyme_next or $pos_apres < $pos_anonyme_next)) {
926
		$boucle['pos_postcond'] = $pos_apres;
927
		$pos_apres += strlen($apres_boucle);
928
		$boucle['pos_postcond_inside'] = $pos_apres;
929
		$pos_courante = $pos_apres ;
930
	}
931
932
	//
933
	// 2. Récuperer la partie alternative apres
934
	//
935
	$altern_boucle = BALISE_ALT_BOUCLE . $id_boucle . ">";
936
	$pos_altern = strpos($texte, $altern_boucle, $pos_courante);
937
	if ($pos_altern !== false
938
		and (!$pos_anonyme_next or $pos_altern < $pos_anonyme_next)) {
939
		$boucle['pos_altern'] = $pos_altern;
940
		$pos_altern += strlen($altern_boucle);
941
		$boucle['pos_altern_inside'] = $pos_altern;
942
		$pos_courante = $pos_altern;
943
	}
944
945
	//
946
	// 3. Recuperer la partie footer non alternative
947
	//
948
	$postaff_boucle = BALISE_POSTAFF_BOUCLE . $id_boucle . ">";
949
	$pos_postaff = strpos($texte, $postaff_boucle, $pos_courante);
950
	if ($pos_postaff !== false
951
	  and (!$pos_anonyme_next or $pos_postaff < $pos_anonyme_next)) {
952
		$boucle['pos_postaff'] = $pos_postaff;
953
		$pos_postaff += strlen($postaff_boucle);
954
		$boucle['pos_postaff_inside'] = $pos_postaff;
955
		$pos_courante = $pos_postaff ;
0 ignored issues
show
Unused Code introduced by
$pos_courante 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...
956
	}
957
958
	return $boucle;
959
}
960
961
962
/**
963
 * @param object|string $champ
964
 * @param null|string $boucle_placeholder
965
 * @param null|object $boucle
966
 */
967
function phraser_boucle_placeholder(&$champ, $boucle_placeholder=null, $boucle = null) {
968
	static $boucles_connues = array();
969
	// si c'est un appel pour memoriser une boucle, memorisons la
970
	if (is_string($champ) and !empty($boucle_placeholder) and !empty($boucle)) {
971
		$boucles_connues[$boucle_placeholder][$champ] = &$boucle;
972
	}
973
	else {
974
		if (!empty($champ->nom_champ) and !empty($boucles_connues[$champ->nom_champ])) {
975
			$placeholder = $champ->nom_champ;
976
			$id = reset($champ->param[0][1]);
977
			$id = $id->texte;
978
			if (!empty($boucles_connues[$placeholder][$id])) {
979
				$champ = $boucles_connues[$placeholder][$id];
980
			}
981
		}
982
	}
983
}
984
985
986
/**
987
 * Generer une balise placeholder qui prend la place de la boucle pour continuer le parsing des balises
988
 * @param string $id_boucle
989
 * @param $boucle
990
 * @param string $boucle_placeholder
991
 * @param int $nb_lignes
992
 * @return string
993
 */
994
function public_generer_boucle_placeholder($id_boucle, &$boucle, $boucle_placeholder, $nb_lignes) {
995
	$placeholder = "[(#{$boucle_placeholder}{" . $id_boucle . '})' . str_pad("", $nb_lignes, "\n") . "]";
996
	//memoriser la boucle a reinjecter
997
	$id_boucle = "$id_boucle";
998
	phraser_boucle_placeholder($id_boucle, $boucle_placeholder, $boucle);
999
	return $placeholder;
1000
}
1001
1002
function public_phraser_html_dist($texte, $id_parent, &$boucles, $descr, $ligne_debut_texte = 1, $boucle_placeholder = null) {
1003
1004
	$all_res = array();
1005
	// definir un placholder pour les boucles dont on est sur d'avoir aucune occurence dans le squelette
1006
	if (is_null($boucle_placeholder)) {
1007
		do {
1008
			$boucle_placeholder = "BOUCLE_PLACEHOLDER_" . strtoupper(md5(uniqid()));
1009
		} while (strpos($texte, $boucle_placeholder) !== false);
1010
	}
1011
1012
	$ligne_debut_initial = $ligne_debut_texte;
1013
	$pos_debut_texte = 0;
1014
	while ($boucle = public_trouver_premiere_boucle($texte, $id_parent, $descr, $pos_debut_texte)) {
1015
		$err_b = ''; // indiquera s'il y a eu une erreur
1016
		$result = new Boucle;
1017
		$result->id_parent = $id_parent;
1018
		$result->descr = $descr;
1019
1020
		$pos_courante = $boucle['pos_boucle'];
1021
		$pos_parent = $boucle['pos_parent'];
1022
		$id_boucle_search = $id_boucle = $boucle['id_boucle'];
1023
1024
		$ligne_preaff = $ligne_avant = $ligne_milieu = $ligne_debut_texte + public_compte_ligne($texte, $pos_debut_texte, $pos_parent);
1025
1026
		// boucle anonyme ?
1027
		if (!strlen($id_boucle)) {
1028
			$id_boucle = '_anon_L' . $ligne_milieu . '_' . substr(md5('anonyme:' .$id_parent.':'. json_encode($boucle)), 0, 8);
1029
		}
1030
1031
		$pos_debut_boucle = $pos_courante;
1032
1033
		$pos_milieu = $pos_parent;
1034
1035
		// Regarder si on a une partie conditionnelle avant <B_xxx>
1036 View Code Duplication
		if ($boucle['pos_precond'] !== false) {
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...
1037
1038
			$pos_debut_boucle = $boucle['pos_precond'];
1039
1040
			$pos_avant = $boucle['pos_precond_inside'];
1041
			$result->avant = substr($texte, $pos_avant, $pos_courante - $pos_avant);
1042
			$ligne_avant = $ligne_debut_texte +  public_compte_ligne($texte, $pos_debut_texte, $pos_avant);
1043
		}
1044
1045
		// Regarder si on a une partie inconditionnelle avant <BB_xxx>
1046 View Code Duplication
		if ($boucle['pos_preaff'] !== false) {
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...
1047
1048
			$end_preaff = $pos_debut_boucle;
1049
1050
			$pos_preaff = $boucle['pos_preaff_inside'];
1051
			$result->preaff = substr($texte, $pos_preaff, $end_preaff - $pos_preaff);
1052
			$ligne_preaff = $ligne_debut_texte +  public_compte_ligne($texte, $pos_debut_texte, $pos_preaff);
1053
		}
1054
1055
		$result->id_boucle = $id_boucle;
0 ignored issues
show
Documentation Bug introduced by
It seems like $id_boucle can also be of type integer or false. However, the property $id_boucle is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
1056
1057
		if (!preg_match(SPEC_BOUCLE, $texte, $match, 0, $pos_milieu)
1058
		  or ($pos_match = strpos($texte, $match[0], $pos_milieu)) === false
1059
			or $pos_match > $pos_milieu
1060
		) {
1061
			$err_b = array('zbug_erreur_boucle_syntaxe', array('id' => $id_boucle));
1062
			erreur_squelette($err_b, $result);
1063
1064
			$ligne_debut_texte += public_compte_ligne($texte, $pos_debut_texte, $pos_courante + 1);
1065
			$pos_debut_texte = $pos_courante + 1;
1066
			continue;
1067
		}
1068
1069
		$result->type_requete = $match[0];
1070
		$pos_milieu += strlen($match[0]);
1071
		$pos_courante = $pos_milieu; // on s'en sert pour compter les lignes plus precisemment
0 ignored issues
show
Unused Code introduced by
$pos_courante 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...
1072
1073
		$type = $match[1];
1074
		$jointures = trim($match[2]);
1075
		$table_optionnelle = ($match[3]);
1076
		if ($jointures) {
1077
			// on affecte pas ici les jointures explicites, mais dans la compilation
1078
			// ou elles seront completees des jointures declarees
1079
			$result->jointures_explicites = $jointures;
1080
		}
1081
1082
		if ($table_optionnelle) {
1083
			$result->table_optionnelle = $type;
1084
		}
1085
1086
		// 1ere passe sur les criteres, vu comme des arguments sans fct
1087
		// Resultat mis dans result->param
1088
		$pos_fin_criteres = $pos_milieu;
1089
		phraser_args($texte, "/>", "", $all_res, $result, $pos_fin_criteres);
1090
1091
		// En 2e passe result->criteres contiendra un tableau
1092
		// pour l'instant on met le source (chaine) :
1093
		// si elle reste ici au final, c'est qu'elle contient une erreur
1094
		$pos_courante = $pos_fin_criteres; // on s'en sert pour compter les lignes plus precisemment
1095
		$result->criteres = substr($texte, $pos_milieu, $pos_fin_criteres - $pos_milieu);
1096
		$pos_milieu = $pos_fin_criteres;
1097
1098
		//
1099
		// Recuperer la fin :
1100
		//
1101
		if ($texte[$pos_milieu] === '/') {
1102
			// boucle autofermante : pas de partie conditionnelle apres
1103
			$pos_courante += 2;
1104
			$result->milieu = '';
1105
		} else {
1106
			$pos_milieu += 1;
1107
1108
			$fin_boucle = BALISE_FIN_BOUCLE . $id_boucle_search . ">";
1109
			$pos_fin = strpos($texte, $fin_boucle, $pos_milieu);
1110
			if ($pos_fin === false) {
1111
				$err_b = array(
1112
					'zbug_erreur_boucle_fermant',
1113
					array('id' => $id_boucle)
1114
				);
1115
				erreur_squelette($err_b, $result);
1116
			}
1117
1118
			$pos_courante = $pos_fin + strlen($fin_boucle);
1119
			$result->milieu = substr($texte, $pos_milieu, $pos_fin - $pos_milieu);
1120
		}
1121
1122
		$ligne_suite = $ligne_apres = $ligne_debut_texte + public_compte_ligne($texte, $pos_debut_texte, $pos_courante);
1123
		$boucle = public_trouver_fin_boucle($texte, $id_parent, $boucle, $pos_courante, $result);
1124
1125
		//
1126
		// 1. Partie conditionnelle apres ?
1127
		//
1128
		if ($boucle['pos_postcond']) {
1129
			$result->apres = substr($texte, $pos_courante, $boucle['pos_postcond'] - $pos_courante);
1130
			$ligne_suite += public_compte_ligne($texte, $pos_courante, $boucle['pos_postcond_inside']);
1131
			$pos_courante = $boucle['pos_postcond_inside'] ;
1132
		}
1133
1134
1135
		//
1136
		// 2. Partie alternative apres ?
1137
		//
1138
		$ligne_altern = $ligne_suite;
1139
		if ($boucle['pos_altern']) {
1140
			$result->altern = substr($texte, $pos_courante, $boucle['pos_altern'] - $pos_courante);
1141
			$ligne_suite += public_compte_ligne($texte, $pos_courante, $boucle['pos_altern_inside']);
1142
			$pos_courante = $boucle['pos_altern_inside'];
1143
		}
1144
1145
		//
1146
		// 3. Partie footer non alternative ?
1147
		//
1148
		$ligne_postaff = $ligne_suite;
1149
		if ($boucle['pos_postaff']) {
1150
			$result->postaff = substr($texte, $pos_courante, $boucle['pos_postaff'] - $pos_courante);
1151
			$ligne_suite += public_compte_ligne($texte, $pos_courante, $boucle['pos_postaff_inside']);
1152
			$pos_courante = $boucle['pos_postaff_inside'];
1153
		}
1154
1155
		$result->ligne = $ligne_preaff;
1156
1157 View Code Duplication
		if ($p = strpos($type, ':')) {
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...
1158
			$result->sql_serveur = substr($type, 0, $p);
1159
			$type = substr($type, $p + 1);
1160
		}
1161
		$soustype = strtolower($type);
1162
1163
		if (!isset($GLOBALS["table_des_tables"][$soustype])) {
1164
			$soustype = $type;
1165
		}
1166
1167
		$result->type_requete = $soustype;
1168
		// Lancer la 2e passe sur les criteres si la 1ere etait bonne
1169
		if (!is_array($result->param)) {
1170
			$err_b = true;
1171
		} else {
1172
			phraser_criteres($result->param, $result);
1173
			if (strncasecmp($soustype, TYPE_RECURSIF, strlen(TYPE_RECURSIF)) == 0) {
1174
				$result->type_requete = TYPE_RECURSIF;
1175
				$args = $result->param;
1176
				array_unshift($args,
1177
					substr($type, strlen(TYPE_RECURSIF)));
1178
				$result->param = $args;
1179
			}
1180
		}
1181
1182
		$descr['id_mere_contexte'] = $id_boucle;
1183
		$result->milieu = public_phraser_html_dist($result->milieu, $id_boucle, $boucles, $descr, $ligne_milieu, $boucle_placeholder);
1184
		// reserver la place dans la pile des boucles pour compiler ensuite dans le bon ordre
1185
		// ie les boucles qui apparaissent dans les partie conditionnelles doivent etre compilees apres cette boucle
1186
		// si il y a deja une boucle de ce nom, cela declenchera une erreur ensuite
1187
		if (empty($boucles[$id_boucle])){
1188
			$boucles[$id_boucle] = null;
1189
		}
1190
		$result->preaff = public_phraser_html_dist($result->preaff, $id_parent, $boucles, $descr, $ligne_preaff, $boucle_placeholder);
1191
		$result->avant = public_phraser_html_dist($result->avant, $id_parent, $boucles, $descr, $ligne_avant, $boucle_placeholder);
1192
		$result->apres = public_phraser_html_dist($result->apres, $id_parent, $boucles, $descr, $ligne_apres, $boucle_placeholder);
1193
		$result->altern = public_phraser_html_dist($result->altern, $id_parent, $boucles, $descr, $ligne_altern, $boucle_placeholder);
1194
		$result->postaff = public_phraser_html_dist($result->postaff, $id_parent, $boucles, $descr, $ligne_postaff, $boucle_placeholder);
1195
1196
		// Prevenir le generateur de code que le squelette est faux
1197
		if ($err_b) {
1198
			$result->type_requete = false;
1199
		}
1200
1201
		// Verifier qu'il n'y a pas double definition
1202
		// apres analyse des sous-parties (pas avant).
1203
		if (!empty($boucles[$id_boucle])) {
1204
			$err_b_d = array(
1205
				'zbug_erreur_boucle_double',
1206
				array('id' => $id_boucle)
1207
			);
1208
			erreur_squelette($err_b_d, $result);
1209
			// Prevenir le generateur de code que le squelette est faux
1210
			$boucles[$id_boucle]->type_requete = false;
1211
		} else {
1212
			$boucles[$id_boucle] = $result;
1213
		}
1214
1215
		// remplacer la boucle par un placeholder qui compte le meme nombre de lignes
1216
		$placeholder = public_generer_boucle_placeholder($id_boucle, $boucles[$id_boucle], $boucle_placeholder, $ligne_suite - $ligne_debut_texte);
1217
		$longueur_boucle = $pos_courante - $boucle['debut_boucle'];
1218
		$texte = substr_replace($texte, $placeholder, $boucle['debut_boucle'], $longueur_boucle);
1219
		$pos_courante = $pos_courante - $longueur_boucle + strlen($placeholder);
1220
1221
		// phraser la partie avant le debut de la boucle
1222
		#$all_res = phraser_champs_etendus(substr($texte, $pos_debut_texte, $boucle['debut_boucle'] - $pos_debut_texte), $ligne_debut_texte, $all_res);
1223
		#$all_res[] = &$boucles[$id_boucle];
1224
1225
		$ligne_debut_texte = $ligne_suite;
1226
		$pos_debut_texte = $pos_courante;
1227
	}
1228
1229
	$all_res = phraser_champs_etendus($texte, $ligne_debut_initial, $all_res);
1230
1231
	return $all_res;
1232
}