Completed
Push — master ( 23dea2...2a7532 )
by cam
06:32 queued 02:13
created

composer.php ➔ synthetiser_balise_dynamique()   B

Complexity

Conditions 6
Paths 8

Size

Total Lines 29

Duplication

Lines 0
Ratio 0 %

Importance

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