Completed
Push — master ( 1c0099...508762 )
by cam
05:21
created

composer.php ➔ executer_balise_dynamique()   F

Complexity

Conditions 21
Paths 396

Size

Total Lines 79

Duplication

Lines 13
Ratio 16.46 %

Importance

Changes 0
Metric Value
cc 21
nc 396
nop 3
dl 13
loc 79
rs 0.8833
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
 * 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
	$nomfonction = $nom;
398
	$nomfonction_generique = "";
399
400
	$appel_php_depuis_modele = false;
401
	if (is_array($context_compil)
402
	  and !is_numeric($context_compil[3])
403
	  and empty($context_compil[0])
404
		and empty($context_compil[1])
405
		and empty($context_compil[2])
406
		and empty($context_compil[3])) {
407
		$appel_php_depuis_modele = true;
408
	}
409
410
	// Calculer un nom générique (ie. 'formulaire_' dans 'formulaire_editer_article')
411
	if (false !== ($p = strpos($nom, "_"))) {
412
		$nomfonction_generique = substr($nom, 0, $p + 1);
413
	}
414
415
	if (!$fonction_balise = charger_fonction($nomfonction, 'balise', true)) {
416
		if ($nomfonction_generique and $fonction_balise = charger_fonction($nomfonction_generique, 'balise', true)) {
417
			// et injecter en premier arg le nom de la balise 
418
			array_unshift($args, $nom);
419
			$nomfonction = $nomfonction_generique;
420
		}
421
	}
422
423 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...
424
		$msg = array('zbug_balise_inexistante', array('from' => 'CVT', 'balise' => $nom));
425
		erreur_squelette($msg, $context_compil);
426
427
		return '';
428
	}
429
430
	// retrouver le fichier qui a déclaré la fonction
431
	// même si la fonction dynamique est déclarée dans un fichier de fonctions.
432
	// Attention sous windows, getFileName() retourne un antislash. 
433
	$reflector = new ReflectionFunction($fonction_balise);
434
	$file = str_replace('\\', '/', $reflector->getFileName());
435 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...
436
		$file = substr($file, strlen(_ROOT_RACINE));
437
	}
438
439
	// Y a-t-il une fonction de traitement des arguments ?
440
	$f = 'balise_' . $nomfonction . '_stat';
441
442
	$r = !function_exists($f) ? $args : $f($args, $context_compil);
443
444
	if (!is_array($r)) {
445
		return $r;
446
	}
447
448
	// verifier que la fonction dyn est la, 
449
	// sinon se replier sur la generique si elle existe
450
	if (!function_exists('balise_' . $nomfonction . '_dyn')) {
451
		if ($nomfonction_generique
452
			and $file = include_spip("balise/" . strtolower($nomfonction_generique))
453
			and function_exists('balise_' . $nomfonction_generique . '_dyn')
454
		) {
455
			// et lui injecter en premier arg le nom de la balise 
456
			array_unshift($r, $nom);
457
			$nomfonction = $nomfonction_generique;
458
			if (!_DIR_RESTREINT) {
459
				$file = _DIR_RESTREINT_ABS . $file;
460
			}
461 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...
462
			$msg = array('zbug_balise_inexistante', array('from' => 'CVT', 'balise' => $nom));
463
			erreur_squelette($msg, $context_compil);
464
465
			return '';
466
		}
467
	}
468
469
	if ($appel_php_depuis_modele) {
470
		$context_compil['appel_php_depuis_modele'] = true;
471
	}
472
	return synthetiser_balise_dynamique($nomfonction, $r, $file, $context_compil);
0 ignored issues
show
Bug introduced by
It seems like $file defined by include_spip('balise/' ....nomfonction_generique)) on line 452 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...
473
474
}
475
476
477
/**
478
 * Selectionner la langue de l'objet dans la boucle
479
 *
480
 * Applique sur un item de boucle la langue de l'élément qui est parcourru.
481
 * Sauf dans les cas ou il ne le faut pas !
482
 *
483
 * La langue n'est pas modifiée lorsque :
484
 * - la globale 'forcer_lang' est définie à true
485
 * - l'objet ne définit pas de langue
486
 * - le titre contient une balise multi.
487
 *
488
 * @param string $lang
489
 *     Langue de l'objet
490
 * @param string $lang_select
491
 *     'oui' si critère lang_select est présent, '' sinon.
492
 * @param null|string $titre
493
 *     Titre de l'objet
494
 * @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...
495
 **/
496
function lang_select_public($lang, $lang_select, $titre = null) {
497
	// Cas 1. forcer_lang = true et pas de critere {lang_select}
498
	if (isset($GLOBALS['forcer_lang']) and $GLOBALS['forcer_lang']
499
		and $lang_select !== 'oui'
500
	) {
501
		$lang = $GLOBALS['spip_lang'];
502
	} // Cas 2. l'objet n'a pas de langue definie (ou definie a '')
503
	elseif (!strlen($lang)) {
504
		$lang = $GLOBALS['spip_lang'];
505
	} // Cas 3. l'objet est multilingue !
506
	elseif ($lang_select !== 'oui'
507
		and strlen($titre) > 10
508
		and strpos($titre, '<multi>') !== false
509
		and strpos(echappe_html($titre), '<multi>') !== false
510
	) {
511
		$lang = $GLOBALS['spip_lang'];
512
	}
513
514
	// faire un lang_select() eventuellement sur la langue inchangee
515
	lang_select($lang);
516
517
	return;
518
}
519
520
521
// Si un tableau &doublons[articles] est passe en parametre,
522
// il faut le nettoyer car il pourrait etre injecte en SQL
523
// https://code.spip.net/@nettoyer_env_doublons
524
function nettoyer_env_doublons($envd) {
525
	foreach ($envd as $table => $liste) {
526
		$n = '';
527
		foreach (explode(',', $liste) as $val) {
528
			if ($a = intval($val) and $val === strval($a)) {
529
				$n .= ',' . $val;
530
			}
531
		}
532
		if (strlen($n)) {
533
			$envd[$table] = $n;
534
		} else {
535
			unset($envd[$table]);
536
		}
537
	}
538
539
	return $envd;
540
}
541
542
/**
543
 * Cherche la présence d'un opérateur SELF ou SUBSELECT
544
 *
545
 * Cherche dans l'index 0 d'un tableau, la valeur SELF ou SUBSELECT
546
 * indiquant pour une expression WHERE de boucle que nous sommes
547
 * face à une sous-requête.
548
 *
549
 * Cherche de manière récursive également dans les autres valeurs si celles-ci
550
 * sont des tableaux
551
 *
552
 * @param string|array $w
553
 *     Description d'une condition WHERE de boucle (ou une partie de cette description)
554
 * @return string|bool
555
 *     Opérateur trouvé (SELF ou SUBSELECT) sinon false.
556
 **/
557
function match_self($w) {
558
	if (is_string($w)) {
559
		return false;
560
	}
561
	if (is_array($w)) {
562
		if (in_array(reset($w), array("SELF", "SUBSELECT"))) {
563
			return $w;
564
		}
565
		foreach (array_filter($w, 'is_array') as $sw) {
566
			if ($m = match_self($sw)) {
567
				return $m;
568
			}
569
		}
570
	}
571
572
	return false;
573
}
574
575
/**
576
 * Remplace une condition décrivant une sous requête par son code
577
 *
578
 * @param array|string $w
579
 *     Description d'une condition WHERE de boucle (ou une partie de cette description)
580
 *     qui possède une description de sous-requête
581
 * @param string $sousrequete
582
 *     Code PHP de la sous requête (qui doit remplacer la description)
583
 * @return array|string
584
 *     Tableau de description du WHERE dont la description de sous-requête
585
 *     est remplacée par son code.
586
 **/
587
function remplace_sous_requete($w, $sousrequete) {
588
	if (is_array($w)) {
589
		if (in_array(reset($w), array("SELF", "SUBSELECT"))) {
590
			return $sousrequete;
591
		}
592
		foreach ($w as $k => $sw) {
593
			$w[$k] = remplace_sous_requete($sw, $sousrequete);
594
		}
595
	}
596
597
	return $w;
598
}
599
600
/**
601
 * Sépare les conditions de boucles simples de celles possédant des sous-requêtes.
602
 *
603
 * @param array $where
604
 *     Description d'une condition WHERE de boucle
605
 * @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...
606
 *     Liste de 2 tableaux :
607
 *     - Conditions simples (ne possédant pas de sous requêtes)
608
 *     - Conditions avec des sous requêtes
609
 **/
610
function trouver_sous_requetes($where) {
611
	$where_simples = array();
612
	$where_sous = array();
613
	foreach ($where as $k => $w) {
614
		if (match_self($w)) {
615
			$where_sous[$k] = $w;
616
		} else {
617
			$where_simples[$k] = $w;
618
		}
619
	}
620
621
	return array($where_simples, $where_sous);
622
}
623
624
625
/**
626
 * Calcule une requête et l’exécute
627
 *
628
 * Cette fonction est présente dans les squelettes compilés.
629
 * Elle peut permettre de générer des requêtes avec jointure.
630
 *
631
 * @param array $select
632
 * @param array $from
633
 * @param array $from_type
634
 * @param array $where
635
 * @param array $join
636
 * @param array $groupby
637
 * @param array $orderby
638
 * @param string $limit
639
 * @param array $having
640
 * @param string $table
641
 * @param string $id
642
 * @param string $serveur
643
 * @param bool $requeter
644
 * @return resource
645
 */
646
function calculer_select(
647
	$select = array(),
648
	$from = array(),
649
	$from_type = array(),
650
	$where = array(),
651
	$join = array(),
652
	$groupby = array(),
653
	$orderby = array(),
654
	$limit = '',
655
	$having = array(),
656
	$table = '',
657
	$id = '',
658
	$serveur = '',
659
	$requeter = true
660
) {
661
662
	// retirer les criteres vides:
663
	// {X ?} avec X absent de l'URL
664
	// {par #ENV{X}} avec X absent de l'URL
665
	// IN sur collection vide (ce dernier devrait pouvoir etre fait a la compil)
666
	$menage = false;
667
	foreach ($where as $k => $v) {
668 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...
669
			if ((count($v) >= 2) && ($v[0] == 'REGEXP') && ($v[2] == "'.*'")) {
670
				$op = false;
671
			} elseif ((count($v) >= 2) && ($v[0] == 'LIKE') && ($v[2] == "'%'")) {
672
				$op = false;
673
			} else {
674
				$op = $v[0] ? $v[0] : $v;
675
			}
676
		} else {
677
			$op = $v;
678
		}
679
		if ((!$op) or ($op == 1) or ($op == '0=0')) {
680
			unset($where[$k]);
681
			$menage = true;
682
		}
683
	}
684
685
	// evacuer les eventuels groupby vide issus d'un calcul dynamique
686
	$groupby = array_diff($groupby, array(''));
687
688
	// remplacer les sous requetes recursives au calcul
689
	list($where_simples, $where_sous) = trouver_sous_requetes($where);
690
	foreach ($where_sous as $k => $w) {
691
		$menage = true;
692
		// on recupere la sous requete 
693
		$sous = match_self($w);
694
		if ($sous[0] == 'SELF') {
695
			// c'est une sous requete identique a elle meme sous la forme (SELF,$select,$where)
696
			array_push($where_simples, $sous[2]);
697
			$wheresub = array(
698
				$sous[2],
699
				'0=0'
700
			); // pour accepter une string et forcer a faire le menage car on a surement simplifie select et where
701
			$jsub = $join;
702
			// trouver les jointures utiles a
703
			// reinjecter dans le where de la sous requete les conditions supplementaires des jointures qui y sont mentionnees
704
			// ie L1.objet='article'
705
			// on construit le where une fois, puis on ajoute les where complentaires si besoin, et on reconstruit le where en fonction
706
			$i = 0;
707
			do {
708
				$where[$k] = remplace_sous_requete($w, "(" . calculer_select(
709
						array($sous[1] . " AS id"),
710
						$from,
711
						$from_type,
712
						$wheresub,
713
						$jsub,
714
						array(), array(), '',
715
						$having, $table, $id, $serveur, false) . ")");
716
				if (!$i) {
717
					$i = 1;
718
					$wherestring = calculer_where_to_string($where[$k]);
719
					foreach ($join as $cle => $wj) {
720
						if (count($wj) == 4
721
							and strpos($wherestring, "{$cle}.") !== false
722
						) {
723
							$i = 0;
724
							$wheresub[] = $wj[3];
725
							unset($jsub[$cle][3]);
726
						}
727
					}
728
				}
729
			} while ($i++ < 1);
730
		}
731
		if ($sous[0] == 'SUBSELECT') {
732
			// c'est une sous requete explicite sous la forme identique a sql_select : (SUBSELECT,$select,$from,$where,$groupby,$orderby,$limit,$having)
733
			array_push($where_simples, $sous[3]); // est-ce utile dans ce cas ?
734
			$where[$k] = remplace_sous_requete($w, "(" . calculer_select(
735
					$sous[1], # select
736
					$sous[2], #from
737
					array(), #from_type
738
					$sous[3] ? (is_array($sous[3]) ? $sous[3] : array($sous[3])) : array(),
739
					#where, qui peut etre de la forme string comme dans sql_select
740
					array(), #join
741
					$sous[4] ? $sous[4] : array(), #groupby
742
					$sous[5] ? $sous[5] : array(), #orderby
743
					$sous[6], #limit
744
					$sous[7] ? $sous[7] : array(), #having
745
					$table, $id, $serveur, false
746
				) . ")");
747
		}
748
		array_pop($where_simples);
749
	}
750
751
	foreach ($having as $k => $v) {
752
		if ((!$v) or ($v == 1) or ($v == '0=0')) {
753
			unset($having[$k]);
754
		}
755
	}
756
757
	// Installer les jointures.
758
	// Retirer celles seulement utiles aux criteres finalement absents mais
759
	// parcourir de la plus recente a la moins recente pour pouvoir eliminer Ln
760
	// si elle est seulement utile a Ln+1 elle meme inutile
761
762
	$afrom = array();
763
	$equiv = array();
764
	$k = count($join);
765
	foreach (array_reverse($join, true) as $cledef => $j) {
766
		$cle = $cledef;
767
		// le format de join est :
768
		// array(table depart, cle depart [,cle arrivee[,condition optionnelle and ...]])
769
		$join[$cle] = array_values($join[$cle]); // recalculer les cles car des unset ont pu perturber
770
		if (count($join[$cle]) == 2) {
771
			$join[$cle][] = $join[$cle][1];
772
		}
773
		if (count($join[$cle]) == 3) {
774
			$join[$cle][] = '';
775
		}
776
		list($t, $c, $carr, $and) = $join[$cle];
777
		// si le nom de la jointure n'a pas ete specifiee, on prend Lx avec x sont rang dans la liste
778
		// pour compat avec ancienne convention
779
		if (is_numeric($cle)) {
780
			$cle = "L$k";
781
		}
782
		if (!$menage
783
			or isset($afrom[$cle])
784
			or calculer_jointnul($cle, $select)
785
			or calculer_jointnul($cle, array_diff_key($join, array($cle => $join[$cle])))
786
			or calculer_jointnul($cle, $having)
787
			or calculer_jointnul($cle, $where_simples)
788
		) {
789
			// corriger les references non explicites dans select
790
			// ou groupby
791
			foreach ($select as $i => $s) {
792
				if ($s == $c) {
793
					$select[$i] = "$cle.$c AS $c";
794
					break;
795
				}
796
			}
797
			foreach ($groupby as $i => $g) {
798
				if ($g == $c) {
799
					$groupby[$i] = "$cle.$c";
800
					break;
801
				}
802
			}
803
			// on garde une ecriture decomposee pour permettre une simplification ulterieure si besoin
804
			// sans recours a preg_match
805
			// un implode(' ',..) est fait dans reinjecte_joint un peu plus bas
806
			$afrom[$t][$cle] = array(
807
				"\n" .
808
				(isset($from_type[$cle]) ? $from_type[$cle] : "INNER") . " JOIN",
809
				$from[$cle],
810
				"AS $cle",
811
				"ON (",
812
				"$cle.$c",
813
				"=",
814
				"$t.$carr",
815
				($and ? "AND " . $and : "") .
816
				")"
817
			);
818
			if (isset($afrom[$cle])) {
819
				$afrom[$t] = $afrom[$t] + $afrom[$cle];
820
				unset($afrom[$cle]);
821
			}
822
			$equiv[] = $carr;
823
		} else {
824
			unset($join[$cledef]);
825
		}
826
		unset($from[$cle]);
827
		$k--;
828
	}
829
830
	if (count($afrom)) {
831
		// Regarder si la table principale ne sert finalement a rien comme dans
832
		//<BOUCLE3(MOTS){id_article}{id_mot}> class='on'</BOUCLE3>
833
		//<BOUCLE2(MOTS){id_article} />#TOTAL_BOUCLE<//B2>
834
		//<BOUCLE5(RUBRIQUES){id_mot}{tout} />#TOTAL_BOUCLE<//B5>
835
		// ou dans
836
		//<BOUCLE8(HIERARCHIE){id_rubrique}{tout}{type='Squelette'}{inverse}{0,1}{lang_select=non} />#TOTAL_BOUCLE<//B8>
837
		// qui comporte plusieurs jointures
838
		// ou dans
839
		// <BOUCLE6(ARTICLES){id_mot=2}{statut==.*} />#TOTAL_BOUCLE<//B6>
840
		// <BOUCLE7(ARTICLES){id_mot>0}{statut?} />#TOTAL_BOUCLE<//B7>
841
		// penser a regarder aussi la clause orderby pour ne pas simplifier abusivement
842
		// <BOUCLE9(ARTICLES){recherche truc}{par titre}>#ID_ARTICLE</BOUCLE9>
843
		// penser a regarder aussi la clause groubpy pour ne pas simplifier abusivement
844
		// <BOUCLE10(EVENEMENTS){id_rubrique} />#TOTAL_BOUCLE<//B10>
845
846
		$t = key($from);
847
		$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...
848
		reset($from);
849
		$e = '/\b(' . "$t\\." . join("|" . $t . '\.', $equiv) . ')\b/';
850
		if (!(strpos($t, ' ') or // jointure des le depart cf boucle_doc
851
				calculer_jointnul($t, $select, $e) or
852
				calculer_jointnul($t, $join, $e) or
853
				calculer_jointnul($t, $where, $e) or
854
				calculer_jointnul($t, $orderby, $e) or
855
				calculer_jointnul($t, $groupby, $e) or
856
				calculer_jointnul($t, $having, $e))
857
			&& count($afrom[$t])
858
		) {
859
			$nfrom = reset($afrom[$t]);
860
			$nt = key($afrom[$t]);
861
			unset($from[$t]);
862
			$from[$nt] = $nfrom[1];
863
			unset($afrom[$t][$nt]);
864
			$afrom[$nt] = $afrom[$t];
865
			unset($afrom[$t]);
866
			$e = '/\b' . preg_quote($nfrom[6]) . '\b/';
867
			$t = $nfrom[4];
868
			$alias = "";
869
			// verifier que les deux cles sont homonymes, sinon installer un alias dans le select
870
			$oldcle = explode('.', $nfrom[6]);
871
			$oldcle = end($oldcle);
872
			$newcle = explode('.', $nfrom[4]);
873
			$newcle = end($newcle);
874
			if ($newcle != $oldcle) {
875
				// si l'ancienne cle etait deja dans le select avec un AS
876
				// reprendre simplement ce AS
877
				$as = '/\b' . preg_quote($nfrom[6]) . '\s+(AS\s+\w+)\b/';
878
				if (preg_match($as, implode(',', $select), $m)) {
879
					$alias = "";
880
				} else {
881
					$alias = ", " . $nfrom[4] . " AS $oldcle";
882
				}
883
			}
884
			$select = remplacer_jointnul($t . $alias, $select, $e);
885
			$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...
886
			$where = remplacer_jointnul($t, $where, $e);
887
			$having = remplacer_jointnul($t, $having, $e);
888
			$groupby = remplacer_jointnul($t, $groupby, $e);
889
			$orderby = remplacer_jointnul($t, $orderby, $e);
890
		}
891
		$from = reinjecte_joint($afrom, $from);
892
	}
893
	$GLOBALS['debug']['aucasou'] = array($table, $id, $serveur, $requeter);
894
	$r = sql_select($select, $from, $where,
895
		$groupby, array_filter($orderby), $limit, $having, $serveur, $requeter);
896
	unset($GLOBALS['debug']['aucasou']);
897
898
	return $r;
899
}
900
901
/**
902
 * Analogue a calculer_mysql_expression et autre (a unifier ?)
903
 *
904
 * @param string|array $v
905
 * @param string $join
906
 * @return string
907
 */
908 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...
909
	if (empty($v)) {
910
		return '';
911
	}
912
913
	if (!is_array($v)) {
914
		return $v;
915
	} else {
916
		$exp = "";
917
		if (strtoupper($join) === 'AND') {
918
			return $exp . join(" $join ", array_map('calculer_where_to_string', $v));
919
		} else {
920
			return $exp . join($join, $v);
921
		}
922
	}
923
}
924
925
926
//condition suffisante (mais non necessaire) pour qu'une table soit utile
927
928
// https://code.spip.net/@calculer_jointnul
929
function calculer_jointnul($cle, $exp, $equiv = '') {
930
	if (!is_array($exp)) {
931
		if ($equiv) {
932
			$exp = preg_replace($equiv, '', $exp);
933
		}
934
935
		return preg_match("/\\b$cle\\./", $exp);
936
	} else {
937
		foreach ($exp as $v) {
938
			if (calculer_jointnul($cle, $v, $equiv)) {
939
				return true;
940
			}
941
		}
942
943
		return false;
944
	}
945
}
946
947
// https://code.spip.net/@reinjecte_joint
948
function reinjecte_joint($afrom, $from) {
949
	$from_synth = array();
950
	foreach ($from as $k => $v) {
951
		$from_synth[$k] = $from[$k];
952
		if (isset($afrom[$k])) {
953
			foreach ($afrom[$k] as $kk => $vv) {
954
				$afrom[$k][$kk] = implode(' ', $afrom[$k][$kk]);
955
			}
956
			$from_synth["$k@"] = implode(' ', $afrom[$k]);
957
			unset($afrom[$k]);
958
		}
959
	}
960
961
	return $from_synth;
962
}
963
964
// https://code.spip.net/@remplacer_jointnul
965
function remplacer_jointnul($cle, $exp, $equiv = '') {
966
	if (!is_array($exp)) {
967
		return preg_replace($equiv, $cle, $exp);
968
	} else {
969
		foreach ($exp as $k => $v) {
970
			$exp[$k] = remplacer_jointnul($cle, $v, $equiv);
971
		}
972
973
		return $exp;
974
	}
975
}
976
977
// calcul du nom du squelette
978
// https://code.spip.net/@calculer_nom_fonction_squel
979
function calculer_nom_fonction_squel($skel, $mime_type = 'html', $connect = '') {
980
	// ne pas doublonner les squelette selon qu'ils sont calcules depuis ecrire/ ou depuis la racine
981 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...
982
		$skel = substr($skel, strlen(_DIR_RACINE));
983
	}
984
985
	return $mime_type
986
	. (!$connect ? '' : preg_replace('/\W/', "_", $connect)) . '_'
987
	. md5($GLOBALS['spip_version_code'] . ' * ' . $skel . (isset($GLOBALS['marqueur_skel']) ? '*' . $GLOBALS['marqueur_skel'] : ''));
988
}
989