Completed
Push — master ( 03d021...09bea4 )
by cam
04:40
created

composer.php ➔ chercher_balise_generique()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
nc 4
nop 1
dl 0
loc 19
rs 9.6333
c 0
b 0
f 0
1
<?php
2
3
/***************************************************************************\
4
 *  SPIP, Système de publication pour l'internet                           *
5
 *                                                                         *
6
 *  Copyright © avec tendresse depuis 2001                                 *
7
 *  Arnaud Martin, Antoine Pitrou, Philippe Rivière, Emmanuel Saint-James  *
8
 *                                                                         *
9
 *  Ce programme est un logiciel libre distribué sous licence GNU/GPL.     *
10
 *  Pour plus de détails voir le fichier COPYING.txt ou l'aide en ligne.   *
11
\***************************************************************************/
12
13
/**
14
 * Compose un squelette : compile le squelette au besoin et vérifie
15
 * la validité du code compilé
16
 *
17
 * @package SPIP\Core\Compilateur\Composer
18
 **/
19
20
if (!defined('_ECRIRE_INC_VERSION')) {
21
	return;
22
}
23
24
include_spip('inc/texte');
25
include_spip('inc/documents');
26
include_spip('inc/distant');
27
include_spip('inc/rubriques'); # pour calcul_branche (cf critere branche)
28
include_spip('inc/acces'); // Gestion des acces pour ical
29
include_spip('inc/actions');
30
include_spip('public/fonctions');
31
include_spip('public/iterateur');
32
include_spip('public/interfaces');
33
include_spip('public/quete');
34
35
# Charge et retourne un composeur ou '' s'il est inconnu. Le compile au besoin
36
# Charge egalement un fichier homonyme de celui du squelette
37
# mais de suffixe '_fonctions.php' pouvant contenir:
38
# 1. des filtres
39
# 2. des fonctions de traduction de balise, de critere et de boucle
40
# 3. des declaration de tables SQL supplementaires
41
# Toutefois pour 2. et 3. preferer la technique de la surcharge
42
43
// https://code.spip.net/@public_composer_dist
44
function public_composer_dist($squelette, $mime_type, $gram, $source, $connect = '') {
45
46
	$nom = calculer_nom_fonction_squel($squelette, $mime_type, $connect);
47
48
	//  si deja en memoire (INCLURE  a repetition) c'est bon.
49
	if (function_exists($nom)) {
50
		return $nom;
51
	}
52
53
	if (defined('_VAR_MODE') and _VAR_MODE == 'debug') {
54
		$GLOBALS['debug_objets']['courant'] = $nom;
55
	}
56
57
	$phpfile = sous_repertoire(_DIR_SKELS, '', false, true) . $nom . '.php';
58
59
	// si squelette est deja compile et perenne, le charger
60
	if (!squelette_obsolete($phpfile, $source)) {
61
		include_once $phpfile;
62
		#if (!squelette_obsolete($phpfile, $source)
63
		#  AND lire_fichier ($phpfile, $skel_code,
64
		#  array('critique' => 'oui', 'phpcheck' => 'oui'))){
65
		## eval('?'.'>'.$skel_code);
66
		#	 spip_log($skel_code, 'comp')
67
		#}
68
	}
69
70
	if (file_exists($lib = $squelette . '_fonctions' . '.php')) {
71
		include_once $lib;
72
	}
73
74
	// tester si le eval ci-dessus a mis le squelette en memoire
75
76
	if (function_exists($nom)) {
77
		return $nom;
78
	}
79
80
	// charger le source, si possible, et compiler 
81
	$skel_code = '';
82
	if (lire_fichier($source, $skel)) {
83
		$compiler = charger_fonction('compiler', 'public');
84
		$skel_code = $compiler($skel, $nom, $gram, $source, $connect);
85
	}
86
87
	// Ne plus rien faire si le compilateur n'a pas pu operer.
88
	if (!$skel_code) {
89
		return false;
90
	}
91
92
	foreach ($skel_code as $id => $boucle) {
93
		$f = $boucle->return;
94
		try {
95
			// @todo : a remplacer quand _PHP_MIN >= 7
96
			// eval("return true; $f ;");
97
			// PHP 5.x compat
98
			if ($ok = @eval("return true; $f ;") === false) {
0 ignored issues
show
Unused Code introduced by
$ok 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...
99
				// Code syntaxiquement faux (critere etc mal programme')
100
				$msg = _T('zbug_erreur_compilation');
101
				erreur_squelette($msg, $boucle);
102
				// continuer pour trouver d'autres fautes eventuelles
103
				// mais prevenir que c'est mort
104
				$nom = '';
105
			}
106
		} catch (\ParseError $e) {
0 ignored issues
show
Bug introduced by
The class ParseError does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
107
			// Code syntaxiquement faux (critere etc mal programme')
108
			$msg = _T('zbug_erreur_compilation') . ' | Line ' . $e->getLine() . ' : ' . $e->getMessage();
109
			erreur_squelette($msg, $boucle);
110
			// continuer pour trouver d'autres fautes eventuelles
111
			// mais prevenir que c'est mort
112
			$nom = '';
113
		}
114
115
		// Contexte de compil inutile a present
116
		// (mais la derniere valeur de $boucle est utilisee ci-dessous)
117
		$skel_code[$id] = $f;
118
	}
119
120
	$code = '';
121
	if ($nom) {
122
		// Si le code est bon, concatener et mettre en cache
123
		if (function_exists($nom)) {
124
			$code = squelette_traduit($skel, $source, $phpfile, $skel_code);
125
		} else {
126
			// code semantiquement faux: bug du compilateur
127
			// $boucle est en fait ici la fct principale du squelette
128
			$msg = _T('zbug_erreur_compilation');
129
			erreur_squelette($msg, $boucle);
0 ignored issues
show
Bug introduced by
The variable $boucle seems to be defined by a foreach iteration on line 92. Are you sure the iterator is never empty, otherwise this variable is not defined?

It seems like you are relying on a variable being defined by an iteration:

foreach ($a as $b) {
}

// $b is defined here only if $a has elements, for example if $a is array()
// then $b would not be defined here. To avoid that, we recommend to set a
// default value for $b.


// Better
$b = 0; // or whatever default makes sense in your context
foreach ($a as $b) {
}

// $b is now guaranteed to be defined here.
Loading history...
130
			$nom = '';
131
		}
132
	}
133
134
	if (defined('_VAR_MODE') and _VAR_MODE == 'debug') {
135
136
		// Tracer ce qui vient d'etre compile
137
		$GLOBALS['debug_objets']['code'][$nom . 'tout'] = $code;
138
139
		// si c'est ce que demande le debusqueur, lui passer la main
140 View Code Duplication
		if ($GLOBALS['debug_objets']['sourcefile']
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...
141
			and (_request('var_mode_objet') == $nom)
142
			and (_request('var_mode_affiche') == 'code')
143
		) {
144
			erreur_squelette();
145
		}
146
	}
147
148
	return $nom ? $nom : false;
149
}
150
151
function squelette_traduit($squelette, $sourcefile, $phpfile, $boucles) {
0 ignored issues
show
Unused Code introduced by
The parameter $squelette 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...
152
153
	// Le dernier index est '' (fonction principale)
154
	$noms = substr(join(', ', array_keys($boucles)), 0, -2);
155
	if (CODE_COMMENTE) {
156
		$code = "
157
/*
158
 * Squelette : $sourcefile
159
 * Date :      " . gmdate("D, d M Y H:i:s", @filemtime($sourcefile)) . " GMT
160
 * Compile :   " . gmdate("D, d M Y H:i:s", time()) . " GMT
161
 * " . (!$boucles ? "Pas de boucle" : ("Boucles :   " . $noms)) . "
162
 */ ";
163
	}
164
165
	$code = '<' . "?php\n" . $code . join('', $boucles) . "\n?" . '>';
0 ignored issues
show
Bug introduced by
The variable $code 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...
166
	if (!defined('_VAR_NOCACHE') or !_VAR_NOCACHE) {
167
		ecrire_fichier($phpfile, $code);
168
	}
169
170
	return $code;
171
}
172
173
// Le squelette compile est-il trop vieux ?
174
// https://code.spip.net/@squelette_obsolete
175
function squelette_obsolete($skel, $squelette) {
176
	static $date_change = null;
177
	// ne verifier la date de mes_fonctions et mes_options qu'une seule fois
178
	// par hit
179
	if (is_null($date_change)) {
180
		if (@file_exists($fonc = 'mes_fonctions.php')) {
181
			$date_change = @filemtime($fonc);
182
		} # compatibilite
183
		if (defined('_FILE_OPTIONS')) {
184
			$date_change = max($date_change, @filemtime(_FILE_OPTIONS));
185
		}
186
	}
187
188
	return (
189
		(defined('_VAR_MODE') and in_array(_VAR_MODE, array('recalcul', 'preview', 'debug')))
190
		or !@file_exists($skel)
191
		or ((@file_exists($squelette) ? @filemtime($squelette) : 0)
192
			> ($date = @filemtime($skel)))
193
		or ($date_change > $date)
194
	);
195
}
196
197
// Activer l'invalideur de session
198
// https://code.spip.net/@invalideur_session
199
function invalideur_session(&$Cache, $code = null) {
200
	$Cache['session'] = spip_session();
201
202
	return $code;
203
}
204
205
206
// https://code.spip.net/@analyse_resultat_skel
207
function analyse_resultat_skel($nom, $cache, $corps, $source = '') {
208
	static $filtres = array();
209
	$headers = array();
210
211
	// Recupere les < ?php header('Xx: y'); ? > pour $page['headers']
212
	// note: on essaie d'attrapper aussi certains de ces entetes codes
213
	// "a la main" dans les squelettes, mais evidemment sans exhaustivite
214
	if (stripos($corps, 'header') !== false
215
		and preg_match_all(
216
			'/(<[?]php\s+)@?header\s*\(\s*.([^:\'"]*):?\s*([^)]*)[^)]\s*\)\s*[;]?\s*[?]>/ims',
217
			$corps, $regs, PREG_SET_ORDER)
218
	) {
219
		foreach ($regs as $r) {
220
			$corps = str_replace($r[0], '', $corps);
221
			# $j = Content-Type, et pas content-TYPE.
222
			$j = join('-', array_map('ucwords', explode('-', strtolower($r[2]))));
223
224
			if ($j == 'X-Spip-Filtre' and isset($headers[$j])) {
225
				$headers[$j] .= "|" . $r[3];
226
			} else {
227
				$headers[$j] = $r[3];
228
			}
229
		}
230
	}
231
	// S'agit-il d'un resultat constant ou contenant du code php
232
	$process_ins = (
233
		strpos($corps, '<' . '?') === false
234
		or
235
		(strpos($corps, '<' . '?xml') !== false and
236
			strpos(str_replace('<' . '?xml', '', $corps), '<' . '?') === false)
237
	)
238
		? 'html'
239
		: 'php';
240
241
	$skel = array(
242
		'squelette' => $nom,
243
		'source' => $source,
244
		'process_ins' => $process_ins,
245
		'invalideurs' => $cache,
246
		'entetes' => $headers,
247
		'duree' => isset($headers['X-Spip-Cache']) ? intval($headers['X-Spip-Cache']) : 0
248
	);
249
250
	// traiter #FILTRE{} et filtres
251
	if (!isset($filtres[$nom])) {
252
		$filtres[$nom] = pipeline('declarer_filtres_squelettes', array('args' => $skel, 'data' => array()));
253
	}
254
	$filtres_headers = array();
255
	if (isset($headers['X-Spip-Filtre']) and strlen($headers['X-Spip-Filtre'])) {
256
		$filtres_headers = array_filter(explode('|', $headers['X-Spip-Filtre']));
257
		unset($headers['X-Spip-Filtre']);
258
	}
259
	if (count($filtres[$nom]) or count($filtres_headers)) {
260
		include_spip('public/sandbox');
261
		$corps = sandbox_filtrer_squelette($skel, $corps, $filtres_headers, $filtres[$nom]);
262
263
		if ($process_ins == 'html') {
264
			$skel['process_ins'] = (
265
				strpos($corps, '<' . '?') === false
266
				or
267
				(strpos($corps, '<' . '?xml') !== false and
268
					strpos(str_replace('<' . '?xml', '', $corps), '<' . '?') === false)
269
			)
270
				? 'html'
271
				: 'php';
272
		}
273
	}
274
275
	$skel['entetes'] = $headers;
276
	$skel['texte'] = $corps;
277
278
	return $skel;
279
}
280
281
//
282
// Balises dynamiques
283
//
284
285
/** Code PHP pour inclure une balise dynamique à l'exécution d'une page */
286
define('CODE_INCLURE_BALISE', '<' . '?php 
287
include_once("%s");
288
if ($lang_select = "%s") $lang_select = lang_select($lang_select);
289
inserer_balise_dynamique(balise_%s_dyn(%s), array(%s));
290
if ($lang_select) lang_select();
291
?'
292
	. '>');
293
294
/**
295
 * Synthétise une balise dynamique : crée l'appel à l'inclusion
296
 * en transmettant les arguments calculés et le contexte de compilation.
297
 *
298
 * @uses argumenter_squelette() Pour calculer les arguments de l'inclusion
299
 *
300
 * @param string $nom
301
 *     Nom de la balise dynamique
302
 * @param array $args
303
 *     Liste des arguments calculés
304
 * @param string $file
305
 *     Chemin du fichier de squelette à inclure
306
 * @param array $context_compil
307
 *     Tableau d'informations sur la compilation
308
 * @return string
309
 *     Code PHP pour inclure le squelette de la balise dynamique
310
 **/
311
function synthetiser_balise_dynamique($nom, $args, $file, $context_compil) {
312
	if (strncmp($file, "/", 1) !== 0) {
313
		$file = './" . _DIR_RACINE . "' . $file;
314
	}
315
316
	$lang = $context_compil[4];
317
	if (preg_match(",\W,", $lang)) {
318
		$lang = '';
319
	}
320
321
	$args = array_map('argumenter_squelette', $args);
322
	if (!empty($context_compil['appel_php_depuis_modele'])) {
323
		$args[0] = 'arguments_balise_dyn_depuis_modele('.$args[0].')';
324
	}
325
	$args = join(', ', $args);
326
327
	$r = sprintf(CODE_INCLURE_BALISE,
328
		$file,
329
		$lang,
330
		$nom,
331
		$args,
332
		join(', ', array_map('_q', $context_compil)));
333
334
	return $r;
335
}
336
337
/**
338
 * Crée le code PHP pour transmettre des arguments (généralement pour une inclusion)
339
 *
340
 * @param array|string $v
341
 *     Arguments à transmettre :
342
 *
343
 *    - string : un simple texte à faire écrire
344
 *    - array : couples ('nom' => 'valeur') liste des arguments et leur valeur
345
 * @return string
346
 *
347
 *    - Code PHP créant le tableau des arguments à transmettre,
348
 *    - ou texte entre quote `'` (si `$v` était une chaîne)
349
 **/
350
function argumenter_squelette($v) {
351
352
	if (is_object($v)) {
353
		if (PHP_VERSION_ID < 73000 and $v instanceof \stdClass) {
354
			return "(object) " . var_export((array) $v, true);
355
		}
356
		return var_export($v, true);
357
	} elseif (!is_array($v)) {
358
		return "'" . texte_script($v) . "'";
359
	} else {
360
		$out = array();
361
		foreach ($v as $k => $val) {
362
			$out [] = argumenter_squelette($k) . '=>' . argumenter_squelette($val);
363
		}
364
365
		return 'array(' . join(", ", $out) . ')';
366
	}
367
}
368
369
370
/**
371
 * Calcule et retourne le code PHP retourné par l'exécution d'une balise
372
 * dynamique.
373
 *
374
 * Vérifier les arguments et filtres et calcule le code PHP à inclure.
375
 *
376
 * - charge le fichier PHP de la balise dynamique dans le répertoire
377
 *   `balise/`, soit du nom complet de la balise, soit d'un nom générique
378
 *    (comme 'formulaire_.php'). Dans ce dernier cas, le nom de la balise
379
 *    est ajouté en premier argument.
380
 * - appelle une éventuelle fonction de traitement des arguments `balise_NOM_stat()`
381
 * - crée le code PHP de la balise si une fonction `balise_NOM_dyn()` (ou variantes)
382
 *   est effectivement trouvée.
383
 *
384
 * @uses synthetiser_balise_dynamique()
385
 *     Pour calculer le code PHP d'inclusion produit
386
 *
387
 * @param string $nom
388
 *     Nom de la balise dynamique
389
 * @param array $args
390
 *     Liste des arguments calculés de la balise
391
 * @param array $context_compil
392
 *     Tableau d'informations sur la compilation
393
 * @return string
394
 *     Code PHP d'exécutant l'inclusion du squelette (ou texte) de la balise dynamique
395
 **/
396
function executer_balise_dynamique($nom, $args, $context_compil) {
397
	/** @var string Nom de la balise à charger (balise demandée ou balise générique) */
398
	$nom_balise = $nom;
399
	/** @var string Nom de la balise générique (si utilisée) */
400
	$nom_balise_generique = "";
401
402
	$appel_php_depuis_modele = false;
403
	if (is_array($context_compil)
404
	  and !is_numeric($context_compil[3])
405
	  and empty($context_compil[0])
406
		and empty($context_compil[1])
407
		and empty($context_compil[2])
408
		and empty($context_compil[3])) {
409
		$appel_php_depuis_modele = true;
410
	}
411
412
	if (!$fonction_balise = charger_fonction($nom_balise, 'balise', true)) {
413
		// Calculer un nom générique (ie. 'formulaire_' dans 'formulaire_editer_article')
414
		if ($balise_generique = chercher_balise_generique($nom)) {
415
			// injecter en premier arg le nom de la balise 
416
			array_unshift($args, $nom);
417
			$nom_balise_generique = $balise_generique['nom_generique'];
418
			$fonction_balise = $balise_generique['fonction_generique'];
419
			$nom_balise = $nom_balise_generique;
420
		}
421
		unset($balise_generique);
422
	}
423
424 View Code Duplication
	if (!$fonction_balise) {
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...
425
		$msg = array('zbug_balise_inexistante', array('from' => 'CVT', 'balise' => $nom));
426
		erreur_squelette($msg, $context_compil);
427
428
		return '';
429
	}
430
431
	// retrouver le fichier qui a déclaré la fonction
432
	// même si la fonction dynamique est déclarée dans un fichier de fonctions.
433
	// Attention sous windows, getFileName() retourne un antislash. 
434
	$reflector = new ReflectionFunction($fonction_balise);
435
	$file = str_replace('\\', '/', $reflector->getFileName());
436 View Code Duplication
	if (strncmp($file, str_replace('\\', '/', _ROOT_RACINE), strlen(_ROOT_RACINE)) === 0) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
437
		$file = substr($file, strlen(_ROOT_RACINE));
438
	}
439
440
	// Y a-t-il une fonction de traitement des arguments ?
441
	$f = 'balise_' . $nom_balise . '_stat';
442
443
	$r = !function_exists($f) ? $args : $f($args, $context_compil);
444
445
	if (!is_array($r)) {
446
		return $r;
447
	}
448
449
	// verifier que la fonction dyn est la, 
450
	// sinon se replier sur la generique si elle existe
451
	if (!function_exists('balise_' . $nom_balise . '_dyn')) {
452
		if (
453
			$balise_generique = chercher_balise_generique($nom)
454
			and $nom_balise_generique = $balise_generique['nom_generique']
455
			and $file = include_spip("balise/" . strtolower($nom_balise_generique))
456
			and function_exists('balise_' . $nom_balise_generique . '_dyn')
457
		) {
458
			// et lui injecter en premier arg le nom de la balise 
459
			array_unshift($r, $nom);
460
			$nom_balise = $nom_balise_generique;
461
			if (!_DIR_RESTREINT) {
462
				$file = _DIR_RESTREINT_ABS . $file;
463
			}
464 View Code Duplication
		} else {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
465
			$msg = array('zbug_balise_inexistante', array('from' => 'CVT', 'balise' => $nom));
466
			erreur_squelette($msg, $context_compil);
467
468
			return '';
469
		}
470
	}
471
472
	if ($appel_php_depuis_modele) {
473
		$context_compil['appel_php_depuis_modele'] = true;
474
	}
475
	return synthetiser_balise_dynamique($nom_balise, $r, $file, $context_compil);
0 ignored issues
show
Bug introduced by
It seems like $file defined by include_spip('balise/' ....$nom_balise_generique)) on line 455 can also be of type boolean; however, synthetiser_balise_dynamique() does only seem to accept string, 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...
476
477
}
478
479
/**
480
 * Pour une balise "NOM" donné, cherche s'il existe une balise générique qui peut la traiter
481
 *
482
 * Le nom de balise doit contenir au moins un souligné "A_B", auquel cas on cherche une balise générique "A_"
483
 * 
484
 * S'il y a plus d'un souligné, tel que "A_B_C_D" on cherche différentes balises génériques en commençant par la plus longue possible,
485
 * tel que "A_B_C_", sinon "A_B_" sinon "A_"
486
 * 
487
 * @param string $nom
488
 * @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>.

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...
489
 */
490
function chercher_balise_generique($nom) {
491
	if (false === strpos($nom, "_")) {
492
		return null;
493
	}
494
	$nom_generique = $nom;
495
	while (false !== ($p = strrpos($nom_generique, "_"))) {
496
		$nom_generique = substr($nom_generique, 0, $p + 1);
497
		$fonction_generique = charger_fonction($nom_generique, 'balise', true);
498
		if ($fonction_generique) {
499
			return [
500
				'nom' => $nom,
501
				'nom_generique' => $nom_generique,
502
				'fonction_generique' => $fonction_generique,
503
			];
504
		}
505
		$nom_generique = substr($nom_generique, 0, -1);
506
	}
507
	return null;
508
}
509
510
511
/**
512
 * Selectionner la langue de l'objet dans la boucle
513
 *
514
 * Applique sur un item de boucle la langue de l'élément qui est parcourru.
515
 * Sauf dans les cas ou il ne le faut pas !
516
 *
517
 * La langue n'est pas modifiée lorsque :
518
 * - la globale 'forcer_lang' est définie à true
519
 * - l'objet ne définit pas de langue
520
 * - le titre contient une balise multi.
521
 *
522
 * @param string $lang
523
 *     Langue de l'objet
524
 * @param string $lang_select
525
 *     'oui' si critère lang_select est présent, '' sinon.
526
 * @param null|string $titre
527
 *     Titre de l'objet
528
 * @return null;
0 ignored issues
show
Documentation introduced by
The doc-type null; could not be parsed: Expected "|" or "end of type", but got ";" at position 4. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
529
 **/
530
function lang_select_public($lang, $lang_select, $titre = null) {
531
	// Cas 1. forcer_lang = true et pas de critere {lang_select}
532
	if (isset($GLOBALS['forcer_lang']) and $GLOBALS['forcer_lang']
533
		and $lang_select !== 'oui'
534
	) {
535
		$lang = $GLOBALS['spip_lang'];
536
	} // Cas 2. l'objet n'a pas de langue definie (ou definie a '')
537
	elseif (!strlen($lang)) {
538
		$lang = $GLOBALS['spip_lang'];
539
	} // Cas 3. l'objet est multilingue !
540
	elseif ($lang_select !== 'oui'
541
		and strlen($titre) > 10
542
		and strpos($titre, '<multi>') !== false
543
		and strpos(echappe_html($titre), '<multi>') !== false
544
	) {
545
		$lang = $GLOBALS['spip_lang'];
546
	}
547
548
	// faire un lang_select() eventuellement sur la langue inchangee
549
	lang_select($lang);
550
551
	return;
552
}
553
554
555
// Si un tableau &doublons[articles] est passe en parametre,
556
// il faut le nettoyer car il pourrait etre injecte en SQL
557
// https://code.spip.net/@nettoyer_env_doublons
558
function nettoyer_env_doublons($envd) {
559
	foreach ($envd as $table => $liste) {
560
		$n = '';
561
		foreach (explode(',', $liste) as $val) {
562
			if ($a = intval($val) and $val === strval($a)) {
563
				$n .= ',' . $val;
564
			}
565
		}
566
		if (strlen($n)) {
567
			$envd[$table] = $n;
568
		} else {
569
			unset($envd[$table]);
570
		}
571
	}
572
573
	return $envd;
574
}
575
576
/**
577
 * Cherche la présence d'un opérateur SELF ou SUBSELECT
578
 *
579
 * Cherche dans l'index 0 d'un tableau, la valeur SELF ou SUBSELECT
580
 * indiquant pour une expression WHERE de boucle que nous sommes
581
 * face à une sous-requête.
582
 *
583
 * Cherche de manière récursive également dans les autres valeurs si celles-ci
584
 * sont des tableaux
585
 *
586
 * @param string|array $w
587
 *     Description d'une condition WHERE de boucle (ou une partie de cette description)
588
 * @return string|bool
589
 *     Opérateur trouvé (SELF ou SUBSELECT) sinon false.
590
 **/
591
function match_self($w) {
592
	if (is_string($w)) {
593
		return false;
594
	}
595
	if (is_array($w)) {
596
		if (in_array(reset($w), array("SELF", "SUBSELECT"))) {
597
			return $w;
598
		}
599
		foreach (array_filter($w, 'is_array') as $sw) {
600
			if ($m = match_self($sw)) {
601
				return $m;
602
			}
603
		}
604
	}
605
606
	return false;
607
}
608
609
/**
610
 * Remplace une condition décrivant une sous requête par son code
611
 *
612
 * @param array|string $w
613
 *     Description d'une condition WHERE de boucle (ou une partie de cette description)
614
 *     qui possède une description de sous-requête
615
 * @param string $sousrequete
616
 *     Code PHP de la sous requête (qui doit remplacer la description)
617
 * @return array|string
618
 *     Tableau de description du WHERE dont la description de sous-requête
619
 *     est remplacée par son code.
620
 **/
621
function remplace_sous_requete($w, $sousrequete) {
622
	if (is_array($w)) {
623
		if (in_array(reset($w), array("SELF", "SUBSELECT"))) {
624
			return $sousrequete;
625
		}
626
		foreach ($w as $k => $sw) {
627
			$w[$k] = remplace_sous_requete($sw, $sousrequete);
628
		}
629
	}
630
631
	return $w;
632
}
633
634
/**
635
 * Sépare les conditions de boucles simples de celles possédant des sous-requêtes.
636
 *
637
 * @param array $where
638
 *     Description d'une condition WHERE de boucle
639
 * @return array
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array[].

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

Loading history...
640
 *     Liste de 2 tableaux :
641
 *     - Conditions simples (ne possédant pas de sous requêtes)
642
 *     - Conditions avec des sous requêtes
643
 **/
644
function trouver_sous_requetes($where) {
645
	$where_simples = array();
646
	$where_sous = array();
647
	foreach ($where as $k => $w) {
648
		if (match_self($w)) {
649
			$where_sous[$k] = $w;
650
		} else {
651
			$where_simples[$k] = $w;
652
		}
653
	}
654
655
	return array($where_simples, $where_sous);
656
}
657
658
659
/**
660
 * Calcule une requête et l’exécute
661
 *
662
 * Cette fonction est présente dans les squelettes compilés.
663
 * Elle peut permettre de générer des requêtes avec jointure.
664
 *
665
 * @param array $select
666
 * @param array $from
667
 * @param array $from_type
668
 * @param array $where
669
 * @param array $join
670
 * @param array $groupby
671
 * @param array $orderby
672
 * @param string $limit
673
 * @param array $having
674
 * @param string $table
675
 * @param string $id
676
 * @param string $serveur
677
 * @param bool $requeter
678
 * @return resource
679
 */
680
function calculer_select(
681
	$select = array(),
682
	$from = array(),
683
	$from_type = array(),
684
	$where = array(),
685
	$join = array(),
686
	$groupby = array(),
687
	$orderby = array(),
688
	$limit = '',
689
	$having = array(),
690
	$table = '',
691
	$id = '',
692
	$serveur = '',
693
	$requeter = true
694
) {
695
696
	// retirer les criteres vides:
697
	// {X ?} avec X absent de l'URL
698
	// {par #ENV{X}} avec X absent de l'URL
699
	// IN sur collection vide (ce dernier devrait pouvoir etre fait a la compil)
700
	$menage = false;
701
	foreach ($where as $k => $v) {
702 View Code Duplication
		if (is_array($v)) {
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...
703
			if ((count($v) >= 2) && ($v[0] == 'REGEXP') && ($v[2] == "'.*'")) {
704
				$op = false;
705
			} elseif ((count($v) >= 2) && ($v[0] == 'LIKE') && ($v[2] == "'%'")) {
706
				$op = false;
707
			} else {
708
				$op = $v[0] ? $v[0] : $v;
709
			}
710
		} else {
711
			$op = $v;
712
		}
713
		if ((!$op) or ($op == 1) or ($op == '0=0')) {
714
			unset($where[$k]);
715
			$menage = true;
716
		}
717
	}
718
719
	// evacuer les eventuels groupby vide issus d'un calcul dynamique
720
	$groupby = array_diff($groupby, array(''));
721
722
	// remplacer les sous requetes recursives au calcul
723
	list($where_simples, $where_sous) = trouver_sous_requetes($where);
724
	foreach ($where_sous as $k => $w) {
725
		$menage = true;
726
		// on recupere la sous requete 
727
		$sous = match_self($w);
728
		if ($sous[0] == 'SELF') {
729
			// c'est une sous requete identique a elle meme sous la forme (SELF,$select,$where)
730
			array_push($where_simples, $sous[2]);
731
			$wheresub = array(
732
				$sous[2],
733
				'0=0'
734
			); // pour accepter une string et forcer a faire le menage car on a surement simplifie select et where
735
			$jsub = $join;
736
			// trouver les jointures utiles a
737
			// reinjecter dans le where de la sous requete les conditions supplementaires des jointures qui y sont mentionnees
738
			// ie L1.objet='article'
739
			// on construit le where une fois, puis on ajoute les where complentaires si besoin, et on reconstruit le where en fonction
740
			$i = 0;
741
			do {
742
				$where[$k] = remplace_sous_requete($w, "(" . calculer_select(
743
						array($sous[1] . " AS id"),
744
						$from,
745
						$from_type,
746
						$wheresub,
747
						$jsub,
748
						array(), array(), '',
749
						$having, $table, $id, $serveur, false) . ")");
750
				if (!$i) {
751
					$i = 1;
752
					$wherestring = calculer_where_to_string($where[$k]);
753
					foreach ($join as $cle => $wj) {
754
						if (count($wj) == 4
755
							and strpos($wherestring, "{$cle}.") !== false
756
						) {
757
							$i = 0;
758
							$wheresub[] = $wj[3];
759
							unset($jsub[$cle][3]);
760
						}
761
					}
762
				}
763
			} while ($i++ < 1);
764
		}
765
		if ($sous[0] == 'SUBSELECT') {
766
			// c'est une sous requete explicite sous la forme identique a sql_select : (SUBSELECT,$select,$from,$where,$groupby,$orderby,$limit,$having)
767
			array_push($where_simples, $sous[3]); // est-ce utile dans ce cas ?
768
			$where[$k] = remplace_sous_requete($w, "(" . calculer_select(
769
					$sous[1], # select
770
					$sous[2], #from
771
					array(), #from_type
772
					$sous[3] ? (is_array($sous[3]) ? $sous[3] : array($sous[3])) : array(),
773
					#where, qui peut etre de la forme string comme dans sql_select
774
					array(), #join
775
					$sous[4] ? $sous[4] : array(), #groupby
776
					$sous[5] ? $sous[5] : array(), #orderby
777
					$sous[6], #limit
778
					$sous[7] ? $sous[7] : array(), #having
779
					$table, $id, $serveur, false
780
				) . ")");
781
		}
782
		array_pop($where_simples);
783
	}
784
785
	foreach ($having as $k => $v) {
786
		if ((!$v) or ($v == 1) or ($v == '0=0')) {
787
			unset($having[$k]);
788
		}
789
	}
790
791
	// Installer les jointures.
792
	// Retirer celles seulement utiles aux criteres finalement absents mais
793
	// parcourir de la plus recente a la moins recente pour pouvoir eliminer Ln
794
	// si elle est seulement utile a Ln+1 elle meme inutile
795
796
	$afrom = array();
797
	$equiv = array();
798
	$k = count($join);
799
	foreach (array_reverse($join, true) as $cledef => $j) {
800
		$cle = $cledef;
801
		// le format de join est :
802
		// array(table depart, cle depart [,cle arrivee[,condition optionnelle and ...]])
803
		$join[$cle] = array_values($join[$cle]); // recalculer les cles car des unset ont pu perturber
804
		if (count($join[$cle]) == 2) {
805
			$join[$cle][] = $join[$cle][1];
806
		}
807
		if (count($join[$cle]) == 3) {
808
			$join[$cle][] = '';
809
		}
810
		list($t, $c, $carr, $and) = $join[$cle];
811
		// si le nom de la jointure n'a pas ete specifiee, on prend Lx avec x sont rang dans la liste
812
		// pour compat avec ancienne convention
813
		if (is_numeric($cle)) {
814
			$cle = "L$k";
815
		}
816
		$cle_where_lie = "JOIN-$cle";
817
		if (!$menage
818
			or isset($afrom[$cle])
819
			or calculer_jointnul($cle, $select)
820
			or calculer_jointnul($cle, array_diff_key($join, array($cle => $join[$cle])))
821
			or calculer_jointnul($cle, $having)
822
			or calculer_jointnul($cle, array_diff_key($where_simples, [$cle_where_lie => '']))
823
		) {
824
			// corriger les references non explicites dans select
825
			// ou groupby
826
			foreach ($select as $i => $s) {
827
				if ($s == $c) {
828
					$select[$i] = "$cle.$c AS $c";
829
					break;
830
				}
831
			}
832
			foreach ($groupby as $i => $g) {
833
				if ($g == $c) {
834
					$groupby[$i] = "$cle.$c";
835
					break;
836
				}
837
			}
838
			// on garde une ecriture decomposee pour permettre une simplification ulterieure si besoin
839
			// sans recours a preg_match
840
			// un implode(' ',..) est fait dans reinjecte_joint un peu plus bas
841
			$afrom[$t][$cle] = array(
842
				"\n" .
843
				(isset($from_type[$cle]) ? $from_type[$cle] : "INNER") . " JOIN",
844
				$from[$cle],
845
				"AS $cle",
846
				"ON (",
847
				"$cle.$c",
848
				"=",
849
				"$t.$carr",
850
				($and ? "AND " . $and : "") .
851
				")"
852
			);
853
			if (isset($afrom[$cle])) {
854
				$afrom[$t] = $afrom[$t] + $afrom[$cle];
855
				unset($afrom[$cle]);
856
			}
857
			$equiv[] = $carr;
858
		} else {
859
			unset($join[$cledef]);
860
			if (isset($where_simples[$cle_where_lie])) {
861
				unset($where_simples[$cle_where_lie]);
862
				unset($where[$cle_where_lie]);
863
			}
864
		}
865
		unset($from[$cle]);
866
		$k--;
867
	}
868
869
	if (count($afrom)) {
870
		// Regarder si la table principale ne sert finalement a rien comme dans
871
		//<BOUCLE3(MOTS){id_article}{id_mot}> class='on'</BOUCLE3>
872
		//<BOUCLE2(MOTS){id_article} />#TOTAL_BOUCLE<//B2>
873
		//<BOUCLE5(RUBRIQUES){id_mot}{tout} />#TOTAL_BOUCLE<//B5>
874
		// ou dans
875
		//<BOUCLE8(HIERARCHIE){id_rubrique}{tout}{type='Squelette'}{inverse}{0,1}{lang_select=non} />#TOTAL_BOUCLE<//B8>
876
		// qui comporte plusieurs jointures
877
		// ou dans
878
		// <BOUCLE6(ARTICLES){id_mot=2}{statut==.*} />#TOTAL_BOUCLE<//B6>
879
		// <BOUCLE7(ARTICLES){id_mot>0}{statut?} />#TOTAL_BOUCLE<//B7>
880
		// penser a regarder aussi la clause orderby pour ne pas simplifier abusivement
881
		// <BOUCLE9(ARTICLES){recherche truc}{par titre}>#ID_ARTICLE</BOUCLE9>
882
		// penser a regarder aussi la clause groubpy pour ne pas simplifier abusivement
883
		// <BOUCLE10(EVENEMENTS){id_rubrique} />#TOTAL_BOUCLE<//B10>
884
885
		$t = key($from);
886
		$c = current($from);
0 ignored issues
show
Unused Code introduced by
$c 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...
887
		reset($from);
888
		$e = '/\b(' . "$t\\." . join("|" . $t . '\.', $equiv) . ')\b/';
889
		if (!(strpos($t, ' ') or // jointure des le depart cf boucle_doc
890
				calculer_jointnul($t, $select, $e) or
891
				calculer_jointnul($t, $join, $e) or
892
				calculer_jointnul($t, $where, $e) or
893
				calculer_jointnul($t, $orderby, $e) or
894
				calculer_jointnul($t, $groupby, $e) or
895
				calculer_jointnul($t, $having, $e))
896
			&& count($afrom[$t])
897
		) {
898
			$nfrom = reset($afrom[$t]);
899
			$nt = key($afrom[$t]);
900
			unset($from[$t]);
901
			$from[$nt] = $nfrom[1];
902
			unset($afrom[$t][$nt]);
903
			$afrom[$nt] = $afrom[$t];
904
			unset($afrom[$t]);
905
			$e = '/\b' . preg_quote($nfrom[6]) . '\b/';
906
			$t = $nfrom[4];
907
			$alias = "";
908
			// verifier que les deux cles sont homonymes, sinon installer un alias dans le select
909
			$oldcle = explode('.', $nfrom[6]);
910
			$oldcle = end($oldcle);
911
			$newcle = explode('.', $nfrom[4]);
912
			$newcle = end($newcle);
913
			if ($newcle != $oldcle) {
914
				// si l'ancienne cle etait deja dans le select avec un AS
915
				// reprendre simplement ce AS
916
				$as = '/\b' . preg_quote($nfrom[6]) . '\s+(AS\s+\w+)\b/';
917
				if (preg_match($as, implode(',', $select), $m)) {
918
					$alias = "";
919
				} else {
920
					$alias = ", " . $nfrom[4] . " AS $oldcle";
921
				}
922
			}
923
			$select = remplacer_jointnul($t . $alias, $select, $e);
924
			$join = remplacer_jointnul($t, $join, $e);
0 ignored issues
show
Unused Code introduced by
$join 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...
925
			$where = remplacer_jointnul($t, $where, $e);
926
			$having = remplacer_jointnul($t, $having, $e);
927
			$groupby = remplacer_jointnul($t, $groupby, $e);
928
			$orderby = remplacer_jointnul($t, $orderby, $e);
929
		}
930
		$from = reinjecte_joint($afrom, $from);
931
	}
932
	$GLOBALS['debug']['aucasou'] = array($table, $id, $serveur, $requeter);
933
	$r = sql_select($select, $from, $where,
934
		$groupby, array_filter($orderby), $limit, $having, $serveur, $requeter);
935
	unset($GLOBALS['debug']['aucasou']);
936
937
	return $r;
938
}
939
940
/**
941
 * Analogue a calculer_mysql_expression et autre (a unifier ?)
942
 *
943
 * @param string|array $v
944
 * @param string $join
945
 * @return string
946
 */
947 View Code Duplication
function calculer_where_to_string($v, $join = 'AND') {
0 ignored issues
show
Duplication introduced by
This function seems to be duplicated in your project.

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

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

Loading history...
948
	if (empty($v)) {
949
		return '';
950
	}
951
952
	if (!is_array($v)) {
953
		return $v;
954
	} else {
955
		$exp = "";
956
		if (strtoupper($join) === 'AND') {
957
			return $exp . join(" $join ", array_map('calculer_where_to_string', $v));
958
		} else {
959
			return $exp . join($join, $v);
960
		}
961
	}
962
}
963
964
965
//condition suffisante (mais non necessaire) pour qu'une table soit utile
966
967
// https://code.spip.net/@calculer_jointnul
968
function calculer_jointnul($cle, $exp, $equiv = '') {
969
	if (!is_array($exp)) {
970
		if ($equiv) {
971
			$exp = preg_replace($equiv, '', $exp);
972
		}
973
974
		return preg_match("/\\b$cle\\./", $exp);
975
	} else {
976
		foreach ($exp as $v) {
977
			if (calculer_jointnul($cle, $v, $equiv)) {
978
				return true;
979
			}
980
		}
981
982
		return false;
983
	}
984
}
985
986
// https://code.spip.net/@reinjecte_joint
987
function reinjecte_joint($afrom, $from) {
988
	$from_synth = array();
989
	foreach ($from as $k => $v) {
990
		$from_synth[$k] = $from[$k];
991
		if (isset($afrom[$k])) {
992
			foreach ($afrom[$k] as $kk => $vv) {
993
				$afrom[$k][$kk] = implode(' ', $afrom[$k][$kk]);
994
			}
995
			$from_synth["$k@"] = implode(' ', $afrom[$k]);
996
			unset($afrom[$k]);
997
		}
998
	}
999
1000
	return $from_synth;
1001
}
1002
1003
// https://code.spip.net/@remplacer_jointnul
1004
function remplacer_jointnul($cle, $exp, $equiv = '') {
1005
	if (!is_array($exp)) {
1006
		return preg_replace($equiv, $cle, $exp);
1007
	} else {
1008
		foreach ($exp as $k => $v) {
1009
			$exp[$k] = remplacer_jointnul($cle, $v, $equiv);
1010
		}
1011
1012
		return $exp;
1013
	}
1014
}
1015
1016
// calcul du nom du squelette
1017
// https://code.spip.net/@calculer_nom_fonction_squel
1018
function calculer_nom_fonction_squel($skel, $mime_type = 'html', $connect = '') {
1019
	// ne pas doublonner les squelette selon qu'ils sont calcules depuis ecrire/ ou depuis la racine
1020 View Code Duplication
	if ($l = strlen(_DIR_RACINE) and strncmp($skel, _DIR_RACINE, $l) == 0) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1021
		$skel = substr($skel, strlen(_DIR_RACINE));
1022
	}
1023
1024
	return $mime_type
1025
	. (!$connect ? '' : preg_replace('/\W/', "_", $connect)) . '_'
1026
	. md5($GLOBALS['spip_version_code'] . ' * ' . $skel . (isset($GLOBALS['marqueur_skel']) ? '*' . $GLOBALS['marqueur_skel'] : ''));
1027
}
1028