Completed
Push — master ( c12d0a...30a01b )
by cam
31:59
created

utils.php ➔ avertir_auteurs()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
nc 4
nop 3
dl 0
loc 14
rs 9.7998
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
 * Utilitaires indispensables autour du serveur Http.
15
 *
16
 * @package SPIP\Core\Utilitaires
17
 **/
18
19
if (!defined('_ECRIRE_INC_VERSION')) {
20
	return;
21
}
22
23
24
/**
25
 * Cherche une fonction surchargeable et en retourne le nom exact,
26
 * après avoir chargé le fichier la contenant si nécessaire.
27
 *
28
 * Charge un fichier (suivant les chemins connus) et retourne si elle existe
29
 * le nom de la fonction homonyme `$dir_$nom`, ou suffixé `$dir_$nom_dist`
30
 *
31
 * Peut être appelé plusieurs fois, donc optimisé.
32
 *
33
 * @api
34
 * @uses include_spip() Pour charger le fichier
35
 * @example
36
 *     ```
37
 *     $envoyer_mail = charger_fonction('envoyer_mail', 'inc');
38
 *     $envoyer_mail($email, $sujet, $texte);
39
 *     ```
40
 *
41
 * @param string $nom
42
 *     Nom de la fonction (et du fichier)
43
 * @param string $dossier
44
 *     Nom du dossier conteneur
45
 * @param bool $continue
46
 *     true pour ne pas râler si la fonction n'est pas trouvée
47
 * @return string
48
 *     Nom de la fonction, ou false.
49
 */
50
function charger_fonction($nom, $dossier = 'exec', $continue = false) {
51
	static $echecs = array();
52
53
	if (strlen($dossier) and substr($dossier, -1) != '/') {
54
		$dossier .= '/';
55
	}
56
	$f = str_replace('/', '_', $dossier) . $nom;
57
58
	if (function_exists($f)) {
59
		return $f;
60
	}
61
	if (function_exists($g = $f . '_dist')) {
62
		return $g;
63
	}
64
65
	if (isset($echecs[$f])) {
66
		return $echecs[$f];
67
	}
68
	// Sinon charger le fichier de declaration si plausible
69
70
	if (!preg_match(',^\w+$,', $f)) {
71
		if ($continue) {
72
			return false;
73
		} //appel interne, on passe
74
		include_spip('inc/minipres');
75
		echo minipres();
76
		exit;
77
	}
78
79
	// passer en minuscules (cf les balises de formulaires)
80
	// et inclure le fichier
81
	if (!$inc = include_spip($dossier . ($d = strtolower($nom)))
82
		// si le fichier truc/machin/nom.php n'existe pas,
83
		// la fonction peut etre definie dans truc/machin.php qui regroupe plusieurs petites fonctions
84
		and strlen(dirname($dossier)) and dirname($dossier) != '.'
85
	) {
86
		include_spip(substr($dossier, 0, -1));
87
	}
88
	if (function_exists($f)) {
89
		return $f;
90
	}
91
	if (function_exists($g)) {
92
		return $g;
93
	}
94
95
	if ($continue) {
96
		return $echecs[$f] = false;
97
	}
98
99
	// Echec : message d'erreur
100
	spip_log("fonction $nom ($f ou $g) indisponible" .
101
		($inc ? "" : " (fichier $d absent de $dossier)"));
102
103
	include_spip('inc/minipres');
104
	echo minipres(_T('forum_titre_erreur'),
105
		_T('fichier_introuvable', array('fichier' => '<b>' . spip_htmlentities($d) . '</b>')),
106
		array('all_inline'=>true,'status'=>404));
107
	exit;
108
}
109
110
/**
111
 * Inclusion unique avec verification d'existence du fichier + log en crash sinon
112
 *
113
 * @param string $file
114
 * @return bool
115
 */
116
function include_once_check($file) {
117
	if (file_exists($file)) {
118
		include_once $file;
119
120
		return true;
121
	}
122
	$crash = (isset($GLOBALS['meta']['message_crash_plugins']) ? unserialize($GLOBALS['meta']['message_crash_plugins']) : '');
123
	$crash = ($crash ? $crash : array());
124
	$crash[$file] = true;
125
	ecrire_meta('message_crash_plugins', serialize($crash));
126
127
	return false;
128
}
129
130
131
/**
132
 * Inclut un fichier PHP (en le cherchant dans les chemins)
133
 *
134
 * @api
135
 * @uses find_in_path()
136
 * @example
137
 *     ```
138
 *     include_spip('inc/texte');
139
 *     ```
140
 *
141
 * @param string $f
142
 *     Nom du fichier (sans l'extension)
143
 * @param bool $include
144
 *     - true pour inclure le fichier,
145
 *     - false ne fait que le chercher
146
 * @return string|bool
147
 *     - false : fichier introuvable
148
 *     - string : chemin du fichier trouvé
149
 **/
150
function include_spip($f, $include = true) {
151
	return find_in_path($f . '.php', '', $include);
152
}
153
154
/**
155
 * Requiert un fichier PHP (en le cherchant dans les chemins)
156
 *
157
 * @uses find_in_path()
158
 * @see  include_spip()
159
 * @example
160
 *     ```
161
 *     require_spip('inc/texte');
162
 *     ```
163
 *
164
 * @param string $f
165
 *     Nom du fichier (sans l'extension)
166
 * @return string|bool
167
 *     - false : fichier introuvable
168
 *     - string : chemin du fichier trouvé
169
 **/
170
function require_spip($f) {
171
	return find_in_path($f . '.php', '', 'required');
172
}
173
174
175
/**
176
 * Raccourci pour inclure mes_fonctions.php et tous les fichiers _fonctions.php des plugin
177
 * quand on a besoin dans le PHP de filtres/fonctions qui y sont definis
178
 */
179
function include_fichiers_fonctions() {
180
	static $done = false;
181
	if (!$done) {
182
		include_spip('inc/lang');
183
184
		// NB: mes_fonctions peut initialiser $dossier_squelettes (old-style)
185
		// donc il faut l'inclure "en globals"
186
		if ($f = find_in_path('mes_fonctions.php')) {
187
			global $dossier_squelettes;
188
			include_once(_ROOT_CWD . $f);
189
		}
190
191
		if (@is_readable(_CACHE_PLUGINS_FCT)) {
192
			// chargement optimise precompile
193
			include_once(_CACHE_PLUGINS_FCT);
194
		}
195
		if (test_espace_prive()) {
196
			include_spip('inc/filtres_ecrire');
197
		}
198
		include_spip('public/fonctions'); // charger les fichiers fonctions associes aux criteres, balises..
199
		$done = true;
200
	}
201
}
202
203
/**
204
 * Exécute une fonction (appellée par un pipeline) avec la donnée transmise.
205
 *
206
 * Un pipeline est lie a une action et une valeur
207
 * chaque element du pipeline est autorise a modifier la valeur
208
 * le pipeline execute les elements disponibles pour cette action,
209
 * les uns apres les autres, et retourne la valeur finale
210
 *
211
 * Cf. compose_filtres dans references.php, qui est la
212
 * version compilee de cette fonctionnalite
213
 * appel unitaire d'une fonction du pipeline
214
 * utilisee dans le script pipeline precompile
215
 *
216
 * on passe $val par reference pour limiter les allocations memoire
217
 *
218
 * @param string $fonc
219
 *     Nom de la fonction appelée par le pipeline
220
 * @param string|array $val
221
 *     Les paramètres du pipeline, son environnement
222
 * @return string|array $val
223
 *     Les paramètres du pipeline modifiés
224
 **/
225
function minipipe($fonc, &$val) {
226
	// fonction
227
	if (function_exists($fonc)) {
228
		$val = call_user_func($fonc, $val);
229
	} // Class::Methode
230
	else {
231
		if (preg_match("/^(\w*)::(\w*)$/S", $fonc, $regs)
232
			and $methode = array($regs[1], $regs[2])
233
			and is_callable($methode)
234
		) {
235
			$val = call_user_func($methode, $val);
236
		} else {
237
			spip_log("Erreur - '$fonc' non definie !");
238
		}
239
	}
240
241
	return $val;
242
}
243
244
/**
245
 * Appel d’un pipeline
246
 *
247
 * Exécute le pipeline souhaité, éventuellement avec des données initiales.
248
 * Chaque plugin qui a demandé à voir ce pipeline vera sa fonction spécifique appelée.
249
 * Les fonctions (des plugins) appelées peuvent modifier à leur guise le contenu.
250
 *
251
 * Deux types de retours. Si `$val` est un tableau de 2 éléments, avec une clé `data`
252
 * on retourne uniquement ce contenu (`$val['data']`) sinon on retourne tout `$val`.
253
 *
254
 *
255
 * @example
256
 *     Appel du pipeline `pre_insertion`
257
 *     ```
258
 *     $champs = pipeline('pre_insertion', array(
259
 *         'args' => array('table' => 'spip_articles'),
260
 *         'data' => $champs
261
 *     ));
262
 *     ```
263
 *
264
 * @param string $action
265
 *     Nom du pipeline
266
 * @param null|string|array $val
267
 *     Données à l’entrée du pipeline
268
 * @return mixed|null
269
 *     Résultat
270
 */
271
function pipeline($action, $val = null) {
272
	static $charger;
273
274
	// chargement initial des fonctions mises en cache, ou generation du cache
275
	if (!$charger) {
276
		if (!($ok = @is_readable($charger = _CACHE_PIPELINES))) {
277
			include_spip('inc/plugin');
278
			// generer les fichiers php precompiles
279
			// de chargement des plugins et des pipelines
280
			actualise_plugins_actifs();
281
			if (!($ok = @is_readable($charger))) {
282
				spip_log("fichier $charger pas cree");
283
			}
284
		}
285
286
		if ($ok) {
287
			include_once $charger;
288
		}
289
	}
290
291
	// appliquer notre fonction si elle existe
292
	$fonc = 'execute_pipeline_' . strtolower($action);
293
	if (function_exists($fonc)) {
294
		$val = $fonc($val);
295
	} // plantage ?
296
	else {
297
		spip_log("fonction $fonc absente : pipeline desactive", _LOG_ERREUR);
298
	}
299
300
	// si le flux est une table avec 2 cle args&data
301
	// on ne ressort du pipe que les donnees dans 'data'
302
	// array_key_exists pour php 4.1.0
303
	if (is_array($val)
304
		and count($val) == 2
305
		and (array_key_exists('data', $val))
306
	) {
307
		$val = $val['data'];
308
	}
309
310
	return $val;
311
}
312
313
/**
314
 * Enregistrement des événements
315
 *
316
 * Signature : `spip_log(message[,niveau|type|type.niveau])`
317
 *
318
 * Le niveau de log par défaut est la valeur de la constante `_LOG_INFO`
319
 *
320
 * Les différents niveaux possibles sont :
321
 *
322
 * - `_LOG_HS` : écrira 'HS' au début de la ligne logguée
323
 * - `_LOG_ALERTE_ROUGE` : 'ALERTE'
324
 * - `_LOG_CRITIQUE` :  'CRITIQUE'
325
 * - `_LOG_ERREUR` : 'ERREUR'
326
 * - `_LOG_AVERTISSEMENT` : 'WARNING'
327
 * - `_LOG_INFO_IMPORTANTE` : '!INFO'
328
 * - `_LOG_INFO` : 'info'
329
 * - `_LOG_DEBUG` : 'debug'
330
 *
331
 * @example
332
 *   ```
333
 *   spip_log($message)
334
 *   spip_log($message, 'recherche')
335
 *   spip_log($message, _LOG_DEBUG)
336
 *   spip_log($message, 'recherche.'._LOG_DEBUG)
337
 *   ```
338
 *
339
 * @api
340
 * @link https://programmer.spip.net/spip_log
341
 * @uses inc_log_dist()
342
 *
343
 * @param string $message
0 ignored issues
show
Documentation introduced by
Should the type for parameter $message not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
344
 *     Message à loger
345
 * @param string|int $name
0 ignored issues
show
Documentation introduced by
Should the type for parameter $name not be string|integer|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
346
 *
347
 *     - int indique le niveau de log, tel que `_LOG_DEBUG`
348
 *     - string indique le type de log
349
 *     - `string.int` indique les 2 éléments.
350
 *     Cette dernière notation est controversée mais le 3ème
351
 *     paramètre est planté pour cause de compatibilité ascendante.
352
 */
353
function spip_log($message = null, $name = null) {
354
	static $pre = array();
355
	static $log;
356
	preg_match('/^([a-z_]*)\.?(\d)?$/iS', (string)$name, $regs);
357
	if (!isset($regs[1]) or !$logname = $regs[1]) {
358
		$logname = null;
359
	}
360
	if (!isset($regs[2])) {
361
		$niveau = _LOG_INFO;
362
	}
363
	else {
364
		$niveau = intval($regs[2]);
365
	}
366
367
	if ($niveau <= (defined('_LOG_FILTRE_GRAVITE') ? _LOG_FILTRE_GRAVITE : _LOG_INFO_IMPORTANTE)) {
368
		if (!$pre) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $pre of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
369
			$pre = array(
370
				_LOG_HS => 'HS:',
371
				_LOG_ALERTE_ROUGE => 'ALERTE:',
372
				_LOG_CRITIQUE => 'CRITIQUE:',
373
				_LOG_ERREUR => 'ERREUR:',
374
				_LOG_AVERTISSEMENT => 'WARNING:',
375
				_LOG_INFO_IMPORTANTE => '!INFO:',
376
				_LOG_INFO => 'info:',
377
				_LOG_DEBUG => 'debug:'
378
			);
379
			$log = charger_fonction('log', 'inc');
380
		}
381
		if (!is_string($message)) {
382
			$message = print_r($message, true);
383
		}
384
		$log($pre[$niveau] . ' ' . $message, $logname);
385
	}
386
}
387
388
/**
389
 * Enregistrement des journaux
390
 *
391
 * @uses inc_journal_dist()
392
 * @param string $phrase Texte du journal
393
 * @param array $opt Tableau d'options
394
 **/
395
function journal($phrase, $opt = array()) {
396
	$journal = charger_fonction('journal', 'inc');
397
	$journal($phrase, $opt);
398
}
399
400
401
/**
402
 * Renvoie le `$_GET` ou le `$_POST` émis par l'utilisateur
403
 * ou pioché dans un tableau transmis
404
 *
405
 * @api
406
 * @param string $var
407
 *     Clé souhaitée
408
 * @param bool|array $c
409
 *     Tableau transmis (sinon cherche dans GET ou POST)
410
 * @return mixed|null
411
 *     - null si la clé n'a pas été trouvée
412
 *     - la valeur de la clé sinon.
413
 **/
414
function _request($var, $c = false) {
415
416
	if (is_array($c)) {
417
		return isset($c[$var]) ? $c[$var] : null;
418
	}
419
420
	if (isset($_GET[$var])) {
421
		$a = $_GET[$var];
422
	} elseif (isset($_POST[$var])) {
423
		$a = $_POST[$var];
424
	} else {
425
		return null;
426
	}
427
428
	// Si on est en ajax et en POST tout a ete encode
429
	// via encodeURIComponent, il faut donc repasser
430
	// dans le charset local...
431
	if (defined('_AJAX')
432
		and _AJAX
433
		and isset($GLOBALS['meta']['charset'])
434
		and $GLOBALS['meta']['charset'] != 'utf-8'
435
		and is_string($a)
436
		// check rapide mais pas fiable
437
		and preg_match(',[\x80-\xFF],', $a)
438
		// check fiable
439
		and include_spip('inc/charsets')
440
		and is_utf8($a)
441
	) {
442
		return importer_charset($a, 'utf-8');
443
	}
444
445
	return $a;
446
}
447
448
449
/**
450
 * Affecte une valeur à une clé (pour usage avec `_request()`)
451
 *
452
 * @see _request() Pour obtenir la valeur
453
 * @note Attention au cas ou l'on fait `set_request('truc', NULL);`
454
 *
455
 * @param string $var Nom de la clé
456
 * @param string $val Valeur à affecter
0 ignored issues
show
Documentation introduced by
Should the type for parameter $val not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
457
 * @param bool|array $c Tableau de données (sinon utilise `$_GET` et `$_POST`)
458
 * @return array|bool
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array|false.

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

Loading history...
459
 *     - array $c complété si un $c est transmis,
460
 *     - false sinon
461
 **/
462
function set_request($var, $val = null, $c = false) {
463
	if (is_array($c)) {
464
		unset($c[$var]);
465
		if ($val !== null) {
466
			$c[$var] = $val;
467
		}
468
469
		return $c;
470
	}
471
472
	unset($_GET[$var]);
473
	unset($_POST[$var]);
474
	if ($val !== null) {
475
		$_GET[$var] = $val;
476
	}
477
478
	return false; # n'affecte pas $c
479
}
480
481
/**
482
 * Sanitizer une valeur *SI* elle provient du GET ou POST
483
 * Utile dans les squelettes pour les valeurs qu'on attrape dans le env,
484
 * dont on veut permettre à un squelette de confiance appelant de fournir une valeur complexe
485
 * mais qui doit etre nettoyee si elle provient de l'URL
486
 *
487
 * On peut sanitizer
488
 * - une valeur simple : `$where = spip_sanitize_from_request($value, 'where')`
489
 * - un tableau en partie : `$env = spip_sanitize_from_request($env, ['key1','key2'])`
490
 * - un tableau complet : `$env = spip_sanitize_from_request($env, '*')`
491
 *
492
 * @param string|array $value
493
 * @param string|array $key
494
 * @param string $sanitize_function
495
 * @return array|mixed|string
496
 */
497
function spip_sanitize_from_request($value, $key, $sanitize_function='entites_html') {
498
	if (is_array($value)) {
499
		if ($key=='*') {
500
			$key = array_keys($value);
501
		}
502
		if (!is_array($key)) {
503
			$key = [$key];
504
		}
505
		foreach ($key as $k) {
506
			if (!empty($value[$k])) {
507
				$value[$k] = spip_sanitize_from_request($value[$k], $k, $sanitize_function);
508
			}
509
		}
510
		return $value;
511
	}
512
	// si la valeur vient des GET ou POST on la sanitize
513
	if (!empty($value) and $value == _request($key)) {
0 ignored issues
show
Bug introduced by
It seems like $key defined by parameter $key on line 497 can also be of type array; however, _request() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
514
		$value = $sanitize_function($value);
515
	}
516
	return $value;
517
}
518
519
/**
520
 * Tester si une URL est absolue
521
 * 
522
 * On est sur le web, on exclut certains protocoles, 
523
 * notamment 'file://', 'php://' et d'autres…
524
525
 * @param string $url
526
 * @return bool
527
 */
528
function tester_url_absolue($url) {
529
	$url = trim($url);
530
	if (preg_match(";^([a-z]{3,7}:)?//;Uims", $url, $m)) {
531
		if (
0 ignored issues
show
Unused Code introduced by
This if statement, and the following return statement can be replaced with return !(isset($m[1]) an...g', 'expect', 'zip')));.
Loading history...
532
			isset($m[1])
533
			and $p = strtolower(rtrim($m[1], ':'))
534
			and in_array($p, array('file', 'php', 'zlib', 'glob', 'phar', 'ssh2', 'rar', 'ogg', 'expect', 'zip'))
535
		  ) {
536
			return false;
537
		}
538
		return true;
539
	}
540
	return false;
541
}
542
543
/**
544
 * Prend une URL et lui ajoute/retire un paramètre
545
 *
546
 * @filtre
547
 * @link https://www.spip.net/4255
548
 * @example
549
 *     ```
550
 *     [(#SELF|parametre_url{suite,18})] (ajout)
551
 *     [(#SELF|parametre_url{suite,''})] (supprime)
552
 *     [(#SELF|parametre_url{suite[],1})] (tableaux valeurs multiples)
553
 *     ```
554
 *
555
 * @param string $url URL
556
 * @param string $c Nom du paramètre
557
 * @param string|array|null $v Valeur du paramètre
558
 * @param string $sep Séparateur entre les paramètres
559
 * @return string URL
0 ignored issues
show
Documentation introduced by
Should the return type not be null|string|string[]?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
560
 */
561
function parametre_url($url, $c, $v = null, $sep = '&amp;') {
562
	// requete erronnee : plusieurs variable dans $c et aucun $v
563
	if (strpos($c, "|") !== false and is_null($v)) {
564
		return null;
565
	}
566
567
	// lever l'#ancre
568
	if (preg_match(',^([^#]*)(#.*)$,', $url, $r)) {
569
		$url = $r[1];
570
		$ancre = $r[2];
571
	} else {
572
		$ancre = '';
573
	}
574
575
	// eclater
576
	$url = preg_split(',[?]|&amp;|&,', $url);
577
578
	// recuperer la base
579
	$a = array_shift($url);
580
	if (!$a) {
581
		$a = './';
582
	}
583
584
	$regexp = ',^(' . str_replace('[]', '\[\]', $c) . '[[]?[]]?)(=.*)?$,';
585
	$ajouts = array_flip(explode('|', $c));
586
	$u = is_array($v) ? $v : rawurlencode($v);
587
	$testv = (is_array($v) ? count($v) : strlen($v));
588
	$v_read = null;
589
	// lire les variables et agir
590
	foreach ($url as $n => $val) {
591
		if (preg_match($regexp, urldecode($val), $r)) {
592
			$r = array_pad($r, 3, null);
593
			if ($v === null) {
594
				// c'est un tableau, on memorise les valeurs
595
				if (substr($r[1], -2) == "[]") {
596
					if (!$v_read) {
597
						$v_read = array();
598
					}
599
					$v_read[] = $r[2] ? substr($r[2], 1) : '';
600
				} // c'est un scalaire, on retourne direct
601
				else {
602
					return $r[2] ? substr($r[2], 1) : '';
603
				}
604
			} // suppression
605
			elseif (!$testv) {
606
				unset($url[$n]);
607
			}
608
			// Ajout. Pour une variable, remplacer au meme endroit,
609
			// pour un tableau ce sera fait dans la prochaine boucle
610
			elseif (substr($r[1], -2) != '[]') {
611
				$url[$n] = $r[1] . '=' . $u;
612
				unset($ajouts[$r[1]]);
613
			}
614
			// Pour les tableaux on laisse tomber les valeurs de
615
			// départ, on remplira à l'étape suivante
616
			else {
617
				unset($url[$n]);
618
			}
619
		}
620
	}
621
622
	// traiter les parametres pas encore trouves
623
	if ($v === null
624
		and $args = func_get_args()
625
		and count($args) == 2
626
	) {
627
		return $v_read; // rien trouve ou un tableau
628
	} elseif ($testv) {
629
		foreach ($ajouts as $k => $n) {
630
			if (!is_array($v)) {
631
				$url[] = $k . '=' . $u;
632
			} else {
633
				$id = (substr($k, -2) == '[]') ? $k : ($k . "[]");
634
				foreach ($v as $w) {
635
					$url[] = $id . '=' . (is_array($w) ? 'Array' : $w);
636
				}
637
			}
638
		}
639
	}
640
641
	// eliminer les vides
642
	$url = array_filter($url);
643
644
	// recomposer l'adresse
645
	if ($url) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $url of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
646
		$a .= '?' . join($sep, $url);
647
	}
648
649
	return $a . $ancre;
650
}
651
652
/**
653
 * Ajoute (ou retire) une ancre sur une URL
654
 *
655
 * L’ancre est nettoyée : on translitère, vire les non alphanum du début,
656
 * et on remplace ceux à l'interieur ou au bout par `-`
657
 *
658
 * @example
659
 *     - `$url = ancre_url($url, 'navigation'); // => mettra l’ancre #navigation
660
 *     - `$url = ancre_url($url, ''); // => enlèvera une éventuelle ancre
661
 * @uses translitteration()
662
 * @param string $url
663
 * @param string $ancre
664
 * @return string
665
 */
666
function ancre_url($url, $ancre) {
667
	// lever l'#ancre
668
	if (preg_match(',^([^#]*)(#.*)$,', $url, $r)) {
669
		$url = $r[1];
670
	}
671
	if (preg_match('/[^-_a-zA-Z0-9]+/S', $ancre)) {
672
		if (!function_exists('translitteration')) {
673
			include_spip('inc/charsets');
674
		}
675
		$ancre = preg_replace(
676
			array('/^[^-_a-zA-Z0-9]+/', '/[^-_a-zA-Z0-9]/'),
677
			array('', '-'),
678
			translitteration($ancre)
679
		);
680
	}
681
	return $url . (strlen($ancre) ? '#' . $ancre : '');
682
}
683
684
/**
685
 * Pour le nom du cache, les `types_urls` et `self`
686
 *
687
 * @param string|null $reset
688
 * @return string
689
 */
690
function nettoyer_uri($reset = null) {
691
	static $done = false;
692
	static $propre = '';
693
	if (!is_null($reset)) {
694
		return $propre = $reset;
695
	}
696
	if ($done) {
697
		return $propre;
698
	}
699
	$done = true;
700
	return $propre = nettoyer_uri_var($GLOBALS['REQUEST_URI']);
701
}
702
703
/**
704
 * Nettoie une request_uri des paramètres var_xxx
705
 * 
706
 * Attention, la regexp doit suivre _CONTEXTE_IGNORE_VARIABLES défini au début de public/assembler.php
707
 * 
708
 * @param $request_uri
709
 * @return string
710
 */
711
function nettoyer_uri_var($request_uri) {
712
	$uri1 = $request_uri;
713
	do {
714
		$uri = $uri1;
715
		$uri1 = preg_replace(',([?&])(var_[^=&]*|PHPSESSID|fbclid|utm_[^=&]*)=[^&]*(&|$),i',
716
			'\1', $uri);
717
	} while ($uri <> $uri1);
718
	return preg_replace(',[?&]$,', '', $uri1);
719
}
720
721
722
/**
723
 * Donner l'URL de base d'un lien vers "soi-meme", modulo les trucs inutiles
724
 *
725
 * @param string $amp
726
 *    Style des esperluettes
727
 * @param bool $root
728
 * @return string
729
 *    URL vers soi-même
730
 **/
731
function self($amp = '&amp;', $root = false) {
732
	$url = nettoyer_uri();
733
	if (!$root
734
		and (
735
			// si pas de profondeur on peut tronquer
736
			$GLOBALS['profondeur_url'] < (_DIR_RESTREINT ? 1 : 2)
737
			// sinon c'est OK si _SET_HTML_BASE a ete force a false
738
			or (defined('_SET_HTML_BASE') and !_SET_HTML_BASE))
739
	) {
740
		$url = preg_replace(',^[^?]*/,', '', $url);
741
	}
742
	// ajouter le cas echeant les variables _POST['id_...']
743
	foreach ($_POST as $v => $c) {
744
		if (substr($v, 0, 3) == 'id_') {
745
			$url = parametre_url($url, $v, $c, '&');
746
		}
747
	}
748
749
	// supprimer les variables sans interet
750
	if (test_espace_prive()) {
751
		$url = preg_replace(',([?&])('
752
			. 'lang|show_docs|'
753
			. 'changer_lang|var_lang|action)=[^&]*,i', '\1', $url);
754
		$url = preg_replace(',([?&])[&]+,', '\1', $url);
755
		$url = preg_replace(',[&]$,', '\1', $url);
756
	}
757
758
	// eviter les hacks
759
	include_spip('inc/filtres_mini');
760
	$url = spip_htmlspecialchars($url);
0 ignored issues
show
Bug introduced by
It seems like $url can also be of type array<integer,string> or null; however, spip_htmlspecialchars() 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...
761
	
762
	$url = str_replace(array("'", '"', '<', '[', ']', ':'), array('%27', '%22', '%3C', '%5B', '%5D', '%3A'), $url);
763
764
	// &amp; ?
765
	if ($amp != '&amp;') {
766
		$url = str_replace('&amp;', $amp, $url);
767
	}
768
769
	// Si ca demarre par ? ou vide, donner './'
770
	$url = preg_replace(',^([?].*)?$,', './\1', $url);
771
772
	return $url;
773
}
774
775
776
/**
777
 * Indique si on est dans l'espace prive
778
 *
779
 * @return bool
780
 *     true si c'est le cas, false sinon.
781
 */
782
function test_espace_prive() {
783
	return defined('_ESPACE_PRIVE') ? _ESPACE_PRIVE : false;
784
}
785
786
/**
787
 * Vérifie la présence d'un plugin actif, identifié par son préfixe
788
 *
789
 * @param string $plugin
790
 * @return bool
791
 */
792
function test_plugin_actif($plugin) {
793
	return ($plugin and defined('_DIR_PLUGIN_' . strtoupper($plugin))) ? true : false;
794
}
795
796
/**
797
 * Traduction des textes de SPIP
798
 *
799
 * Traduit une clé de traduction en l'obtenant dans les fichiers de langues.
800
 *
801
 * @api
802
 * @uses inc_traduire_dist()
803
 * @uses _L()
804
 * @example
805
 *     ```
806
 *     _T('bouton_enregistrer')
807
 *     _T('medias:image_tourner_droite')
808
 *     _T('medias:erreurs', array('nb'=>3))
809
 *     _T("email_sujet", array('spip_lang'=>$lang_usager))
810
 *     ```
811
 *
812
 * @param string $texte
813
 *     Clé de traduction
814
 * @param array $args
815
 *     Couples (variable => valeur) pour passer des variables à la chaîne traduite. la variable spip_lang permet de forcer la langue
816
 * @param array $options
817
 *     - string class : nom d'une classe a ajouter sur un span pour encapsuler la chaine
818
 *     - bool force : forcer un retour meme si la chaine n'a pas de traduction
819
 *     - bool sanitize : nettoyer le html suspect dans les arguments
820
 * @return string
821
 *     Texte
822
 */
823
function _T($texte, $args = array(), $options = array()) {
824
	static $traduire = false;
825
	$o = array('class' => '', 'force' => true, 'sanitize' => true);
826
	if ($options) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $options of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
827
		// support de l'ancien argument $class
828
		if (is_string($options)) {
829
			$options = array('class' => $options);
830
		}
831
		$o = array_merge($o, $options);
832
	}
833
834
	if (!$traduire) {
835
		$traduire = charger_fonction('traduire', 'inc');
836
		include_spip('inc/lang');
837
	}
838
839
	// On peut passer explicitement la langue dans le tableau
840
	// On utilise le même nom de variable que la globale
841 View Code Duplication
	if (isset($args['spip_lang'])) {
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...
842
		$lang = $args['spip_lang'];
843
		// On l'enleve pour ne pas le passer au remplacement
844
		unset($args['spip_lang']);
845
	} // Sinon on prend la langue du contexte
846
	else {
847
		$lang = $GLOBALS['spip_lang'];
848
	}
849
	$text = $traduire($texte, $lang);
850
851
	if (!strlen($text)) {
852
		if (!$o['force']) {
853
			return '';
854
		}
855
856
		$text = $texte;
857
858
		// pour les chaines non traduites, assurer un service minimum
859
		if (!$GLOBALS['test_i18n'] and (_request('var_mode') != 'traduction')) {
860
			$text = str_replace('_', ' ',
861
				(($n = strpos($text, ':')) === false ? $texte :
862
					substr($texte, $n + 1)));
863
		}
864
		$o['class'] = null;
865
866
	}
867
868
	return _L($text, $args, $o);
869
870
}
871
872
873
/**
874
 * Remplace les variables `@...@` par leur valeur dans une chaîne de langue.
875
 *
876
 * Cette fonction est également appelée dans le code source de SPIP quand une
877
 * chaîne n'est pas encore dans les fichiers de langue.
878
 *
879
 * @see _T()
880
 * @example
881
 *     ```
882
 *     _L('Texte avec @nb@ ...', array('nb'=>3)
883
 *     ```
884
 *
885
 * @param string $text
886
 *     Texte
887
 * @param array $args
888
 *     Couples (variable => valeur) à transformer dans le texte
889
 * @param array $options
890
 *     - string class : nom d'une classe a ajouter sur un span pour encapsuler la chaine
891
 *     - bool sanitize : nettoyer le html suspect dans les arguments
892
 * @return string
893
 *     Texte
894
 */
895
function _L($text, $args = array(), $options = array()) {
896
	$f = $text;
897
	$defaut_options = array(
898
		'class' => null,
899
		'sanitize' => true,
900
	);
901
	// support de l'ancien argument $class
902
	if ($options and is_string($options)) {
903
		$options = array('class' => $options);
904
	}
905
	if (is_array($options)) {
906
		$options += $defaut_options;
907
	} else {
908
		$options = $defaut_options;
909
	}
910
911
	if (is_array($args) and count($args)) {
912
		if (!function_exists('interdire_scripts')) {
913
			include_spip('inc/texte');
914
		}
915
		if (!function_exists('echapper_html_suspect')) {
916
			include_spip('inc/texte_mini');
917
		}
918
		foreach ($args as $name => $value) {
919
			if (strpos($text, "@$name@") !== false) {
920
				if ($options['sanitize']) {
921
					$value = echapper_html_suspect($value);
922
					$value = interdire_scripts($value, -1);
923
				}
924
				if (!empty($options['class'])) {
925
					$value = "<span class='".$options['class']."'>$value</span>";
926
				}
927
				$text = str_replace("@$name@", $value, $text);
928
				unset($args[$name]);
929
			}
930
		}
931
		// Si des variables n'ont pas ete inserees, le signaler
932
		// (chaines de langues pas a jour)
933
		if ($args) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $args of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
934
			spip_log("$f:  variables inutilisees " . join(', ', array_keys($args)), _LOG_DEBUG);
935
		}
936
	}
937
938
	if (($GLOBALS['test_i18n'] or (_request('var_mode') == 'traduction')) and is_null($options['class'])) {
939
		return "<span class=debug-traduction-erreur>$text</span>";
940
	} else {
941
		return $text;
942
	}
943
}
944
945
946
/**
947
 * Retourne un joli chemin de répertoire
948
 *
949
 * Pour afficher `ecrire/action/` au lieu de `action/` dans les messages
950
 * ou `tmp/` au lieu de `../tmp/`
951
 *
952
 * @param stirng $rep Chemin d’un répertoire
953
 * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be string|string[]?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
954
 */
955
function joli_repertoire($rep) {
956
	$a = substr($rep, 0, 1);
957
	if ($a <> '.' and $a <> '/') {
958
		$rep = (_DIR_RESTREINT ? '' : _DIR_RESTREINT_ABS) . $rep;
959
	}
960
	$rep = preg_replace(',(^\.\.\/),', '', $rep);
961
962
	return $rep;
963
}
964
965
966
/**
967
 * Débute ou arrête un chronomètre et retourne sa valeur
968
 *
969
 * On exécute 2 fois la fonction, la première fois pour démarrer le chrono,
970
 * la seconde fois pour l’arrêter et récupérer la valeur
971
 *
972
 * @example
973
 *     ```
974
 *     spip_timer('papoter');
975
 *     // actions
976
 *     $duree = spip_timer('papoter');
977
 *     ```
978
 *
979
 * @param string $t
980
 *     Nom du chronomètre
981
 * @param bool $raw
982
 *     - false : retour en texte humainement lisible
983
 *     - true : retour en millisecondes
984
 * @return float|int|string|void
985
 */
986
function spip_timer($t = 'rien', $raw = false) {
987
	static $time;
988
	$a = time();
989
	$b = microtime();
990
	// microtime peut contenir les microsecondes et le temps
991
	$b = explode(' ', $b);
992
	if (count($b) == 2) {
993
		$a = end($b);
994
	} // plus precis !
995
	$b = reset($b);
996
	if (!isset($time[$t])) {
997
		$time[$t] = $a + $b;
998
	} else {
999
		$p = ($a + $b - $time[$t]) * 1000;
1000
		unset($time[$t]);
1001
#			echo "'$p'";exit;
1002
		if ($raw) {
1003
			return $p;
1004
		}
1005
		if ($p < 1000) {
1006
			$s = '';
1007
		} else {
1008
			$s = sprintf("%d ", $x = floor($p / 1000));
1009
			$p -= ($x * 1000);
1010
		}
1011
1012
		return $s . sprintf($s ? "%07.3f ms" : "%.3f ms", $p);
1013
	}
1014
}
1015
1016
1017
// Renvoie False si un fichier n'est pas plus vieux que $duree secondes,
1018
// sinon renvoie True et le date sauf si ca n'est pas souhaite
1019
// https://code.spip.net/@spip_touch
1020
function spip_touch($fichier, $duree = 0, $touch = true) {
1021
	if ($duree) {
1022
		clearstatcache();
1023
		if ((@$f = filemtime($fichier)) and ($f >= time() - $duree)) {
1024
			return false;
1025
		}
1026
	}
1027
	if ($touch !== false) {
1028
		if (!@touch($fichier)) {
1029
			spip_unlink($fichier);
1030
			@touch($fichier);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1031
		};
1032
		@chmod($fichier, _SPIP_CHMOD & ~0111);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1033
	}
1034
1035
	return true;
1036
}
1037
1038
1039
/**
1040
 * Action qui déclenche une tache de fond
1041
 *
1042
 * @see  queue_affichage_cron()
1043
 * @uses cron()
1044
 **/
1045
function action_cron() {
1046
	include_spip('inc/headers');
1047
	http_status(204); // No Content
1048
	header("Connection: close");
1049
	define('_DIRECT_CRON_FORCE', true);
1050
	cron();
1051
}
1052
1053
/**
1054
 * Exécution des tâches de fond
1055
 *
1056
 * @uses inc_genie_dist()
1057
 *
1058
 * @param array $taches
1059
 *     Tâches forcées
1060
 * @param array $taches_old
1061
 *     Tâches forcées, pour compat avec ancienne syntaxe
1062
 * @return bool
1063
 *     True si la tache a pu être effectuée
1064
 */
1065
function cron($taches = array(), $taches_old = array()) {
1066
	// si pas en mode cron force, laisser tomber.
1067
	if (!defined('_DIRECT_CRON_FORCE')) {
1068
		return false;
1069
	}
1070
	if (!is_array($taches)) {
1071
		$taches = $taches_old;
1072
	} // compat anciens appels
1073
	// si taches a inserer en base et base inaccessible, laisser tomber
1074
	// sinon on ne verifie pas la connexion tout de suite, car si ca se trouve
1075
	// queue_sleep_time_to_next_job() dira qu'il n'y a rien a faire
1076
	// et on evite d'ouvrir une connexion pour rien (utilisation de _DIRECT_CRON_FORCE dans mes_options.php)
1077
	if ($taches and count($taches) and !spip_connect()) {
1078
		return false;
1079
	}
1080
	spip_log("cron !", 'jq' . _LOG_DEBUG);
1081
	if ($genie = charger_fonction('genie', 'inc', true)) {
1082
		return $genie($taches);
1083
	}
1084
1085
	return false;
1086
}
1087
1088
/**
1089
 * Ajout d'une tache dans la file d'attente
1090
 *
1091
 * @param string $function
1092
 *     Le nom de la fonction PHP qui doit être appelée.
1093
 * @param string $description
1094
 *     Une description humainement compréhensible de ce que fait la tâche
1095
 *     (essentiellement pour l’affichage dans la page de suivi de l’espace privé)
1096
 * @param array $arguments
1097
 *     Facultatif, vide par défaut : les arguments qui seront passés à la fonction, sous forme de tableau PHP
1098
 * @param string $file
1099
 *     Facultatif, vide par défaut : nom du fichier à inclure, via `include_spip($file)`
1100
 *     exemple : `'inc/mail'` : il ne faut pas indiquer .php
1101
 *     Si le nom finit par un '/' alors on considère que c’est un répertoire et SPIP fera un `charger_fonction($function, $file)`
1102
 * @param bool $no_duplicate
1103
 *     Facultatif, `false` par défaut
1104
 *
1105
 *     - si `true` la tâche ne sera pas ajoutée si elle existe déjà en file d’attente avec la même fonction et les mêmes arguments.
1106
 *     - si `function_only` la tâche ne sera pas ajoutée si elle existe déjà en file d’attente avec la même fonction indépendamment de ses arguments
1107
 * @param int $time
1108
 *     Facultatif, `0` par défaut : indique la date sous forme de timestamp à laquelle la tâche doit être programmée.
1109
 *     Si `0` ou une date passée, la tâche sera exécutée aussitôt que possible (en général en fin hit, en asynchrone).
1110
 * @param int $priority
1111
 *     Facultatif, `0` par défaut : indique un niveau de priorité entre -10 et +10.
1112
 *     Les tâches sont exécutées par ordre de priorité décroissante, une fois leur date d’exécution passée. La priorité est surtout utilisée quand une tâche cron indique qu’elle n’a pas fini et doit être relancée : dans ce cas SPIP réduit sa priorité pour être sûr que celle tâche ne monopolise pas la file d’attente.
1113
 * @return int
1114
 *     Le numéro de travail ajouté ou `0` si aucun travail n’a été ajouté.
1115
 */
1116
function job_queue_add(
1117
	$function,
1118
	$description,
1119
	$arguments = array(),
1120
	$file = '',
1121
	$no_duplicate = false,
1122
	$time = 0,
1123
	$priority = 0
1124
) {
1125
	include_spip('inc/queue');
1126
1127
	return queue_add_job($function, $description, $arguments, $file, $no_duplicate, $time, $priority);
1128
}
1129
1130
/**
1131
 * Supprimer une tache de la file d'attente
1132
 *
1133
 * @param int $id_job
1134
 *  id of jonb to delete
1135
 * @return bool
0 ignored issues
show
Documentation introduced by
Should the return type not be boolean|string?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
1136
 */
1137
function job_queue_remove($id_job) {
1138
	include_spip('inc/queue');
1139
1140
	return queue_remove_job($id_job);
1141
}
1142
1143
/**
1144
 * Associer une tache a un/des objets de SPIP
1145
 *
1146
 * @param int $id_job
1147
 *     id of job to link
1148
 * @param array $objets
1149
 *     can be a simple array('objet'=>'article', 'id_objet'=>23)
1150
 *     or an array of simple array to link multiples objet in one time
1151
 */
1152
function job_queue_link($id_job, $objets) {
1153
	include_spip('inc/queue');
1154
1155
	return queue_link_job($id_job, $objets);
1156
}
1157
1158
1159
/**
1160
 * Renvoyer le temps de repos restant jusqu'au prochain job
1161
 *
1162
 * @staticvar int $queue_next_job_time
1163
 * @see queue_set_next_job_time()
1164
 * @param int|bool $force
0 ignored issues
show
Documentation introduced by
Should the type for parameter $force not be integer|boolean|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
1165
 *    Utilisée par `queue_set_next_job_time()` pour mettre à jour la valeur :
1166
 *
1167
 *    - si `true`, force la relecture depuis le fichier
1168
 *    - si int, affecte la static directement avec la valeur
1169
 * @return int
0 ignored issues
show
Documentation introduced by
Should the return type not be null|integer|double?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
1170
 *
1171
 *  - `0` si un job est à traiter
1172
 *  - `null` si la queue n'est pas encore initialisée
1173
 */
1174
function queue_sleep_time_to_next_job($force = null) {
1175
	static $queue_next_job_time = -1;
1176
	if ($force === true) {
1177
		$queue_next_job_time = -1;
1178
	} elseif ($force) {
1179
		$queue_next_job_time = $force;
1180
	}
1181
1182
	if ($queue_next_job_time == -1) {
1183
		if (!defined('_JQ_NEXT_JOB_TIME_FILENAME')) {
1184
			define('_JQ_NEXT_JOB_TIME_FILENAME', _DIR_TMP . "job_queue_next.txt");
1185
		}
1186
		// utiliser un cache memoire si dispo
1187
		if (function_exists("cache_get") and defined('_MEMOIZE_MEMORY') and _MEMOIZE_MEMORY) {
1188
			$queue_next_job_time = cache_get(_JQ_NEXT_JOB_TIME_FILENAME);
1189
		} else {
1190
			$queue_next_job_time = null;
1191
			if (lire_fichier(_JQ_NEXT_JOB_TIME_FILENAME, $contenu)) {
1192
				$queue_next_job_time = intval($contenu);
1193
			}
1194
		}
1195
	}
1196
1197
	if (is_null($queue_next_job_time)) {
1198
		return null;
1199
	}
1200
	if (!$_SERVER['REQUEST_TIME']) {
1201
		$_SERVER['REQUEST_TIME'] = time();
1202
	}
1203
1204
	return $queue_next_job_time - $_SERVER['REQUEST_TIME'];
1205
}
1206
1207
1208
/**
1209
 * Transformation XML des `&` en `&amp;`
1210
 * 
1211
 * @pipeline post_typo
1212
 * @param string $u
1213
 * @return string
1214
 */
1215
function quote_amp($u) {
1216
	return preg_replace(
1217
		"/&(?![a-z]{0,4}\w{2,3};|#x?[0-9a-f]{2,6};)/i",
1218
		"&amp;", $u);
1219
}
1220
1221
1222
/**
1223
 * Produit une balise `<script>` valide
1224
 *
1225
 * @example
1226
 *     ```
1227
 *     echo http_script('alert("ok");');
1228
 *     echo http_script('','js/jquery.js');
1229
 *     ```
1230
 *
1231
 * @param string $script
1232
 *     Code source du script
1233
 * @param string $src
1234
 *     Permet de faire appel à un fichier javascript distant
1235
 * @param string $noscript
1236
 *     Contenu de la balise  `<noscript>`
1237
 * @return string
1238
 *     Balise HTML `<script>` et son contenu
1239
 **/
1240
function http_script($script, $src = '', $noscript = '') {
1241
	static $done = array();
1242
1243
	if ($src && !isset($done[$src])) {
1244
		$done[$src] = true;
1245
		$src = find_in_path($src, _JAVASCRIPT);
1246
		$src = " src='$src'";
1247
	} else {
1248
		$src = '';
1249
	}
1250
	if ($script) {
1251
		$script = ("/*<![CDATA[*/\n" .
1252
			preg_replace(',</([^>]*)>,', '<\/\1>', $script) .
1253
			"/*]]>*/");
1254
	}
1255
	if ($noscript) {
1256
		$noscript = "<noscript>\n\t$noscript\n</noscript>\n";
1257
	}
1258
1259
	return ($src or $script or $noscript)
1260
		? "<script type='text/javascript'$src>$script</script>$noscript"
1261
		: '';
1262
}
1263
1264
1265
/**
1266
 * Sécurise du texte à écrire dans du PHP ou du Javascript.
1267
 *
1268
 * Transforme n'importe quel texte en une chaîne utilisable
1269
 * en PHP ou Javascript en toute sécurité, à l'intérieur d'apostrophes
1270
 * simples (`'` uniquement ; pas `"`)
1271
 *
1272
 * Utile particulièrement en filtre dans un squelettes
1273
 * pour écrire un contenu dans une variable JS ou PHP.
1274
 *
1275
 * Échappe les apostrophes (') du contenu transmis.
1276
 *
1277
 * @link https://www.spip.net/4281
1278
 * @example
1279
 *     PHP dans un squelette
1280
 *     ```
1281
 *     $x = '[(#TEXTE|texte_script)]';
1282
 *     ```
1283
 *
1284
 *     JS dans un squelette (transmettre une chaîne de langue)
1285
 *     ```
1286
 *     $x = '<:afficher_calendrier|texte_script:>';
1287
 *     ```
1288
 *
1289
 * @filtre
1290
 * @param string $texte
1291
 *     Texte à échapper
1292
 * @return string
1293
 *     Texte échappé
1294
 **/
1295
function texte_script($texte) {
1296
	return str_replace('\'', '\\\'', str_replace('\\', '\\\\', $texte));
1297
}
1298
1299
1300
/**
1301
 * Gestion des chemins (ou path) de recherche de fichiers par SPIP
1302
 *
1303
 * Empile de nouveaux chemins (à la suite de ceux déjà présents, mais avant
1304
 * le répertoire `squelettes` ou les dossiers squelettes), si un répertoire
1305
 * (ou liste de répertoires séparés par `:`) lui est passé en paramètre.
1306
 *
1307
 * Ainsi, si l'argument est de la forme `dir1:dir2:dir3`, ces 3 chemins sont placés
1308
 * en tête du path, dans cet ordre (hormis `squelettes` & la globale
1309
 * `$dossier_squelette` si définie qui resteront devant)
1310
 *
1311
 * Retourne dans tous les cas la liste des chemins.
1312
 *
1313
 * @note
1314
 *     Cette fonction est appelée à plusieurs endroits et crée une liste
1315
 *     de chemins finale à peu près de la sorte :
1316
 *
1317
 *     - dossiers squelettes (si globale précisée)
1318
 *     - squelettes/
1319
 *     - plugins (en fonction de leurs dépendances) : ceux qui dépendent
1320
 *       d'un plugin sont devant eux (ils peuvent surcharger leurs fichiers)
1321
 *     - racine du site
1322
 *     - squelettes-dist/
1323
 *     - prive/
1324
 *     - ecrire/
1325
 *
1326
 * @param string|array $dir_path
0 ignored issues
show
Documentation introduced by
Should the type for parameter $dir_path not be string|array|null? Also, consider making the array more specific, something like array<String>, or String[].

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive. In addition it looks for parameters that have the generic type array and suggests a stricter type like array<String>.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
1327
 *     - Répertoire(s) à empiler au path
1328
 *     - '' provoque un recalcul des chemins.
1329
 * @return array
0 ignored issues
show
Documentation introduced by
Should the return type not be array|null? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
1330
 *     Liste des chemins, par ordre de priorité.
1331
 **/
1332
function _chemin($dir_path = null) {
1333
	static $path_base = null;
1334
	static $path_full = null;
1335
	if ($path_base == null) {
1336
		// Chemin standard depuis l'espace public
1337
		$path = defined('_SPIP_PATH') ? _SPIP_PATH :
1338
			_DIR_RACINE . ':' .
1339
			_DIR_RACINE . 'squelettes-dist/:' .
1340
			_DIR_RACINE . 'prive/:' .
1341
			_DIR_RESTREINT;
1342
		// Ajouter squelettes/
1343
		if (@is_dir(_DIR_RACINE . 'squelettes')) {
1344
			$path = _DIR_RACINE . 'squelettes/:' . $path;
1345
		}
1346
		foreach (explode(':', $path) as $dir) {
1347
			if (strlen($dir) and substr($dir, -1) != '/') {
1348
				$dir .= "/";
1349
			}
1350
			$path_base[] = $dir;
1351
		}
1352
		$path_full = $path_base;
1353
		// Et le(s) dossier(s) des squelettes nommes
1354 View Code Duplication
		if (strlen($GLOBALS['dossier_squelettes'])) {
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...
1355
			foreach (array_reverse(explode(':', $GLOBALS['dossier_squelettes'])) as $d) {
1356
				array_unshift($path_full, ($d[0] == '/' ? '' : _DIR_RACINE) . $d . '/');
1357
			}
1358
		}
1359
		$GLOBALS['path_sig'] = md5(serialize($path_full));
1360
	}
1361
	if ($dir_path === null) {
1362
		return $path_full;
1363
	}
1364
1365
	if (is_array($dir_path) or strlen($dir_path)) {
1366
		$tete = "";
1367
		if (reset($path_base) == _DIR_RACINE . 'squelettes/') {
1368
			$tete = array_shift($path_base);
1369
		}
1370
		$dirs = (is_array($dir_path) ? $dir_path : explode(':', $dir_path));
1371
		$dirs = array_reverse($dirs);
1372
		foreach ($dirs as $dir_path) {
1373
			if (substr($dir_path, -1) != '/') {
1374
				$dir_path .= "/";
1375
			}
1376
			if (!in_array($dir_path, $path_base)) {
1377
				array_unshift($path_base, $dir_path);
1378
			}
1379
		}
1380
		if (strlen($tete)) {
1381
			array_unshift($path_base, $tete);
1382
		}
1383
	}
1384
	$path_full = $path_base;
1385
	// Et le(s) dossier(s) des squelettes nommes
1386 View Code Duplication
	if (strlen($GLOBALS['dossier_squelettes'])) {
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...
1387
		foreach (array_reverse(explode(':', $GLOBALS['dossier_squelettes'])) as $d) {
1388
			array_unshift($path_full, ((isset($d[0]) and $d[0] == '/') ? '' : _DIR_RACINE) . $d . '/');
1389
		}
1390
	}
1391
1392
	$GLOBALS['path_sig'] = md5(serialize($path_full));
1393
1394
	return $path_full;
1395
}
1396
1397
/**
1398
 * Retourne la liste des chemins connus de SPIP, dans l'ordre de priorité
1399
 *
1400
 * Recalcule la liste si le nom ou liste de dossier squelettes a changé.
1401
 *
1402
 * @uses _chemin()
1403
 *
1404
 * @return array Liste de chemins
0 ignored issues
show
Documentation introduced by
Should the return type not be array|null? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
1405
 **/
1406
function creer_chemin() {
1407
	$path_a = _chemin();
1408
	static $c = '';
1409
1410
	// on calcule le chemin si le dossier skel a change
1411
	if ($c != $GLOBALS['dossier_squelettes']) {
1412
		// assurer le non plantage lors de la montee de version :
1413
		$c = $GLOBALS['dossier_squelettes'];
1414
		$path_a = _chemin(''); // forcer un recalcul du chemin
1415
	}
1416
1417
	return $path_a;
1418
}
1419
1420
1421
function lister_themes_prives() {
1422
	static $themes = null;
1423
	if (is_null($themes)) {
1424
		// si pas encore definie
1425
		if (!defined('_SPIP_THEME_PRIVE')) {
1426
			define('_SPIP_THEME_PRIVE', 'spip');
1427
		}
1428
		$themes = array(_SPIP_THEME_PRIVE);
1429
		// lors d'une installation neuve, prefs n'est pas definie.
1430 View Code Duplication
		if (isset($GLOBALS['visiteur_session']['prefs'])) {
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...
1431
			$prefs = $GLOBALS['visiteur_session']['prefs'];
1432
		} else {
1433
			$prefs = array();
1434
		}
1435
		if (is_string($prefs)) {
1436
			$prefs = unserialize($GLOBALS['visiteur_session']['prefs']);
1437
		}
1438
		if (
1439
			((isset($prefs['theme']) and $theme = $prefs['theme'])
1440
				or (isset($GLOBALS['theme_prive_defaut']) and $theme = $GLOBALS['theme_prive_defaut']))
1441
			and $theme != _SPIP_THEME_PRIVE
1442
		) {
1443
			array_unshift($themes, $theme);
1444
		} // placer le theme choisi en tete
1445
	}
1446
1447
	return $themes;
1448
}
1449
1450
function find_in_theme($file, $subdir = '', $include = false) {
1451
	static $themefiles = array();
1452
	if (isset($themefiles["$subdir$file"])) {
1453
		return $themefiles["$subdir$file"];
1454
	}
1455
	// on peut fournir une icone generique -xx.svg qui fera le job dans toutes les tailles, et qui est prioritaire sur le png
1456
	// si il y a un .svg a la bonne taille (-16.svg) a cote, on l'utilise en remplacement du -16.png
1457
	if (preg_match(',-(\d+)[.](png|gif|svg)$,', $file, $m)
1458
	  and $file_svg_generique = substr($file,0, -strlen($m[0])) . "-xx.svg"
1459
		and $f = find_in_theme("$file_svg_generique")) {
1460
		if ($fsize = substr($f,0,-6) . $m[1] . ".svg" and file_exists($fsize)) {
1461
			return $themefiles["$subdir$file"] = $fsize;
1462
		}
1463
		else {
1464
			return $themefiles["$subdir$file"] = "$f?".$m[1]."px";
1465
		}
1466
	}
1467
1468
	$themes = lister_themes_prives();
1469
	foreach ($themes as $theme) {
1470
		if ($f = find_in_path($file, "prive/themes/$theme/$subdir", $include)) {
1471
			return $themefiles["$subdir$file"] = $f;
1472
		}
1473
	}
1474
	spip_log("$file introuvable dans le theme prive " . reset($themes), 'theme');
1475
1476
	return $themefiles["$subdir$file"] = "";
1477
}
1478
1479
1480
/**
1481
 * Cherche une image dans les dossiers d'images
1482
 *
1483
 * Cherche en priorité dans les thèmes d'image (prive/themes/X/images)
1484
 * et si la fonction n'en trouve pas, gère le renommage des icones (ex: 'supprimer' => 'del')
1485
 * de facon temporaire le temps de la migration, et cherche de nouveau.
1486
 *
1487
 * Si l'image n'est toujours pas trouvée, on la cherche dans les chemins,
1488
 * dans le répertoire défini par la constante `_NOM_IMG_PACK`
1489
 *
1490
 * @see find_in_theme()
1491
 * @see inc_icone_renommer_dist()
1492
 *
1493
 * @param string $icone
1494
 *     Nom de l'icone cherchée
1495
 * @return string
1496
 *     Chemin complet de l'icone depuis la racine si l'icone est trouée,
1497
 *     sinon chaîne vide.
1498
 **/
1499
function chemin_image($icone) {
1500
	static $icone_renommer;
1501
	if ($p = strpos($icone, '?')) {
1502
		$icone = substr($icone,0, $p);
1503
	}
1504
	// gerer le cas d'un double appel en evitant de refaire le travail inutilement
1505
	if (strpos($icone, "/") !== false and file_exists($icone)) {
1506
		return $icone;
1507
	}
1508
1509
	// si c'est un nom d'image complet (article-24.png) essayer de le renvoyer direct
1510
	if (preg_match(',[.](png|gif|jpg|webp|svg)$,', $icone) and $f = find_in_theme("images/$icone")) {
1511
		return $f;
1512
	}
1513
	// sinon passer par le module de renommage
1514
	if (is_null($icone_renommer)) {
1515
		$icone_renommer = charger_fonction('icone_renommer', 'inc', true);
1516
	}
1517
	if ($icone_renommer) {
1518
		list($icone, $fonction) = $icone_renommer($icone, "");
0 ignored issues
show
Unused Code introduced by
The assignment to $fonction is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
1519
		if (file_exists($icone)) {
1520
			return $icone;
1521
		}
1522
	}
1523
1524
	return find_in_path($icone, _NOM_IMG_PACK);
1525
}
1526
1527
//
1528
// chercher un fichier $file dans le SPIP_PATH
1529
// si on donne un sous-repertoire en 2e arg optionnel, il FAUT le / final
1530
// si 3e arg vrai, on inclut si ce n'est fait.
1531
$GLOBALS['path_sig'] = '';
1532
$GLOBALS['path_files'] = null;
1533
1534
/**
1535
 * Recherche un fichier dans les chemins de SPIP (squelettes, plugins, core)
1536
 *
1537
 * Retournera le premier fichier trouvé (ayant la plus haute priorité donc),
1538
 * suivant l'ordre des chemins connus de SPIP.
1539
 *
1540
 * @api
1541
 * @see  charger_fonction()
1542
 * @uses creer_chemin() Pour la liste des chemins.
1543
 * @example
1544
 *     ```
1545
 *     $f = find_in_path('css/perso.css');
1546
 *     $f = find_in_path('perso.css', 'css');
1547
 *     ```
1548
 *
1549
 * @param string $file
1550
 *     Fichier recherché
1551
 * @param string $dirname
1552
 *     Répertoire éventuel de recherche (est aussi extrait automatiquement de $file)
1553
 * @param bool|string $include
1554
 *     - false : ne fait rien de plus
1555
 *     - true : inclut le fichier (include_once)
1556
 *     - 'require' : idem, mais tue le script avec une erreur si le fichier n'est pas trouvé.
1557
 * @return string|bool
1558
 *     - string : chemin du fichier trouvé
1559
 *     - false : fichier introuvable
1560
 **/
1561
function find_in_path($file, $dirname = '', $include = false) {
1562
	static $dirs = array();
1563
	static $inc = array(); # cf https://git.spip.net/spip/spip/commit/42e4e028e38c839121efaee84308d08aee307eec
1564
	static $c = '';
1565
1566
	if (!$file and !strlen($file)) {
1567
		return false;
1568
	}
1569
1570
	// on calcule le chemin si le dossier skel a change
1571
	if ($c != $GLOBALS['dossier_squelettes']) {
1572
		// assurer le non plantage lors de la montee de version :
1573
		$c = $GLOBALS['dossier_squelettes'];
1574
		creer_chemin(); // forcer un recalcul du chemin et la mise a jour de path_sig
1575
	}
1576
1577
	if (isset($GLOBALS['path_files'][$GLOBALS['path_sig']][$dirname][$file])) {
1578
		if (!$GLOBALS['path_files'][$GLOBALS['path_sig']][$dirname][$file]) {
1579
			return false;
1580
		}
1581
		if ($include and !isset($inc[$dirname][$file])) {
1582
			include_once _ROOT_CWD . $GLOBALS['path_files'][$GLOBALS['path_sig']][$dirname][$file];
1583
			$inc[$dirname][$file] = $inc[''][$dirname . $file] = true;
1584
		}
1585
1586
		return $GLOBALS['path_files'][$GLOBALS['path_sig']][$dirname][$file];
1587
	}
1588
1589
	$a = strrpos($file, '/');
1590
	if ($a !== false) {
1591
		$dirname .= substr($file, 0, ++$a);
1592
		$file = substr($file, $a);
1593
	}
1594
1595
	foreach (creer_chemin() as $dir) {
0 ignored issues
show
Bug introduced by
The expression creer_chemin() of type array|null is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
1596
		if (!isset($dirs[$a = $dir . $dirname])) {
1597
			$dirs[$a] = (is_dir(_ROOT_CWD . $a) || !$a);
1598
		}
1599
		if ($dirs[$a]) {
1600
			if (file_exists(_ROOT_CWD . ($a .= $file))) {
1601
				if ($include and !isset($inc[$dirname][$file])) {
1602
					include_once _ROOT_CWD . $a;
1603
					$inc[$dirname][$file] = $inc[''][$dirname . $file] = true;
1604
				}
1605 View Code Duplication
				if (!defined('_SAUVER_CHEMIN')) {
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...
1606
					// si le chemin n'a pas encore ete charge, ne pas lever le flag, ne pas cacher
1607
					if (is_null($GLOBALS['path_files'])) {
1608
						return $a;
1609
					}
1610
					define('_SAUVER_CHEMIN', true);
1611
				}
1612
1613
				return $GLOBALS['path_files'][$GLOBALS['path_sig']][$dirname][$file] = $GLOBALS['path_files'][$GLOBALS['path_sig']][''][$dirname . $file] = $a;
1614
			}
1615
		}
1616
	}
1617
1618
	if ($include) {
1619
		spip_log("include_spip $dirname$file non trouve");
1620
		if ($include === 'required') {
1621
			echo '<pre>',
1622
			"<strong>Erreur Fatale</strong><br />";
1623
			if (function_exists('debug_print_backtrace')) {
1624
				echo debug_print_backtrace();
1625
			}
1626
			echo '</pre>';
1627
			die("Erreur interne: ne peut inclure $dirname$file");
1628
		}
1629
	}
1630
1631 View Code Duplication
	if (!defined('_SAUVER_CHEMIN')) {
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...
1632
		// si le chemin n'a pas encore ete charge, ne pas lever le flag, ne pas cacher
1633
		if (is_null($GLOBALS['path_files'])) {
1634
			return false;
1635
		}
1636
		define('_SAUVER_CHEMIN', true);
1637
	}
1638
1639
	return $GLOBALS['path_files'][$GLOBALS['path_sig']][$dirname][$file] = $GLOBALS['path_files'][$GLOBALS['path_sig']][''][$dirname . $file] = false;
1640
}
1641
1642
function clear_path_cache() {
1643
	$GLOBALS['path_files'] = array();
1644
	spip_unlink(_CACHE_CHEMIN);
1645
}
1646
1647
function load_path_cache() {
1648
	// charger le path des plugins
1649
	if (@is_readable(_CACHE_PLUGINS_PATH)) {
1650
		include_once(_CACHE_PLUGINS_PATH);
1651
	}
1652
	$GLOBALS['path_files'] = array();
1653
	// si le visiteur est admin,
1654
	// on ne recharge pas le cache pour forcer sa mise a jour
1655
	if (
1656
		// la session n'est pas encore chargee a ce moment, on ne peut donc pas s'y fier
1657
		//AND (!isset($GLOBALS['visiteur_session']['statut']) OR $GLOBALS['visiteur_session']['statut']!='0minirezo')
1658
		// utiliser le cookie est un pis aller qui marche 'en general'
1659
		// on blinde par un second test au moment de la lecture de la session
1660
		// !isset($_COOKIE[$GLOBALS['cookie_prefix'].'_admin'])
1661
		// et en ignorant ce cache en cas de recalcul explicite
1662
	!_request('var_mode')
1663
	) {
1664
		// on essaye de lire directement sans verrou pour aller plus vite
1665
		if ($contenu = spip_file_get_contents(_CACHE_CHEMIN)) {
1666
			// mais si semble corrompu on relit avec un verrou
1667
			if (!$GLOBALS['path_files'] = unserialize($contenu)) {
1668
				lire_fichier(_CACHE_CHEMIN, $contenu);
1669
				if (!$GLOBALS['path_files'] = unserialize($contenu)) {
1670
					$GLOBALS['path_files'] = array();
1671
				}
1672
			}
1673
		}
1674
	}
1675
}
1676
1677
function save_path_cache() {
1678
	if (defined('_SAUVER_CHEMIN')
1679
		and _SAUVER_CHEMIN
1680
	) {
1681
		ecrire_fichier(_CACHE_CHEMIN, serialize($GLOBALS['path_files']));
1682
	}
1683
}
1684
1685
1686
/**
1687
 * Trouve tous les fichiers du path correspondants à un pattern
1688
 *
1689
 * Pour un nom de fichier donné, ne retourne que le premier qui sera trouvé
1690
 * par un `find_in_path()`
1691
 *
1692
 * @api
1693
 * @uses creer_chemin()
1694
 * @uses preg_files()
1695
 *
1696
 * @param string $dir
1697
 * @param string $pattern
1698
 * @param bool $recurs
1699
 * @return array
1700
 */
1701
function find_all_in_path($dir, $pattern, $recurs = false) {
1702
	$liste_fichiers = array();
1703
	$maxfiles = 10000;
1704
1705
	// cas borderline si dans mes_options on appelle redirige_par_entete qui utilise _T et charge un fichier de langue
1706
	// on a pas encore inclus flock.php
1707
	if (!function_exists('preg_files')) {
1708
		include_once _ROOT_RESTREINT . 'inc/flock.php';
1709
	}
1710
1711
	// Parcourir le chemin
1712
	foreach (creer_chemin() as $d) {
0 ignored issues
show
Bug introduced by
The expression creer_chemin() of type array|null is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
1713
		$f = $d . $dir;
1714
		if (@is_dir($f)) {
1715
			$liste = preg_files($f, $pattern, $maxfiles - count($liste_fichiers), $recurs === true ? array() : $recurs);
0 ignored issues
show
Bug introduced by
It seems like $recurs === true ? array() : $recurs can also be of type boolean; however, preg_files() does only seem to accept array, 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...
1716
			foreach ($liste as $chemin) {
1717
				$nom = basename($chemin);
1718
				// ne prendre que les fichiers pas deja trouves
1719
				// car find_in_path prend le premier qu'il trouve,
1720
				// les autres sont donc masques
1721
				if (!isset($liste_fichiers[$nom])) {
1722
					$liste_fichiers[$nom] = $chemin;
1723
				}
1724
			}
1725
		}
1726
	}
1727
1728
	return $liste_fichiers;
1729
}
1730
1731
/**
1732
 * Prédicat sur les scripts de ecrire qui n'authentifient pas par cookie
1733
 * et beneficient d'une exception
1734
 *
1735
 * @param string $nom
1736
 * @param bool $strict
1737
 * @return bool
1738
 */
1739
function autoriser_sans_cookie($nom, $strict = false) {
1740
	static $autsanscookie = array('install', 'base_repair');
1741
1742
	if (in_array($nom, $autsanscookie)) {
1743
		if (test_espace_prive()){
1744
			include_spip('base/connect_sql');
1745
			if (!$strict or !spip_connect()){
1746
				return true;
1747
			}
1748
		}
1749
	}
1750
	return false;
1751
}
1752
1753
/**
1754
 * Fonction codant et décodant les URLs des objets SQL mis en page par SPIP
1755
 *
1756
 * @api
1757
 * @param string $id
1758
 *   numero de la cle primaire si nombre, URL a decoder si pas numerique
1759
 * @param string $entite
1760
 *   surnom de la table SQL (donne acces au nom de cle primaire)
1761
 * @param string $args
1762
 *   query_string a placer apres cle=$id&....
1763
 * @param string $ancre
1764
 *   ancre a mettre a la fin de l'URL a produire
1765
 * @param bool|string $public
0 ignored issues
show
Documentation introduced by
Should the type for parameter $public not be boolean|string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
1766
 *   produire l'URL publique ou privee (par defaut: selon espace)
1767
 *   si string : serveur de base de donnee (nom du connect)
1768
 * @param string $type
0 ignored issues
show
Documentation introduced by
Should the type for parameter $type not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
1769
 *   fichier dans le repertoire ecrire/urls determinant l'apparence
1770
 * @return string|array
1771
 *   url codee ou fonction de decodage
1772
 *   array : derogatoire, la fonction d'url retourne (objet,id_objet) utilises par nettoyer_raccourcis_typo() pour generer un lien titre
1773
 *           (cas des raccourcis personalises [->spip20] : il faut implementer une fonction generer_url_spip et une fonction generer_url_ecrire_spip)
1774
 */
1775
function generer_url_entite($id = '', $entite = '', $args = '', $ancre = '', $public = null, $type = null) {
1776
	if ($public === null) {
1777
		$public = !test_espace_prive();
1778
	}
1779
	$entite = objet_type($entite); // cas particulier d'appels sur objet/id_objet...
1780
1781
	if (!$public) {
1782
		if (!$entite) {
1783
			return '';
1784
		}
1785
		if (!function_exists('generer_url_ecrire_objet')) {
1786
			include_spip('inc/urls');
1787
		}
1788
		$res = generer_url_ecrire_objet($entite, $id, $args, $ancre, false);
1789
	} else {
1790
		if ($type === null) {
1791
			$type = (isset($GLOBALS['type_urls']))
1792
				? $GLOBALS['type_urls'] // pour surcharge via fichier d'options
1793
				: ((isset($GLOBALS['meta']['type_urls'])) // sinon la config url_etendues
1794
					? ($GLOBALS['meta']['type_urls']) : "page"); // sinon type "page" par défaut
1795
		}
1796
1797
		$f = charger_fonction($type, 'urls', true);
1798
		// se rabattre sur les urls page si les urls perso non dispo
1799
		if (!$f) {
1800
			$f = charger_fonction('page', 'urls', true);
1801
		}
1802
1803
		// si $entite='', on veut la fonction de passage URL ==> id
1804
		// sinon on veut effectuer le passage id ==> URL
1805
		if (!$entite) {
1806
			return $f;
1807
		}
1808
1809
		// mais d'abord il faut tester le cas des urls sur une
1810
		// base distante
1811
		if (is_string($public)
1812
			and $g = charger_fonction('connect', 'urls', true)
1813
		) {
1814
			$f = $g;
1815
		}
1816
1817
		$res = $f(intval($id), $entite, $args, $ancre, $public);
1818
1819
	}
1820
	if ($res) {
1821
		return $res;
1822
	}
1823
	// Sinon c'est un raccourci ou compat SPIP < 2
1824
	if (!function_exists($f = 'generer_url_' . $entite)) {
1825
		if (!function_exists($f .= '_dist')) {
1826
			$f = '';
1827
		}
1828
	}
1829
	if ($f) {
1830
		$url = $f($id, $args, $ancre);
1831
		if (strlen($args)) {
1832
			$url .= strstr($url, '?')
1833
				? '&amp;' . $args
1834
				: '?' . $args;
1835
		}
1836
1837
		return $url;
1838
	}
1839
	// On a ete gentil mais la ....
1840
	spip_log("generer_url_entite: entite $entite ($f) inconnue $type $public");
1841
1842
	return '';
1843
}
1844
1845
function generer_url_ecrire_entite_edit($id, $entite, $args = '', $ancre = '') {
1846
	$exec = objet_info($entite, 'url_edit');
1847
	$url = generer_url_ecrire($exec, $args);
0 ignored issues
show
Bug introduced by
It seems like $exec defined by objet_info($entite, 'url_edit') on line 1846 can also be of type array; however, generer_url_ecrire() 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...
1848
	if (intval($id)) {
1849
		$url = parametre_url($url, id_table_objet($entite), $id);
1850
	} else {
1851
		$url = parametre_url($url, 'new', 'oui');
1852
	}
1853
	if ($ancre) {
1854
		$url = ancre_url($url, $ancre);
0 ignored issues
show
Bug introduced by
It seems like $url can also be of type array<integer,string> or null; however, ancre_url() 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...
1855
	}
1856
1857
	return $url;
1858
}
1859
1860
// https://code.spip.net/@urls_connect_dist
1861
function urls_connect_dist($i, &$entite, $args = '', $ancre = '', $public = null) {
1862
	include_spip('base/connect_sql');
1863
	$id_type = id_table_objet($entite, $public);
1864
1865
	return _DIR_RACINE . get_spip_script('./')
1866
	. "?" . _SPIP_PAGE . "=$entite&$id_type=$i&connect=$public"
1867
	. (!$args ? '' : "&$args")
1868
	. (!$ancre ? '' : "#$ancre");
1869
}
1870
1871
1872
/**
1873
 * Transformer les caractères utf8 d'une URL (farsi par exemple) selon la RFC 1738
1874
 *
1875
 * @param string $url
1876
 * @return string
1877
 */
1878
function urlencode_1738($url) {
1879
	if (preg_match(',[^\x00-\x7E],sS', $url)) {
1880
		$uri = '';
1881
		for ($i = 0; $i < strlen($url); $i++) {
1882
			if (ord($a = $url[$i]) > 127) {
1883
				$a = rawurlencode($a);
1884
			}
1885
			$uri .= $a;
1886
		}
1887
		$url = $uri;
1888
	}
1889
1890
	return quote_amp($url);
1891
}
1892
1893
// https://code.spip.net/@generer_url_entite_absolue
1894
function generer_url_entite_absolue($id = '', $entite = '', $args = '', $ancre = '', $connect = null) {
1895
	if (!$connect) {
1896
		$connect = true;
1897
	}
1898
	$h = generer_url_entite($id, $entite, $args, $ancre, $connect);
1899
	if (!preg_match(',^\w+:,', $h)) {
1900
		include_spip('inc/filtres_mini');
1901
		$h = url_absolue($h);
0 ignored issues
show
Bug introduced by
It seems like $h can also be of type array; however, url_absolue() 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...
1902
	}
1903
1904
	return $h;
1905
}
1906
1907
1908
/**
1909
 * Tester qu'une variable d'environnement est active
1910
 *
1911
 * Sur certains serveurs, la valeur 'Off' tient lieu de false dans certaines
1912
 * variables d'environnement comme `$_SERVER['HTTPS']` ou `ini_get('display_errors')`
1913
 *
1914
 * @param string|bool $truc
1915
 *     La valeur de la variable d'environnement
1916
 * @return bool
1917
 *     true si la valeur est considérée active ; false sinon.
1918
 **/
1919
function test_valeur_serveur($truc) {
1920
	if (!$truc) {
1921
		return false;
1922
	}
1923
1924
	return (strtolower($truc) !== 'off');
1925
}
1926
1927
//
1928
// Fonctions de fabrication des URL des scripts de Spip
1929
//
1930
/**
1931
 * Calcule l'url de base du site
1932
 *
1933
 * Calcule l'URL de base du site, en priorité sans se fier à la méta (adresse_site) qui
1934
 * peut être fausse (sites avec plusieurs noms d’hôtes, déplacements, erreurs).
1935
 * En dernier recours, lorsqu'on ne trouve rien, on utilise adresse_site comme fallback.
1936
 *
1937
 * @note
1938
 *     La globale `$profondeur_url` doit être initialisée de manière à
1939
 *     indiquer le nombre de sous-répertoires de l'url courante par rapport à la
1940
 *     racine de SPIP : par exemple, sur ecrire/ elle vaut 1, sur sedna/ 1, et à
1941
 *     la racine 0. Sur url/perso/ elle vaut 2
1942
 *
1943
 * @param int|boo|array $profondeur
0 ignored issues
show
Documentation introduced by
Should the type for parameter $profondeur not be integer|boo|array|null? Also, consider making the array more specific, something like array<String>, or String[].

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive. In addition it looks for parameters that have the generic type array and suggests a stricter type like array<String>.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
1944
 *    - si non renseignée : retourne l'url pour la profondeur $GLOBALS['profondeur_url']
1945
 *    - si int : indique que l'on veut l'url pour la profondeur indiquée
1946
 *    - si bool : retourne le tableau static complet
1947
 *    - si array : réinitialise le tableau static complet avec la valeur fournie
1948
 * @return string|array
1949
 */
1950
function url_de_base($profondeur = null) {
1951
1952
	static $url = array();
1953
	if (is_array($profondeur)) {
1954
		return $url = $profondeur;
1955
	}
1956
	if ($profondeur === false) {
1957
		return $url;
1958
	}
1959
1960
	if (is_null($profondeur)) {
1961
		$profondeur = $GLOBALS['profondeur_url'];
1962
	}
1963
1964
	if (isset($url[$profondeur])) {
1965
		return $url[$profondeur];
1966
	}
1967
1968
	$http = 'http';
1969
1970
	if (
1971
		isset($_SERVER["SCRIPT_URI"])
1972
		and substr($_SERVER["SCRIPT_URI"], 0, 5) == 'https'
1973
	) {
1974
		$http = 'https';
1975
	} elseif (
1976
		isset($_SERVER['HTTPS'])
1977
		and test_valeur_serveur($_SERVER['HTTPS'])
1978
	) {
1979
		$http = 'https';
1980
	}
1981
1982
	// note : HTTP_HOST contient le :port si necessaire
1983
	$host = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : null;
1984
	// si on n'a pas trouvé d'hôte du tout, en dernier recours on utilise adresse_site comme fallback
1985
	if (is_null($host) and isset($GLOBALS['meta']['adresse_site'])) {
1986
		$host = $GLOBALS['meta']['adresse_site'];
1987
		if ($scheme = parse_url($host, PHP_URL_SCHEME)) {
1988
			$http = $scheme;
1989
			$host = str_replace("{$scheme}://", '', $host);
1990
		}
1991
	}
1992
	if (isset($_SERVER['SERVER_PORT'])
1993
		and $port = $_SERVER['SERVER_PORT']
1994
		and strpos($host, ":") == false
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing strpos($host, ':') of type integer to the boolean false. If you are specifically checking for 0, consider using something more explicit like === 0 instead.
Loading history...
1995
	) {
1996
		if (!defined('_PORT_HTTP_STANDARD')) {
1997
			define('_PORT_HTTP_STANDARD', '80');
1998
		}
1999
		if (!defined('_PORT_HTTPS_STANDARD')) {
2000
			define('_PORT_HTTPS_STANDARD', '443');
2001
		}
2002
		if ($http == "http" and !in_array($port, explode(',', _PORT_HTTP_STANDARD))) {
2003
			$host .= ":$port";
2004
		}
2005
		if ($http == "https" and !in_array($port, explode(',', _PORT_HTTPS_STANDARD))) {
2006
			$host .= ":$port";
2007
		}
2008
	}
2009
2010 View Code Duplication
	if (!$GLOBALS['REQUEST_URI']) {
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...
2011
		if (isset($_SERVER['REQUEST_URI'])) {
2012
			$GLOBALS['REQUEST_URI'] = $_SERVER['REQUEST_URI'];
2013
		} else {
2014
			$GLOBALS['REQUEST_URI'] = (php_sapi_name() !== 'cli') ? $_SERVER['PHP_SELF'] : '';
2015
			if (!empty($_SERVER['QUERY_STRING'])
2016
				and !strpos($_SERVER['REQUEST_URI'], '?')
2017
			) {
2018
				$GLOBALS['REQUEST_URI'] .= '?' . $_SERVER['QUERY_STRING'];
2019
			}
2020
		}
2021
	}
2022
2023
	$url[$profondeur] = url_de_($http, $host, $GLOBALS['REQUEST_URI'], $profondeur);
2024
2025
	return $url[$profondeur];
2026
}
2027
2028
/**
2029
 * fonction testable de construction d'une url appelee par url_de_base()
2030
 *
2031
 * @param string $http
2032
 * @param string $host
2033
 * @param string $request
2034
 * @param int $prof
2035
 * @return string
2036
 */
2037
function url_de_($http, $host, $request, $prof = 0) {
2038
	$prof = max($prof, 0);
2039
2040
	$myself = ltrim($request, '/');
2041
	# supprimer la chaine de GET
2042
	list($myself) = explode('?', $myself);
2043
	// vieux mode HTTP qui envoie après le nom de la methode l'URL compléte
2044
	// protocole, "://", nom du serveur avant le path dans _SERVER["REQUEST_URI"]
2045 View Code Duplication
	if (strpos($myself,'://') !== false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
2046
		$myself = explode('://',$myself);
2047
		array_shift($myself);
2048
		$myself = implode('://',$myself);
2049
		$myself = explode('/',$myself);
2050
		array_shift($myself);
2051
		$myself = implode('/',$myself);
2052
	}
2053
	$url = join('/', array_slice(explode('/', $myself), 0, -1 - $prof)) . '/';
2054
2055
	$url = $http . '://' . rtrim($host, '/') . '/' . ltrim($url, '/');
2056
2057
	return $url;
2058
}
2059
2060
2061
// Pour une redirection, la liste des arguments doit etre separee par "&"
2062
// Pour du code XHTML, ca doit etre &amp;
2063
// Bravo au W3C qui n'a pas ete capable de nous eviter ca
2064
// faute de separer proprement langage et meta-langage
2065
2066
// Attention, X?y=z et "X/?y=z" sont completement differents!
2067
// http://httpd.apache.org/docs/2.0/mod/mod_dir.html
2068
2069
/**
2070
 * Crée une URL vers un script de l'espace privé
2071
 *
2072
 * @example
2073
 *     ```
2074
 *     generer_url_ecrire('admin_plugin')
2075
 *     ```
2076
 *
2077
 * @param string $script
2078
 *     Nom de la page privée (xx dans exec=xx)
2079
 * @param string $args
2080
 *     Arguments à transmettre, tel que `arg1=yy&arg2=zz`
2081
 * @param bool $no_entities
2082
 *     Si false : transforme les `&` en `&amp;`
2083
 * @param bool|string $rel
2084
 *     URL relative ?
2085
 *
2086
 *     - false : l’URL sera complète et contiendra l’URL du site
2087
 *     - true : l’URL sera relavive.
2088
 *     - string : on transmet l'url à la fonction
2089
 * @return string URL
2090
 **/
2091
function generer_url_ecrire($script = '', $args = "", $no_entities = false, $rel = false) {
2092
	if (!$rel) {
2093
		$rel = url_de_base() . _DIR_RESTREINT_ABS . _SPIP_ECRIRE_SCRIPT;
2094
	} else {
2095
		if (!is_string($rel)) {
2096
			$rel = _DIR_RESTREINT ? _DIR_RESTREINT :
2097
				('./' . _SPIP_ECRIRE_SCRIPT);
2098
		}
2099
	}
2100
2101
	list($script, $ancre) = array_pad(explode('#', $script), 2, null);
2102
	if ($script and ($script <> 'accueil' or $rel)) {
2103
		$args = "?exec=$script" . (!$args ? '' : "&$args");
2104
	} elseif ($args) {
2105
		$args = "?$args";
2106
	}
2107
	if ($ancre) {
2108
		$args .= "#$ancre";
2109
	}
2110
2111
	return $rel . ($no_entities ? $args : str_replace('&', '&amp;', $args));
2112
}
2113
2114
//
2115
// Adresse des scripts publics (a passer dans inc-urls...)
2116
//
2117
2118
2119
/**
2120
 * Retourne le nom du fichier d'exécution de SPIP
2121
 *
2122
 * @see _SPIP_SCRIPT
2123
 * @note
2124
 *   Detecter le fichier de base, a la racine, comme etant spip.php ou ''
2125
 *   dans le cas de '', un $default = './' peut servir (comme dans urls/page.php)
2126
 *
2127
 * @param string $default
2128
 *     Script par défaut
2129
 * @return string
2130
 *     Nom du fichier (constante _SPIP_SCRIPT), sinon nom par défaut
2131
 **/
2132
function get_spip_script($default = '') {
2133
	# cas define('_SPIP_SCRIPT', '');
2134
	if (_SPIP_SCRIPT) {
2135
		return _SPIP_SCRIPT;
2136
	} else {
2137
		return $default;
2138
	}
2139
}
2140
2141
/**
2142
 * Crée une URL vers une page publique de SPIP
2143
 *
2144
 * @example
2145
 *     ```
2146
 *     generer_url_public("rubrique","id_rubrique=$id_rubrique")
2147
 *     ```
2148
 *
2149
 * @param string $script
2150
 *     Nom de la page
2151
 * @param string|array $args
2152
 *     Arguments à transmettre a l'URL,
2153
 *      soit sous la forme d'un string tel que `arg1=yy&arg2=zz`
2154
 *      soit sous la forme d'un array tel que array( `arg1` => `yy`, `arg2` => `zz` )
2155
 * @param bool $no_entities
2156
 *     Si false : transforme les `&` en `&amp;`
2157
 * @param bool $rel
2158
 *     URL relative ?
2159
 *
2160
 *     - false : l’URL sera complète et contiendra l’URL du site
2161
 *     - true : l’URL sera relavive.
2162
 * @param string $action
2163
 *     - Fichier d'exécution public (spip.php par défaut)
2164
 * @return string URL
2165
 **/
2166
function generer_url_public($script = '', $args = "", $no_entities = false, $rel = true, $action = '') {
2167
	// si le script est une action (spip_pass, spip_inscription),
2168
	// standardiser vers la nouvelle API
2169
2170
	if (!$action) {
2171
		$action = get_spip_script();
2172
	}
2173
	if ($script) {
2174
		$action = parametre_url($action, _SPIP_PAGE, $script, '&');
2175
	}
2176
2177
	if ($args) {
2178
		if (is_array($args)) {
2179
			$r = '';
2180
			foreach ($args as $k => $v) {
2181
				$r .= '&' . $k . '=' . $v;
2182
			}
2183
			$args = substr($r, 1);
2184
		}
2185
		$action .=
2186
			(strpos($action, '?') !== false ? '&' : '?') . $args;
2187
	}
2188
	if (!$no_entities) {
2189
		$action = quote_amp($action);
0 ignored issues
show
Bug introduced by
It seems like $action can also be of type array<integer,string> or null; however, quote_amp() 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...
2190
	}
2191
2192
	// ne pas generer une url avec /./?page= en cas d'url absolue et de _SPIP_SCRIPT vide
2193
	return ($rel ? _DIR_RACINE . $action : rtrim(url_de_base(), '/') . preg_replace(",^/[.]/,", "/", "/$action"));
2194
}
2195
2196
// https://code.spip.net/@generer_url_prive
2197
function generer_url_prive($script, $args = "", $no_entities = false) {
2198
2199
	return generer_url_public($script, $args, $no_entities, false, _DIR_RESTREINT_ABS . 'prive.php');
2200
}
2201
2202
// Pour les formulaires en methode POST,
2203
// mettre le nom du script a la fois en input-hidden et dans le champ action:
2204
// 1) on peut ainsi memoriser le signet comme si c'etait un GET
2205
// 2) ca suit http://en.wikipedia.org/wiki/Representational_State_Transfer
2206
2207
/**
2208
 * Retourne un formulaire (POST par défaut) vers un script exec
2209
 * de l’interface privée
2210
 *
2211
 * @param string $script
2212
 *     Nom de la page exec
2213
 * @param string $corps
2214
 *     Contenu du formulaire
2215
 * @param string $atts
2216
 *     Si présent, remplace les arguments par défaut (method=post) par ceux indiqués
2217
 * @param string $submit
2218
 *     Si indiqué, un bouton de soumission est créé avec texte sa valeur.
2219
 * @return string
2220
 *     Code HTML du formulaire
2221
 **/
2222
function generer_form_ecrire($script, $corps, $atts = '', $submit = '') {
2223
2224
	$script1 = explode('&', $script);
2225
	$script1 = reset($script1);
2226
2227
	return "<form action='"
2228
	. ($script ? generer_url_ecrire($script) : '')
2229
	. "' "
2230
	. ($atts ? $atts : " method='post'")
2231
	. "><div>\n"
2232
	. "<input type='hidden' name='exec' value='$script1' />"
2233
	. $corps
2234
	. (!$submit ? '' :
2235
		("<div style='text-align: " . $GLOBALS['spip_lang_right'] . "'><input class='fondo submit btn' type='submit' value=\"" . entites_html($submit) . "\" /></div>"))
2236
	. "</div></form>\n";
2237
}
2238
2239
/**
2240
 * Générer un formulaire pour lancer une action vers $script
2241
 *
2242
 * Attention, JS/Ajax n'aime pas le melange de param GET/POST
2243
 * On n'applique pas la recommandation ci-dessus pour les scripts publics
2244
 * qui ne sont pas destines a etre mis en signets
2245
 *
2246
 * @param string $script
2247
 * @param string $corps
2248
 * @param string $atts
2249
 * @param bool $public
2250
 * @return string
2251
 */
2252
function generer_form_action($script, $corps, $atts = '', $public = false) {
2253
	// si l'on est dans l'espace prive, on garde dans l'url
2254
	// l'exec a l'origine de l'action, qui permet de savoir si il est necessaire
2255
	// ou non de proceder a l'authentification (cas typique de l'install par exemple)
2256
	$h = (_DIR_RACINE and !$public)
2257
		? generer_url_ecrire(_request('exec'))
2258
		: generer_url_public();
2259
2260
	return "\n<form action='" .
2261
	$h .
2262
	"'" .
2263
	$atts .
2264
	">\n" .
2265
	"<div>" .
2266
	"\n<input type='hidden' name='action' value='$script' />" .
2267
	$corps .
2268
	"</div></form>";
2269
}
2270
2271
/**
2272
 * Créer une URL
2273
 *
2274
 * @param  string $script
2275
 *     Nom du script à exécuter
2276
 * @param  string $args
2277
 *     Arguments à transmettre a l'URL sous la forme `arg1=yy&arg2=zz`
2278
 * @param bool $no_entities
2279
 *     Si false : transforme les & en &amp;
2280
 * @param boolean $public
2281
 *     URL relative ? false : l’URL sera complète et contiendra l’URL du site.
2282
 *     true : l’URL sera relative.
2283
 * @return string
2284
 *     URL
2285
 */
2286
function generer_url_action($script, $args = "", $no_entities = false, $public = false) {
2287
	// si l'on est dans l'espace prive, on garde dans l'url
2288
	// l'exec a l'origine de l'action, qui permet de savoir si il est necessaire
2289
	// ou non de proceder a l'authentification (cas typique de l'install par exemple)
2290
	$url = (_DIR_RACINE and !$public)
2291
		? generer_url_ecrire(_request('exec'))
2292
		: generer_url_public('', '', false, false);
2293
	$url = parametre_url($url, 'action', $script);
2294
	if ($args) {
2295
		$url .= quote_amp('&' . $args);
2296
	}
2297
2298
	if ($no_entities) {
2299
		$url = str_replace('&amp;', '&', $url);
2300
	}
2301
2302
	return $url;
2303
}
2304
2305
2306
/**
2307
 * Fonction d'initialisation groupée pour compatibilité ascendante
2308
 *
2309
 * @param string $pi Répertoire permanent inaccessible
0 ignored issues
show
Documentation introduced by
Should the type for parameter $pi not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
2310
 * @param string $pa Répertoire permanent accessible
0 ignored issues
show
Documentation introduced by
Should the type for parameter $pa not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
2311
 * @param string $ti Répertoire temporaire inaccessible
0 ignored issues
show
Documentation introduced by
Should the type for parameter $ti not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
2312
 * @param string $ta Répertoire temporaire accessible
0 ignored issues
show
Documentation introduced by
Should the type for parameter $ta not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
2313
 */
2314
function spip_initialisation($pi = null, $pa = null, $ti = null, $ta = null) {
2315
	spip_initialisation_core($pi, $pa, $ti, $ta);
2316
	spip_initialisation_suite();
2317
}
2318
2319
/**
2320
 * Fonction d'initialisation, appellée dans inc_version ou mes_options
2321
 *
2322
 * Elle définit les répertoires et fichiers non partageables
2323
 * et indique dans $test_dirs ceux devant être accessibles en écriture
2324
 * mais ne touche pas à cette variable si elle est déjà définie
2325
 * afin que mes_options.php puisse en spécifier d'autres.
2326
 *
2327
 * Elle définit ensuite les noms des fichiers et les droits.
2328
 * Puis simule un register_global=on sécurisé.
2329
 *
2330
 * @param string $pi Répertoire permanent inaccessible
0 ignored issues
show
Documentation introduced by
Should the type for parameter $pi not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
2331
 * @param string $pa Répertoire permanent accessible
0 ignored issues
show
Documentation introduced by
Should the type for parameter $pa not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
2332
 * @param string $ti Répertoire temporaire inaccessible
0 ignored issues
show
Documentation introduced by
Should the type for parameter $ti not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
2333
 * @param string $ta Répertoire temporaire accessible
0 ignored issues
show
Documentation introduced by
Should the type for parameter $ta not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
2334
 */
2335
function spip_initialisation_core($pi = null, $pa = null, $ti = null, $ta = null) {
2336
	static $too_late = 0;
2337
	if ($too_late++) {
2338
		return;
2339
	}
2340
2341
	// Declaration des repertoires
2342
2343
	// le nom du repertoire plugins/ activables/desactivables
2344
	if (!defined('_DIR_PLUGINS')) {
2345
		define('_DIR_PLUGINS', _DIR_RACINE . "plugins/");
2346
	}
2347
2348
	// le nom du repertoire des extensions/ permanentes du core, toujours actives
2349
	if (!defined('_DIR_PLUGINS_DIST')) {
2350
		define('_DIR_PLUGINS_DIST', _DIR_RACINE . "plugins-dist/");
2351
	}
2352
2353
	// le nom du repertoire des librairies
2354
	if (!defined('_DIR_LIB')) {
2355
		define('_DIR_LIB', _DIR_RACINE . "lib/");
2356
	}
2357
2358
	if (!defined('_DIR_IMG')) {
2359
		define('_DIR_IMG', $pa);
2360
	}
2361
	if (!defined('_DIR_LOGOS')) {
2362
		define('_DIR_LOGOS', $pa);
2363
	}
2364
	if (!defined('_DIR_IMG_ICONES')) {
2365
		define('_DIR_IMG_ICONES', _DIR_LOGOS . "icones/");
2366
	}
2367
2368
	if (!defined('_DIR_DUMP')) {
2369
		define('_DIR_DUMP', $ti . "dump/");
2370
	}
2371
	if (!defined('_DIR_SESSIONS')) {
2372
		define('_DIR_SESSIONS', $ti . "sessions/");
2373
	}
2374
	if (!defined('_DIR_TRANSFERT')) {
2375
		define('_DIR_TRANSFERT', $ti . "upload/");
2376
	}
2377
	if (!defined('_DIR_CACHE')) {
2378
		define('_DIR_CACHE', $ti . "cache/");
2379
	}
2380
	if (!defined('_DIR_CACHE_XML')) {
2381
		define('_DIR_CACHE_XML', _DIR_CACHE . "xml/");
2382
	}
2383
	if (!defined('_DIR_SKELS')) {
2384
		define('_DIR_SKELS', _DIR_CACHE . "skel/");
2385
	}
2386
	if (!defined('_DIR_AIDE')) {
2387
		define('_DIR_AIDE', _DIR_CACHE . "aide/");
2388
	}
2389
	if (!defined('_DIR_TMP')) {
2390
		define('_DIR_TMP', $ti);
2391
	}
2392
2393
	if (!defined('_DIR_VAR')) {
2394
		define('_DIR_VAR', $ta);
2395
	}
2396
2397
	if (!defined('_DIR_ETC')) {
2398
		define('_DIR_ETC', $pi);
2399
	}
2400
	if (!defined('_DIR_CONNECT')) {
2401
		define('_DIR_CONNECT', $pi);
2402
	}
2403
	if (!defined('_DIR_CHMOD')) {
2404
		define('_DIR_CHMOD', $pi);
2405
	}
2406
2407
	if (!isset($GLOBALS['test_dirs']))
2408
		// Pas $pi car il est bon de le mettre hors ecriture apres intstall
2409
		// il sera rajoute automatiquement si besoin a l'etape 2 de l'install
2410
	{
2411
		$GLOBALS['test_dirs'] = array($pa, $ti, $ta);
2412
	}
2413
2414
	// Declaration des fichiers
2415
2416
	if (!defined('_CACHE_PLUGINS_PATH')) {
2417
		define('_CACHE_PLUGINS_PATH', _DIR_CACHE . "charger_plugins_chemins.php");
2418
	}
2419
	if (!defined('_CACHE_PLUGINS_OPT')) {
2420
		define('_CACHE_PLUGINS_OPT', _DIR_CACHE . "charger_plugins_options.php");
2421
	}
2422
	if (!defined('_CACHE_PLUGINS_FCT')) {
2423
		define('_CACHE_PLUGINS_FCT', _DIR_CACHE . "charger_plugins_fonctions.php");
2424
	}
2425
	if (!defined('_CACHE_PIPELINES')) {
2426
		define('_CACHE_PIPELINES', _DIR_CACHE . "charger_pipelines.php");
2427
	}
2428
	if (!defined('_CACHE_CHEMIN')) {
2429
		define('_CACHE_CHEMIN', _DIR_CACHE . "chemin.txt");
2430
	}
2431
2432
	# attention .php obligatoire pour ecrire_fichier_securise
2433
	if (!defined('_FILE_META')) {
2434
		define('_FILE_META', $ti . 'meta_cache.php');
2435
	}
2436
	if (!defined('_DIR_LOG')) {
2437
		define('_DIR_LOG', _DIR_TMP . 'log/');
2438
	}
2439
	if (!defined('_FILE_LOG')) {
2440
		define('_FILE_LOG', 'spip');
2441
	}
2442
	if (!defined('_FILE_LOG_SUFFIX')) {
2443
		define('_FILE_LOG_SUFFIX', '.log');
2444
	}
2445
2446
	// Le fichier de connexion a la base de donnees
2447
	// tient compte des anciennes versions (inc_connect...)
2448
	if (!defined('_FILE_CONNECT_INS')) {
2449
		define('_FILE_CONNECT_INS', 'connect');
2450
	}
2451
	if (!defined('_FILE_CONNECT')) {
2452
		define('_FILE_CONNECT',
2453
		(@is_readable($f = _DIR_CONNECT . _FILE_CONNECT_INS . '.php') ? $f
2454
			: (@is_readable($f = _DIR_RESTREINT . 'inc_connect.php') ? $f
2455
				: false)));
2456
	}
2457
2458
	// Le fichier de reglages des droits
2459
	if (!defined('_FILE_CHMOD_INS')) {
2460
		define('_FILE_CHMOD_INS', 'chmod');
2461
	}
2462
	if (!defined('_FILE_CHMOD')) {
2463
		define('_FILE_CHMOD',
2464
		(@is_readable($f = _DIR_CHMOD . _FILE_CHMOD_INS . '.php') ? $f
2465
			: false));
2466
	}
2467
2468
	if (!defined('_FILE_LDAP')) {
2469
		define('_FILE_LDAP', 'ldap.php');
2470
	}
2471
2472
	if (!defined('_FILE_TMP_SUFFIX')) {
2473
		define('_FILE_TMP_SUFFIX', '.tmp.php');
2474
	}
2475
	if (!defined('_FILE_CONNECT_TMP')) {
2476
		define('_FILE_CONNECT_TMP', _DIR_CONNECT . _FILE_CONNECT_INS . _FILE_TMP_SUFFIX);
2477
	}
2478
	if (!defined('_FILE_CHMOD_TMP')) {
2479
		define('_FILE_CHMOD_TMP', _DIR_CHMOD . _FILE_CHMOD_INS . _FILE_TMP_SUFFIX);
2480
	}
2481
2482
	// Definition des droits d'acces en ecriture
2483
	if (!defined('_SPIP_CHMOD') and _FILE_CHMOD) {
2484
		include_once _FILE_CHMOD;
2485
	}
2486
2487
	// Se mefier des fichiers mal remplis!
2488
	if (!defined('_SPIP_CHMOD')) {
2489
		define('_SPIP_CHMOD', 0777);
2490
	}
2491
2492
	if (!defined('_DEFAULT_CHARSET')) {
2493
		/** Le charset par défaut lors de l'installation */
2494
		define('_DEFAULT_CHARSET', 'utf-8');
2495
	}
2496
	if (!defined('_ROOT_PLUGINS')) {
2497
		define('_ROOT_PLUGINS', _ROOT_RACINE . "plugins/");
2498
	}
2499
	if (!defined('_ROOT_PLUGINS_DIST')) {
2500
		define('_ROOT_PLUGINS_DIST', _ROOT_RACINE . "plugins-dist/");
2501
	}
2502
	if (!defined('_ROOT_PLUGINS_SUPPL') && defined('_DIR_PLUGINS_SUPPL') && _DIR_PLUGINS_SUPPL) {
2503
		define('_ROOT_PLUGINS_SUPPL', _ROOT_RACINE . str_replace(_DIR_RACINE, '', _DIR_PLUGINS_SUPPL));
2504
	}
2505
2506
	// La taille des Log
2507
	if (!defined('_MAX_LOG')) {
2508
		define('_MAX_LOG', 100);
2509
	}
2510
2511
	// Sommes-nous dans l'empire du Mal ?
2512
	// (ou sous le signe du Pingouin, ascendant GNU ?)
2513
	if (isset($_SERVER['SERVER_SOFTWARE']) and strpos($_SERVER['SERVER_SOFTWARE'], '(Win') !== false) {
2514
		if (!defined('_OS_SERVEUR')) {
2515
			define('_OS_SERVEUR', 'windows');
2516
		}
2517
		if (!defined('_SPIP_LOCK_MODE')) {
2518
			define('_SPIP_LOCK_MODE', 1);
2519
		} // utiliser le flock php
2520
	} else {
2521
		if (!defined('_OS_SERVEUR')) {
2522
			define('_OS_SERVEUR', '');
2523
		}
2524
		if (!defined('_SPIP_LOCK_MODE')) {
2525
			define('_SPIP_LOCK_MODE', 1);
2526
		} // utiliser le flock php
2527
		#if (!defined('_SPIP_LOCK_MODE')) define('_SPIP_LOCK_MODE',2); // utiliser le nfslock de spip mais link() est tres souvent interdite
2528
	}
2529
2530
	// Langue par defaut
2531
	if (!defined('_LANGUE_PAR_DEFAUT')) {
2532
		define('_LANGUE_PAR_DEFAUT', 'fr');
2533
	}
2534
2535
	//
2536
	// Module de lecture/ecriture/suppression de fichiers utilisant flock()
2537
	// (non surchargeable en l'etat ; attention si on utilise include_spip()
2538
	// pour le rendre surchargeable, on va provoquer un reecriture
2539
	// systematique du noyau ou une baisse de perfs => a etudier)
2540
	include_once _ROOT_RESTREINT . 'inc/flock.php';
2541
2542
	// charger tout de suite le path et son cache
2543
	load_path_cache();
2544
2545
	// *********** traiter les variables ************
2546
2547
	//
2548
	// Securite
2549
	//
2550
2551
	// Ne pas se faire manger par un bug php qui accepte ?GLOBALS[truc]=toto
2552
	if (isset($_REQUEST['GLOBALS'])) {
2553
		die();
2554
	}
2555
	// nettoyer les magic quotes \' et les caracteres nuls %00
2556
	spip_desinfecte($_GET);
2557
	spip_desinfecte($_POST);
2558
	spip_desinfecte($_COOKIE);
2559
	spip_desinfecte($_REQUEST);
2560
2561
	// appliquer le cookie_prefix
2562
	if ($GLOBALS['cookie_prefix'] != 'spip') {
2563
		include_spip('inc/cookie');
2564
		recuperer_cookies_spip($GLOBALS['cookie_prefix']);
2565
	}
2566
2567
	//
2568
	// Capacites php (en fonction de la version)
2569
	//
2570
	$GLOBALS['flag_ob'] = (function_exists("ob_start")
2571
		&& function_exists("ini_get")
2572
		&& !strstr(@ini_get('disable_functions'), 'ob_'));
2573
	$GLOBALS['flag_sapi_name'] = function_exists("php_sapi_name");
2574
	$GLOBALS['flag_get_cfg_var'] = (@get_cfg_var('error_reporting') != "");
2575
	$GLOBALS['flag_upload'] = (!$GLOBALS['flag_get_cfg_var'] ||
2576
		(get_cfg_var('upload_max_filesize') > 0));
2577
2578
2579
	// Compatibilite avec serveurs ne fournissant pas $REQUEST_URI
2580 View Code Duplication
	if (isset($_SERVER['REQUEST_URI'])) {
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...
2581
		$GLOBALS['REQUEST_URI'] = $_SERVER['REQUEST_URI'];
2582
	} else {
2583
		$GLOBALS['REQUEST_URI'] = (php_sapi_name() !== 'cli') ? $_SERVER['PHP_SELF'] : '';
2584
		if (!empty($_SERVER['QUERY_STRING'])
2585
			and !strpos($_SERVER['REQUEST_URI'], '?')
2586
		) {
2587
			$GLOBALS['REQUEST_URI'] .= '?' . $_SERVER['QUERY_STRING'];
2588
		}
2589
	}
2590
2591
	// Duree de validite de l'alea pour les cookies et ce qui s'ensuit.
2592
	if (!defined('_RENOUVELLE_ALEA')) {
2593
		define('_RENOUVELLE_ALEA', 12 * 3600);
2594
	}
2595
	if (!defined('_DUREE_COOKIE_ADMIN')) {
2596
		define('_DUREE_COOKIE_ADMIN', 14 * 24 * 3600);
2597
	}
2598
2599
	// charger les meta si possible et renouveller l'alea au besoin
2600
	// charge aussi effacer_meta et ecrire_meta
2601
	$inc_meta = charger_fonction('meta', 'inc');
2602
	$inc_meta();
2603
2604
	// nombre de repertoires depuis la racine
2605
	// on compare a l'adresse de spip.php : $_SERVER["SCRIPT_NAME"]
2606
	// ou a defaut celle donnee en meta ; (mais si celle-ci est fausse
2607
	// le calcul est faux)
2608
	if (!_DIR_RESTREINT) {
2609
		$GLOBALS['profondeur_url'] = 1;
2610
	} else {
2611
		$uri = isset($_SERVER['REQUEST_URI']) ? explode('?', $_SERVER['REQUEST_URI']) : '';
2612
		$uri_ref = $_SERVER["SCRIPT_NAME"];
2613
		if (!$uri_ref
2614
			// si on est appele avec un autre ti, on est sans doute en mutu
2615
			// si jamais c'est de la mutu avec sous rep, on est perdu si on se fie
2616
			// a spip.php qui est a la racine du spip, et vue qu'on sait pas se reperer
2617
			// s'en remettre a l'adresse du site. alea jacta est.
2618
			or $ti !== _NOM_TEMPORAIRES_INACCESSIBLES
2619
		) {
2620
2621
			if (isset($GLOBALS['meta']['adresse_site'])) {
2622
				$uri_ref = parse_url($GLOBALS['meta']['adresse_site']);
2623
				$uri_ref = (isset($uri_ref['path']) ? $uri_ref['path'] : '') . '/';
2624
			} else {
2625
				$uri_ref = "";
2626
			}
2627
		}
2628
		if (!$uri or !$uri_ref) {
2629
			$GLOBALS['profondeur_url'] = 0;
2630
		} else {
2631
			$GLOBALS['profondeur_url'] = max(0,
2632
				substr_count($uri[0], '/')
2633
				- substr_count($uri_ref, '/'));
2634
		}
2635
	}
2636
	// s'il y a un cookie ou PHP_AUTH, initialiser visiteur_session
2637
	if (_FILE_CONNECT) {
2638
		if (verifier_visiteur() == '0minirezo'
2639
			// si c'est un admin sans cookie admin, il faut ignorer le cache chemin !
2640
			and !isset($_COOKIE['spip_admin'])
2641
		) {
2642
			clear_path_cache();
2643
		}
2644
	}
2645
2646
}
2647
2648
/**
2649
 * Complements d'initialisation non critiques pouvant etre realises
2650
 * par les plugins
2651
 *
2652
 */
2653
function spip_initialisation_suite() {
2654
	static $too_late = 0;
2655
	if ($too_late++) {
2656
		return;
2657
	}
2658
2659
	// taille mini des login
2660
	if (!defined('_LOGIN_TROP_COURT')) {
2661
		define('_LOGIN_TROP_COURT', 4);
2662
	}
2663
2664
	// la taille maxi des logos (0 : pas de limite) (pas de define par defaut, ce n'est pas utile)
2665
	#if (!defined('_LOGO_MAX_SIZE')) define('_LOGO_MAX_SIZE', 0); # poids en ko
2666
	#if (!defined('_LOGO_MAX_WIDTH')) define('_LOGO_MAX_WIDTH', 0); # largeur en pixels
2667
	#if (!defined('_LOGO_MAX_HEIGHT')) define('_LOGO_MAX_HEIGHT', 0); # hauteur en pixels
2668
2669
	// la taille maxi des images (0 : pas de limite) (pas de define par defaut, ce n'est pas utile)
2670
	#if (!defined('_DOC_MAX_SIZE')) define('_DOC_MAX_SIZE', 0); # poids en ko
2671
	#if (!defined('_IMG_MAX_SIZE')) define('_IMG_MAX_SIZE', 0); # poids en ko
2672
	#if (!defined('_IMG_MAX_WIDTH')) define('_IMG_MAX_WIDTH', 0); # largeur en pixels
2673
	#if (!defined('_IMG_MAX_HEIGHT')) define('_IMG_MAX_HEIGHT', 0); # hauteur en pixels
2674
2675
	if (!defined('_PASS_LONGUEUR_MINI')) {
2676
		define('_PASS_LONGUEUR_MINI', 6);
2677
	}
2678
2679
2680
	// Qualite des images calculees automatiquement. C'est un nombre entre 0 et 100, meme pour imagick (on ramene a 0..1 par la suite)
2681
	if (!defined('_IMG_QUALITE')) {
2682
		define('_IMG_QUALITE', 85);
2683
	} # valeur par defaut
2684
	if (!defined('_IMG_GD_QUALITE')) {
2685
		define('_IMG_GD_QUALITE', _IMG_QUALITE);
2686
	} # surcharge pour la lib GD
2687
	if (!defined('_IMG_CONVERT_QUALITE')) {
2688
		define('_IMG_CONVERT_QUALITE', _IMG_QUALITE);
2689
	} # surcharge pour imagick en ligne de commande
2690
	// Historiquement la valeur pour imagick semble differente. Si ca n'est pas necessaire, il serait preferable de garder _IMG_QUALITE
2691
	if (!defined('_IMG_IMAGICK_QUALITE')) {
2692
		define('_IMG_IMAGICK_QUALITE', 75);
2693
	} # surcharge pour imagick en PHP
2694
2695
	if (!defined('_COPIE_LOCALE_MAX_SIZE')) {
2696
		define('_COPIE_LOCALE_MAX_SIZE', 33554432);
2697
	} // poids en octet
2698
2699
	// qq chaines standard
2700
	if (!defined('_ACCESS_FILE_NAME')) {
2701
		define('_ACCESS_FILE_NAME', '.htaccess');
2702
	}
2703
	if (!defined('_AUTH_USER_FILE')) {
2704
		define('_AUTH_USER_FILE', '.htpasswd');
2705
	}
2706
	if (!defined('_SPIP_DUMP')) {
2707
		define('_SPIP_DUMP', 'dump@nom_site@@[email protected]');
2708
	}
2709
	if (!defined('_CACHE_RUBRIQUES')) {
2710
		/** Fichier cache pour le navigateur de rubrique du bandeau */
2711
		define('_CACHE_RUBRIQUES', _DIR_TMP . 'menu-rubriques-cache.txt');
2712
	}
2713
	if (!defined('_CACHE_RUBRIQUES_MAX')) {
2714
		/** Nombre maxi de rubriques enfants affichées pour chaque rubrique du navigateur de rubrique du bandeau */
2715
		define('_CACHE_RUBRIQUES_MAX', 500);
2716
	}
2717
2718
	if (!defined('_CACHE_CONTEXTES_AJAX_SUR_LONGUEUR')) {
2719
		/**
2720
		 * Basculer les contextes ajax en fichier si la longueur d’url est trop grande
2721
		 * @var int Nombre de caractères */
2722
		define('_CACHE_CONTEXTES_AJAX_SUR_LONGUEUR', 2000);
2723
	}
2724
2725
	if (!defined('_EXTENSION_SQUELETTES')) {
2726
		define('_EXTENSION_SQUELETTES', 'html');
2727
	}
2728
2729
	if (!defined('_DOCTYPE_ECRIRE')) {
2730
		/** Définit le doctype de l’espace privé */
2731
		define('_DOCTYPE_ECRIRE', "<!DOCTYPE html>\n");
2732
	}
2733
	if (!defined('_DOCTYPE_AIDE')) {
2734
		/** Définit le doctype de l’aide en ligne */
2735
		define('_DOCTYPE_AIDE',
2736
		"<!DOCTYPE html PUBLIC '-//W3C//DTD HTML 4.01 Frameset//EN' 'http://www.w3.org/TR/1999/REC-html401-19991224/frameset.dtd'>");
2737
	}
2738
2739
	if (!defined('_SPIP_SCRIPT')) {
2740
		/** L'adresse de base du site ; on peut mettre '' si la racine est gerée par
2741
		 * le script de l'espace public, alias index.php */
2742
		define('_SPIP_SCRIPT', 'spip.php');
2743
	}
2744
	if (!defined('_SPIP_PAGE')) {
2745
		/** Argument page, personalisable en cas de conflit avec un autre script */
2746
		define('_SPIP_PAGE', 'page');
2747
	}
2748
2749
	// le script de l'espace prive
2750
	// Mettre a "index.php" si DirectoryIndex ne le fait pas ou pb connexes:
2751
	// les anciens IIS n'acceptent pas les POST sur ecrire/ (#419)
2752
	// meme pb sur thttpd cf. https://forum.spip.net/fr_184153.html
2753
	if (!defined('_SPIP_ECRIRE_SCRIPT')) {
2754
		if (!empty($_SERVER['SERVER_SOFTWARE']) and preg_match(',IIS|thttpd,', $_SERVER['SERVER_SOFTWARE'])) {
2755
			define('_SPIP_ECRIRE_SCRIPT', 'index.php');
2756
		} else {
2757
			define('_SPIP_ECRIRE_SCRIPT', '');
2758
		}
2759
	}
2760
2761
2762
	if (!defined('_SPIP_AJAX')) {
2763
		define('_SPIP_AJAX', ((!isset($_COOKIE['spip_accepte_ajax']))
2764
			? 1
2765
			: (($_COOKIE['spip_accepte_ajax'] != -1) ? 1 : 0)));
2766
	}
2767
2768
	// La requete est-elle en ajax ?
2769
	if (!defined('_AJAX')) {
2770
		define('_AJAX',
2771
			(isset($_SERVER['HTTP_X_REQUESTED_WITH']) # ajax jQuery
2772
				or !empty($_REQUEST['var_ajax_redir']) # redirection 302 apres ajax jQuery
2773
				or !empty($_REQUEST['var_ajaxcharset']) # compat ascendante pour plugins
2774
				or !empty($_REQUEST['var_ajax']) # forms ajax & inclure ajax de spip
2775
			)
2776
			and empty($_REQUEST['var_noajax']) # horrible exception, car c'est pas parce que la requete est ajax jquery qu'il faut tuer tous les formulaires ajax qu'elle contient
2777
		);
2778
	}
2779
2780
	# nombre de pixels maxi pour calcul de la vignette avec gd
2781
	# au dela de 5500000 on considere que php n'est pas limite en memoire pour cette operation
2782
	# les configurations limitees en memoire ont un seuil plutot vers 1MPixel
2783
	if (!defined('_IMG_GD_MAX_PIXELS')) {
2784
		define('_IMG_GD_MAX_PIXELS',
2785
		(isset($GLOBALS['meta']['max_taille_vignettes']) and $GLOBALS['meta']['max_taille_vignettes'])
2786
			? $GLOBALS['meta']['max_taille_vignettes']
2787
			: 0);
2788
	}
2789
2790
	if (!defined('_MEMORY_LIMIT_MIN')) {
2791
		define('_MEMORY_LIMIT_MIN', 16);
2792
	} // en Mo
2793
	// si on est dans l'espace prive et si le besoin est superieur a 8Mo (qui est vraiment le standard)
2794
	// on verifie que la memoire est suffisante pour le compactage css+js pour eviter la page blanche
2795
	// il y aura d'autres problemes et l'utilisateur n'ira pas tres loin, mais ce sera plus comprehensible qu'une page blanche
2796
	if (test_espace_prive() and _MEMORY_LIMIT_MIN > 8) {
2797
		if ($memory = trim(ini_get('memory_limit')) and $memory != -1) {
2798
			$unit = strtolower(substr($memory, -1));
2799
			$memory = substr($memory, 0, -1);
2800
			switch ($unit) {
2801
				// Le modifieur 'G' est disponible depuis PHP 5.1.0
2802
				case 'g':
2803
					$memory *= 1024;
2804
				case 'm':
2805
					$memory *= 1024;
2806
				case 'k':
2807
					$memory *= 1024;
2808
			}
2809
			if ($memory < _MEMORY_LIMIT_MIN * 1024 * 1024) {
2810
				@ini_set('memory_limit', $m = _MEMORY_LIMIT_MIN . 'M');
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
2811
				if (trim(ini_get('memory_limit')) != $m) {
2812
					if (!defined('_INTERDIRE_COMPACTE_HEAD_ECRIRE')) {
2813
						define('_INTERDIRE_COMPACTE_HEAD_ECRIRE', true);
2814
					} // evite une page blanche car on ne saura pas calculer la css dans ce hit
2815
				}
2816
			}
2817
		} else {
2818
			if (!defined('_INTERDIRE_COMPACTE_HEAD_ECRIRE')) {
2819
				define('_INTERDIRE_COMPACTE_HEAD_ECRIRE', true);
2820
			}
2821
		} // evite une page blanche car on ne saura pas calculer la css dans ce hit
2822
	}
2823
	// Protocoles a normaliser dans les chaines de langues
2824
	if (!defined('_PROTOCOLES_STD')) {
2825
		define('_PROTOCOLES_STD', 'http|https|ftp|mailto|webcal');
2826
	}
2827
2828
	init_var_mode();
2829
}
2830
2831
/**
2832
 * Repérer les variables d'URL spéciales `var_mode` qui conditionnent
2833
 * la validité du cache ou certains affichages spéciaux.
2834
 *
2835
 * Le paramètre d'URL `var_mode` permet de
2836
 * modifier la pérennité du cache, recalculer des urls
2837
 * ou d'autres petit caches (trouver_table, css et js compactes ...),
2838
 * d'afficher un écran de débug ou des traductions non réalisées.
2839
 *
2840
 * En fonction de ces paramètres dans l'URL appelante, on définit
2841
 * da constante `_VAR_MODE` qui servira ensuite à SPIP.
2842
 *
2843
 * Le paramètre `var_mode` accepte ces valeurs :
2844
 *
2845
 * - `calcul` : force un calcul du cache de la page (sans forcément recompiler les squelettes)
2846
 * - `recalcul` : force un calcul du cache de la page en recompilant au préabable les squelettes
2847
 * - `inclure` : modifie l'affichage en ajoutant visuellement le nom de toutes les inclusions qu'elle contient
2848
 * - `debug` :  modifie l'affichage activant le mode "debug"
2849
 * - `preview` : modifie l'affichage en ajoutant aux boucles les éléments prévisualisables
2850
 * - `traduction` : modifie l'affichage en affichant des informations sur les chaînes de langues utilisées
2851
 * - `urls` : permet de recalculer les URLs des objets appelés dans la page par les balises `#URL_xx`
2852
 * - `images` : permet de recalculer les filtres d'images utilisés dans la page
2853
 *
2854
 * En dehors des modes `calcul` et `recalcul`, une autorisation 'previsualiser' ou 'debug' est testée.
2855
 *
2856
 * @note
2857
 *     Il éxiste également le paramètre `var_profile` qui modifie l'affichage pour incruster
2858
 *     le nombre de requêtes SQL utilisées dans la page, qui peut se compléter avec le paramètre
2859
 * `   var_mode` (calcul ou recalcul).
2860
 */
2861
function init_var_mode() {
2862
	static $done = false;
2863
	if (!$done) {
2864
2865
		if (isset($_GET['var_mode'])) {
2866
			$var_mode = explode(',', $_GET['var_mode']);
2867
			// tout le monde peut calcul/recalcul
2868
			if (!defined('_VAR_MODE')) {
2869
				if (in_array('recalcul', $var_mode)) {
2870
					define('_VAR_MODE', 'recalcul');
2871
				} elseif (in_array('calcul', $var_mode)) {
2872
					define('_VAR_MODE', 'calcul');
2873
				}
2874
			}
2875
			$var_mode = array_diff($var_mode, array('calcul', 'recalcul'));
2876
			if ($var_mode) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $var_mode of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
2877
				include_spip('inc/autoriser');
2878
				// autoriser preview si preview seulement, et sinon autoriser debug
2879
				if (autoriser(
2880
					($_GET['var_mode'] == 'preview')
2881
						? 'previsualiser'
2882
						: 'debug'
2883
				)) {
2884 View Code Duplication
					if (in_array('traduction', $var_mode)) {
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...
2885
						// forcer le calcul pour passer dans traduire
2886
						if (!defined('_VAR_MODE')) {
2887
							define('_VAR_MODE', 'calcul');
2888
						}
2889
						// et ne pas enregistrer de cache pour ne pas trainer les surlignages sur d'autres pages
2890
						if (!defined('_VAR_NOCACHE')) {
2891
							define('_VAR_NOCACHE', true);
2892
						}
2893
						$var_mode = array_diff($var_mode, array('traduction'));
2894
					}
2895 View Code Duplication
					if (in_array('preview', $var_mode)) {
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...
2896
						// basculer sur les criteres de preview dans les boucles
2897
						if (!defined('_VAR_PREVIEW')) {
2898
							define('_VAR_PREVIEW', true);
2899
						}
2900
						// forcer le calcul
2901
						if (!defined('_VAR_MODE')) {
2902
							define('_VAR_MODE', 'calcul');
2903
						}
2904
						// et ne pas enregistrer de cache
2905
						if (!defined('_VAR_NOCACHE')) {
2906
							define('_VAR_NOCACHE', true);
2907
						}
2908
						$var_mode = array_diff($var_mode, array('preview'));
2909
					}
2910 View Code Duplication
					if (in_array('inclure', $var_mode)) {
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...
2911
						// forcer le compilo et ignorer les caches existants
2912
						if (!defined('_VAR_MODE')) {
2913
							define('_VAR_MODE', 'calcul');
2914
						}
2915
						if (!defined('_VAR_INCLURE')) {
2916
							define('_VAR_INCLURE', true);
2917
						}
2918
						// et ne pas enregistrer de cache
2919
						if (!defined('_VAR_NOCACHE')) {
2920
							define('_VAR_NOCACHE', true);
2921
						}
2922
						$var_mode = array_diff($var_mode, array('inclure'));
2923
					}
2924 View Code Duplication
					if (in_array('urls', $var_mode)) {
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...
2925
						// forcer le compilo et ignorer les caches existants
2926
						if (!defined('_VAR_MODE')) {
2927
							define('_VAR_MODE', 'calcul');
2928
						}
2929
						if (!defined('_VAR_URLS')) {
2930
							define('_VAR_URLS', true);
2931
						}
2932
						$var_mode = array_diff($var_mode, array('urls'));
2933
					}
2934 View Code Duplication
					if (in_array('images', $var_mode)) {
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...
2935
						// forcer le compilo et ignorer les caches existants
2936
						if (!defined('_VAR_MODE')) {
2937
							define('_VAR_MODE', 'calcul');
2938
						}
2939
						// indiquer qu'on doit recalculer les images
2940
						if (!defined('_VAR_IMAGES')) {
2941
							define('_VAR_IMAGES', true);
2942
						}
2943
						$var_mode = array_diff($var_mode, array('images'));
2944
					}
2945 View Code Duplication
					if (in_array('debug', $var_mode)) {
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...
2946
						if (!defined('_VAR_MODE')) {
2947
							define('_VAR_MODE', 'debug');
2948
						}
2949
						// et ne pas enregistrer de cache
2950
						if (!defined('_VAR_NOCACHE')) {
2951
							define('_VAR_NOCACHE', true);
2952
						}
2953
						$var_mode = array_diff($var_mode, array('debug'));
2954
					}
2955
					if (count($var_mode) and !defined('_VAR_MODE')) {
2956
						define('_VAR_MODE', reset($var_mode));
2957
					}
2958
					if (isset($GLOBALS['visiteur_session']['nom'])) {
2959
						spip_log($GLOBALS['visiteur_session']['nom']
2960
							. " " . _VAR_MODE);
2961
					}
2962
				} // pas autorise ?
2963
				else {
2964
					// si on n'est pas connecte on se redirige, si on est pas en cli et pas deja en train de se loger
2965
					if (!$GLOBALS['visiteur_session']
2966
					  and !empty($_SERVER['HTTP_HOST'])
2967
					  and !empty($_SERVER['REQUEST_METHOD'])
2968
					  and $_SERVER['REQUEST_METHOD'] === 'GET') {
2969
						$self = self('&', true);
2970
						if (strpos($self, 'page=login') === false) {
2971
							include_spip('inc/headers');
2972
							$redirect = parametre_url(self('&', true), 'var_mode', $_GET['var_mode'], '&');
2973
							redirige_par_entete(generer_url_public('login','url=' . rawurlencode($redirect), true));
2974
						}
2975
					}
2976
					// sinon tant pis
2977
				}
2978
			}
2979
		}
2980
		if (!defined('_VAR_MODE')) {
2981
			/**
2982
			 * Indique le mode de calcul ou d'affichage de la page.
2983
			 * @see init_var_mode()
2984
			 */
2985
			define('_VAR_MODE', false);
2986
		}
2987
		$done = true;
2988
	}
2989
}
2990
2991
// Annuler les magic quotes \' sur GET POST COOKIE et GLOBALS ;
2992
// supprimer aussi les eventuels caracteres nuls %00, qui peuvent tromper
2993
// la commande is_readable('chemin/vers/fichier/interdit%00truc_normal')
2994
// https://code.spip.net/@spip_desinfecte
2995
function spip_desinfecte(&$t, $deep = true) {
2996
	foreach ($t as $key => $val) {
2997
		if (is_string($t[$key])) {
2998
			$t[$key] = str_replace(chr(0), '-', $t[$key]);
2999
		} // traiter aussi les "texte_plus" de article_edit
3000
		else {
3001
			if ($deep and is_array($t[$key]) and $key !== 'GLOBALS') {
3002
				spip_desinfecte($t[$key], $deep);
3003
			}
3004
		}
3005
	}
3006
}
3007
3008
//  retourne le statut du visiteur s'il s'annonce
3009
3010
// https://code.spip.net/@verifier_visiteur
3011
function verifier_visiteur() {
3012
	// Rq: pour que cette fonction marche depuis mes_options
3013
	// il faut forcer l'init si ce n'est fait
3014
	// mais on risque de perturber des plugins en initialisant trop tot
3015
	// certaines constantes
3016
	@spip_initialisation_core(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
3017
		(_DIR_RACINE . _NOM_PERMANENTS_INACCESSIBLES),
3018
		(_DIR_RACINE . _NOM_PERMANENTS_ACCESSIBLES),
3019
		(_DIR_RACINE . _NOM_TEMPORAIRES_INACCESSIBLES),
3020
		(_DIR_RACINE . _NOM_TEMPORAIRES_ACCESSIBLES)
3021
	);
3022
3023
	// Demarrer une session NON AUTHENTIFIEE si on donne son nom
3024
	// dans un formulaire sans login (ex: #FORMULAIRE_FORUM)
3025
	// Attention on separe bien session_nom et nom, pour eviter
3026
	// les melanges entre donnees SQL et variables plus aleatoires
3027
	$variables_session = array('session_nom', 'session_email');
3028
	foreach ($variables_session as $var) {
3029
		if (_request($var) !== null) {
3030
			$init = true;
3031
			break;
3032
		}
3033
	}
3034
	if (isset($init)) {
3035
		#@spip_initialisation_suite();
3036
		$session = charger_fonction('session', 'inc');
3037
		$session();
3038
		include_spip('inc/texte');
3039
		foreach ($variables_session as $var) {
3040
			if (($a = _request($var)) !== null) {
3041
				$GLOBALS['visiteur_session'][$var] = safehtml($a);
3042
			}
3043
		}
3044
		if (!isset($GLOBALS['visiteur_session']['id_auteur'])) {
3045
			$GLOBALS['visiteur_session']['id_auteur'] = 0;
3046
		}
3047
		$session($GLOBALS['visiteur_session']);
3048
3049
		return 0;
3050
	}
3051
3052
	$h = (isset($_SERVER['PHP_AUTH_USER']) and !$GLOBALS['ignore_auth_http']);
3053
	if ($h or isset($_COOKIE['spip_session']) or isset($_COOKIE[$GLOBALS['cookie_prefix'] . '_session'])) {
3054
3055
		$session = charger_fonction('session', 'inc');
3056
		if ($session()) {
3057
			return $GLOBALS['visiteur_session']['statut'];
3058
		}
3059
		if ($h and isset($_SERVER['PHP_AUTH_PW'])) {
3060
			include_spip('inc/auth');
3061
			$h = lire_php_auth($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW']);
3062
		}
3063
		if ($h) {
3064
			$GLOBALS['visiteur_session'] = $h;
3065
3066
			return $GLOBALS['visiteur_session']['statut'];
3067
		}
3068
	}
3069
3070
	// au moins son navigateur nous dit la langue preferee de cet inconnu
3071
	include_spip('inc/lang');
3072
	utiliser_langue_visiteur();
3073
3074
	return false;
3075
}
3076
3077
3078
/**
3079
 * Sélectionne la langue donnée en argument et mémorise la courante
3080
 *
3081
 * Restaure l'ancienne langue si appellée sans argument.
3082
 *
3083
 * @note
3084
 *     On pourrait économiser l'empilement en cas de non changemnt
3085
 *     et lui faire retourner `False` pour prevenir l'appelant
3086
 *     Le noyau de Spip sait le faire, mais pour assurer la compatibilité
3087
 *     cette fonction retourne toujours non `False`
3088
 *
3089
 * @uses changer_langue()
3090
 * @param null|string $lang
3091
 *     - string : Langue à appliquer,
3092
 *     - null : Pour restituer la dernière langue mémorisée.
3093
 * @return string
3094
 *     - string Langue utilisée.
3095
 **/
3096
function lang_select($lang = null) {
3097
	static $pile_langues = array();
3098
	if (!function_exists('changer_langue')) {
3099
		include_spip('inc/lang');
3100
	}
3101
	if ($lang === null) {
3102
		$lang = array_pop($pile_langues);
3103
	} else {
3104
		array_push($pile_langues, $GLOBALS['spip_lang']);
3105
	}
3106
	if (isset($GLOBALS['spip_lang']) and $lang == $GLOBALS['spip_lang']) {
3107
		return $lang;
3108
	}
3109
	changer_langue($lang);
3110
3111
	return $lang;
3112
}
3113
3114
/**
3115
 * Renvoie une chaîne qui identifie la session courante
3116
 *
3117
 * Permet de savoir si on peut utiliser un cache enregistré pour cette session.
3118
 * Cette chaîne est courte (8 cars) pour pouvoir être utilisée dans un nom
3119
 * de fichier cache.
3120
 *
3121
 * @pipeline_appel definir_session
3122
 *
3123
 * @param bool $force
3124
 * @return string
3125
 *     Identifiant de la session
3126
 **/
3127
function spip_session($force = false) {
3128
	static $session;
3129
	if ($force or !isset($session)) {
3130
		$s = pipeline('definir_session',
3131
			$GLOBALS['visiteur_session']
3132
				? serialize($GLOBALS['visiteur_session'])
3133
				. '_' . @$_COOKIE['spip_session']
3134
				: ''
3135
		);
3136
		$session = $s ? substr(md5($s), 0, 8) : '';
3137
	}
3138
3139
	#spip_log('session: '.$session);
3140
	return $session;
3141
}
3142
3143
3144
/**
3145
 * Retourne un lien vers une aide
3146
 *
3147
 * Aide, aussi depuis l'espace privé à présent.
3148
 * Surchargeable mais pas d'erreur fatale si indisponible.
3149
 *
3150
 * @param string $aide
3151
 *    Cle d'identification de l'aide desiree
3152
 * @param bool $distante
3153
 *    Generer une url locale (par defaut)
3154
 *    ou une url distante [directement sur spip.net]
3155
 * @return
3156
 *    Lien sur une icone d'aide
3157
 **/
3158
function aider($aide = '', $distante = false) {
3159
	$aider = charger_fonction('aide', 'inc', true);
3160
3161
	return $aider ? $aider($aide, '', array(), $distante) : '';
3162
}
3163
3164
/**
3165
 * Page `exec=info` : retourne le contenu de la fonction php `phpinfo()`
3166
 *
3167
 * Si l’utiliseur est un webmestre.
3168
 */
3169
function exec_info_dist() {
3170
3171
	include_spip('inc/autoriser');
3172
	if (autoriser('phpinfos')) {
3173
		$cookies_masques = ['spip_session', 'PHPSESSID'];
3174
		$cookies_backup = [];
3175
		foreach ($cookies_masques as $k) {
3176
			if (!empty($_COOKIE[$k])) {
3177
				$cookies_backup[$k] = $_COOKIE[$k];
3178
				$_COOKIE[$k] = '******************************';
3179
			}
3180
		}
3181
		phpinfo();
3182
		foreach ($cookies_backup as $k => $v) {
3183
			$_COOKIE[$k] = $v;
3184
		}
3185
	} else {
3186
		include_spip('inc/filtres');
3187
		sinon_interdire_acces();
3188
	}
3189
}
3190
3191
/**
3192
 * Génère une erreur de squelette
3193
 *
3194
 * Génère une erreur de squelette qui sera bien visible par un
3195
 * administrateur authentifié lors d'une visite de la page en erreur
3196
 *
3197
 * @param bool|string|array $message
3198
 *     - Message d'erreur (string|array)
3199
 *     - false pour retourner le texte des messages d'erreurs
3200
 *     - vide pour afficher les messages d'erreurs
3201
 * @param string|array|object $lieu
3202
 *     Lieu d'origine de l'erreur
3203
 * @return null|string
3204
 *     - Rien dans la plupart des cas
3205
 *     - string si $message à false.
3206
 **/
3207
function erreur_squelette($message = '', $lieu = '') {
3208
	$debusquer = charger_fonction('debusquer', 'public');
3209
	if (is_array($lieu)) {
3210
		include_spip('public/compiler');
3211
		$lieu = reconstruire_contexte_compil($lieu);
3212
	}
3213
3214
	return $debusquer($message, $lieu);
3215
}
3216
3217
/**
3218
 * Calcule un squelette avec un contexte et retourne son contenu
3219
 *
3220
 * La fonction de base de SPIP : un squelette + un contexte => une page.
3221
 * $fond peut etre un nom de squelette, ou une liste de squelette au format array.
3222
 * Dans ce dernier cas, les squelettes sont tous evalues et mis bout a bout
3223
 * $options permet de selectionner les options suivantes :
3224
 *
3225
 * - trim => true (valeur par defaut) permet de ne rien renvoyer si le fond ne produit que des espaces ;
3226
 * - raw  => true permet de recuperer la strucure $page complete avec entetes et invalideurs
3227
 *          pour chaque $fond fourni.
3228
 *
3229
 * @api
3230
 * @param string /array $fond
3231
 *     - Le ou les squelettes à utiliser, sans l'extension, {@example prive/liste/auteurs}
3232
 *     - Le fichier sera retrouvé dans la liste des chemins connus de SPIP (squelettes, plugins, spip)
3233
 * @param array $contexte
3234
 *     - Informations de contexte envoyées au squelette, {@example array('id_rubrique' => 8)}
3235
 *     - La langue est transmise automatiquement (sauf option étoile).
3236
 * @param array $options
3237
 *     Options complémentaires :
3238
 *
3239
 *     - trim   : applique un trim sur le résultat (true par défaut)
3240
 *     - raw    : retourne un tableau d'information sur le squelette (false par défaut)
3241
 *     - etoile : ne pas transmettre la langue au contexte automatiquement (false par défaut),
3242
 *                équivalent de INCLURE*
3243
 *     - ajax   : gere les liens internes du squelette en ajax (équivalent du paramètre {ajax})
3244
 * @param string $connect
3245
 *     Non du connecteur de bdd a utiliser
3246
 * @return string|array
3247
 *     - Contenu du squelette calculé
3248
 *     - ou tableau d'information sur le squelette.
3249
 */
3250
function recuperer_fond($fond, $contexte = array(), $options = array(), $connect = '') {
3251
	if (!function_exists('evaluer_fond')) {
3252
		include_spip('public/assembler');
3253
	}
3254
	// assurer la compat avec l'ancienne syntaxe
3255
	// (trim etait le 3eme argument, par defaut a true)
3256
	if (!is_array($options)) {
3257
		$options = array('trim' => $options);
3258
	}
3259
	if (!isset($options['trim'])) {
3260
		$options['trim'] = true;
3261
	}
3262
3263
	if (isset($contexte['connect'])) {
3264
		$connect = $contexte['connect'];
3265
		unset($contexte['connect']);
3266
	}
3267
3268
	$texte = "";
3269
	$pages = array();
3270
	$lang_select = '';
3271 View Code Duplication
	if (!isset($options['etoile']) or !$options['etoile']) {
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...
3272
		// Si on a inclus sans fixer le critere de lang, on prend la langue courante
3273
		if (!isset($contexte['lang'])) {
3274
			$contexte['lang'] = $GLOBALS['spip_lang'];
3275
		}
3276
3277
		if ($contexte['lang'] != $GLOBALS['meta']['langue_site']) {
3278
			$lang_select = lang_select($contexte['lang']);
3279
		}
3280
	}
3281
3282
	if (!isset($GLOBALS['_INC_PUBLIC'])) {
3283
		$GLOBALS['_INC_PUBLIC'] = 0;
3284
	}
3285
3286
	$GLOBALS['_INC_PUBLIC']++;
3287
3288
	// fix #4235
3289
	$cache_utilise_session_appelant	= (isset($GLOBALS['cache_utilise_session']) ? $GLOBALS['cache_utilise_session'] : null);
3290
3291
3292
	foreach (is_array($fond) ? $fond : array($fond) as $f) {
3293
		
3294
		unset($GLOBALS['cache_utilise_session']);	// fix #4235
3295
3296
		$page = evaluer_fond($f, $contexte, $connect);
3297
		if ($page === '') {
3298
			$c = isset($options['compil']) ? $options['compil'] : '';
3299
			$a = array('fichier' => $f);
3300
			$erreur = _T('info_erreur_squelette2', $a); // squelette introuvable
3301
			erreur_squelette($erreur, $c);
3302
			// eviter des erreurs strictes ensuite sur $page['cle'] en PHP >= 5.4
3303
			$page = array('texte' => '', 'erreur' => $erreur);
3304
		}
3305
3306
		$page = pipeline('recuperer_fond', array(
3307
			'args' => array('fond' => $f, 'contexte' => $contexte, 'options' => $options, 'connect' => $connect),
3308
			'data' => $page
3309
		));
3310
		if (isset($options['ajax']) and $options['ajax']) {
3311
			if (!function_exists('encoder_contexte_ajax')) {
3312
				include_spip('inc/filtres');
3313
			}
3314
			$page['texte'] = encoder_contexte_ajax(
3315
				array_merge(
3316
					$contexte,
3317
					array('fond' => $f),
3318
					($connect ? array('connect' => $connect) : array())
3319
				),
3320
				'',
3321
				$page['texte'],
3322
				$options['ajax']
3323
			);
3324
		}
3325
3326
		if (isset($options['raw']) and $options['raw']) {
3327
			$pages[] = $page;
3328
		} else {
3329
			$texte .= $options['trim'] ? rtrim($page['texte']) : $page['texte'];
3330
		}
3331
		
3332
		// contamination de la session appelante, pour les inclusions statiques
3333
		if (isset($page['invalideurs']['session'])){
3334
			$cache_utilise_session_appelant = $page['invalideurs']['session'];
3335
		}
3336
	}
3337
3338
	// restaurer le sessionnement du contexte appelant, 
3339
	// éventuellement contaminé si on vient de récupérer une inclusion statique sessionnée
3340
	if (isset($cache_utilise_session_appelant)) {
3341
		$GLOBALS['cache_utilise_session'] = $cache_utilise_session_appelant;
3342
	}
3343
3344
	$GLOBALS['_INC_PUBLIC']--;
3345
3346
	if ($lang_select) {
3347
		lang_select();
3348
	}
3349
	if (isset($options['raw']) and $options['raw']) {
3350
		return is_array($fond) ? $pages : reset($pages);
3351
	} else {
3352
		return $options['trim'] ? ltrim($texte) : $texte;
3353
	}
3354
}
3355
3356
/**
3357
 * Trouve un squelette dans le repertoire modeles/
3358
 *
3359
 * @param  $nom
3360
 * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be array|string? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
3361
 */
3362
function trouve_modele($nom) {
3363
	return trouver_fond($nom, 'modeles/');
3364
}
3365
3366
/**
3367
 * Trouver un squelette dans le chemin
3368
 * on peut specifier un sous-dossier dans $dir
3369
 * si $pathinfo est a true, retourne un tableau avec
3370
 * les composantes du fichier trouve
3371
 * + le chemin complet sans son extension dans fond
3372
 *
3373
 * @param string $nom
3374
 * @param string $dir
3375
 * @param bool $pathinfo
3376
 * @return array|string
3377
 */
3378
function trouver_fond($nom, $dir = '', $pathinfo = false) {
3379
	$f = find_in_path($nom . '.' . _EXTENSION_SQUELETTES, $dir ? rtrim($dir, '/') . '/' : '');
3380
	if (!$pathinfo) {
3381
		return $f;
3382
	}
3383
	// renvoyer un tableau detaille si $pathinfo==true
3384
	$p = pathinfo($f);
3385
	if (!isset($p['extension']) or !$p['extension']) {
3386
		$p['extension'] = _EXTENSION_SQUELETTES;
3387
	}
3388
	if (!isset($p['extension']) or !$p['filename']) {
3389
		$p['filename'] = ($p['basename'] ? substr($p['basename'], 0, -strlen($p['extension']) - 1) : '');
3390
	}
3391
	$p['fond'] = ($f ? substr($f, 0, -strlen($p['extension']) - 1) : '');
3392
3393
	return $p;
3394
}
3395
3396
/**
3397
 * Teste, pour un nom de page de l'espace privé, s'il est possible
3398
 * de générer son contenu.
3399
 *
3400
 * Dans ce cas, on retourne la fonction d'exécution correspondante à utiliser
3401
 * (du répertoire `ecrire/exec`). Deux cas particuliers et prioritaires :
3402
 * `fond` est retourné si des squelettes existent.
3403
 *
3404
 * - `fond` : pour des squelettes de `prive/squelettes/contenu`
3405
 *          ou pour des objets éditoriaux dont les squelettes seront échaffaudés
3406
 *
3407
 * @param string $nom
3408
 *     Nom de la page
3409
 * @return string
3410
 *     Nom de l'exec, sinon chaîne vide.
3411
 **/
3412
function tester_url_ecrire($nom) {
3413
	static $exec = array();
3414
	if (isset($exec[$nom])) {
3415
		return $exec[$nom];
3416
	}
3417
	// tester si c'est une page en squelette
3418
	if (trouver_fond($nom, 'prive/squelettes/contenu/')) {
3419
		return $exec[$nom] = 'fond';
3420
	} // echafaudage d'un fond !
3421
	elseif (include_spip('public/styliser_par_z') and z_echafaudable($nom)) {
3422
		return $exec[$nom] = 'fond';
3423
	}
3424
	// attention, il ne faut pas inclure l'exec ici
3425
	// car sinon #URL_ECRIRE provoque des inclusions
3426
	// et des define intrusifs potentiels
3427
	return $exec[$nom] = ((find_in_path("{$nom}.php", 'exec/') or charger_fonction($nom, 'exec', true)) ? $nom : '');
3428
}
3429
3430
/**
3431
 * Indique si le code HTML5 est permis sur le site public
3432
 *
3433
 * @return bool
3434
 *     true si la constante _VERSION_HTML n'est pas définie ou égale à html5
3435
 **/
3436
function html5_permis() {
3437
	return (!defined('_VERSION_HTML')
3438
		or _VERSION_HTML !== 'html4');
3439
}
3440
3441
/**
3442
 * Lister les formats image acceptes par les lib et fonctions images
3443
 * @param bool $gd
0 ignored issues
show
Documentation introduced by
Should the type for parameter $gd not be boolean|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
3444
 * @param bool $svg_allowed
3445
 * @return array
3446
 */
3447
function formats_image_acceptables($gd = null, $svg_allowed = true) {
3448
	$formats = null;
3449
	if (!is_null($gd)) {
3450
		$config = ($gd ? "gd_formats" : "formats_graphiques");
3451
		if (isset($GLOBALS['meta'][$config])) {
3452
			$formats = $GLOBALS['meta'][$config];
3453
			$formats = explode(',', $formats);
3454
			$formats = array_filter($formats);
3455
			$formats = array_map('trim', $formats);
3456
		}
3457
	}
3458
	if (is_null($formats)) {
3459
		include_spip('inc/filtres_images_lib_mini');
3460
		$formats = _image_extensions_acceptees_en_entree();
3461
	}
3462
3463
	if ($svg_allowed) {
3464
		if (!in_array('svg', $formats)) {
3465
			$formats[] = 'svg';
3466
		}
3467
	}
3468
	else {
3469
		$formats = array_diff($formats, ['svg']);
3470
	}
3471
	return $formats;
3472
}
3473
3474
/**
3475
 * Extension de la fonction getimagesize pour supporter aussi les images SVG
3476
 * @param string $fichier
3477
 * @return array|bool
3478
 */
3479
function spip_getimagesize($fichier) {
3480
	if (!$imagesize = @getimagesize($fichier)) {
3481
3482
		include_spip("inc/svg");
3483
		if ($attrs = svg_lire_attributs($fichier)) {
3484
			list($width, $height, $viewbox) = svg_getimagesize_from_attr($attrs);
0 ignored issues
show
Unused Code introduced by
The assignment to $viewbox is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
3485
			$imagesize = [
3486
				$width,
3487
				$height,
3488
				IMAGETYPE_SVG,
3489
				"width=\"{$width}\" height=\"{$height}\"",
3490
				"mime" => "image/svg+xml"
3491
			];
3492
		}
3493
	}
3494
	return $imagesize;
3495
}
3496
3497
/**
3498
 * Poser une alerte qui sera affiche aux auteurs de bon statut ('' = tous)
3499
 * au prochain passage dans l'espace prive
3500
 * chaque alerte doit avoir un nom pour eviter duplication a chaque hit
3501
 * les alertes affichees une fois sont effacees
3502
 *
3503
 * @param string $nom
3504
 * @param string $message
3505
 * @param string $statut
3506
 */
3507
function avertir_auteurs($nom, $message, $statut = '') {
3508
	$alertes = $GLOBALS['meta']['message_alertes_auteurs'];
3509
	if (!$alertes
3510
		or !is_array($alertes = unserialize($alertes))
3511
	) {
3512
		$alertes = array();
3513
	}
3514
3515
	if (!isset($alertes[$statut])) {
3516
		$alertes[$statut] = array();
3517
	}
3518
	$alertes[$statut][$nom] = $message;
3519
	ecrire_meta("message_alertes_auteurs", serialize($alertes));
3520
}
3521
3522
/**
3523
 * Nettoie une chaine pour servir comme classes CSS.
3524
 *
3525
 * @note
3526
 *     les classes CSS acceptent théoriquement tous les caractères sauf NUL.
3527
 *     Ici, on limite (enlève) les caractères autres qu’alphanumérique, espace, - + _ @
3528
 *
3529
 * @param string|string[] $classes
3530
 * @return string|string[]
0 ignored issues
show
Documentation introduced by
Should the return type not be array|string? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
3531
 */
3532
function spip_sanitize_classname($classes) {
3533
	if (is_array($classes)) {
3534
		return array_map('spip_sanitize_classname', $classes);
3535
	}
3536
	return preg_replace("/[^ 0-9a-z_\-+@]/i", "", $classes);
3537
}
3538
3539
3540
/**
3541
 * Compare 2 numéros de version entre elles.
3542
 *
3543
 * Cette fonction est identique (arguments et retours) a la fonction PHP
3544
 * version_compare() qu'elle appelle. Cependant, cette fonction reformate
3545
 * les numeros de versions pour ameliorer certains usages dans SPIP ou bugs
3546
 * dans PHP. On permet ainsi de comparer 3.0.4 à 3.0.* par exemple.
3547
 *
3548
 * @param string $v1
3549
 *    Numero de version servant de base a la comparaison.
3550
 *    Ce numero ne peut pas comporter d'etoile.
3551
 * @param string $v2
3552
 *    Numero de version a comparer.
3553
 *    Il peut posseder des etoiles tel que 3.0.*
3554
 * @param string $op
0 ignored issues
show
Documentation introduced by
Should the type for parameter $op not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
3555
 *    Un operateur eventuel (<, >, <=, >=, =, == ...)
3556
 * @return int|bool
3557
 *    Sans operateur : int. -1 pour inferieur, 0 pour egal, 1 pour superieur
3558
 *    Avec operateur : bool.
3559
 **/
3560
function spip_version_compare($v1, $v2, $op = null) {
3561
	$v1 = strtolower(preg_replace(',([0-9])[\s.-]?(dev|alpha|a|beta|b|rc|pl|p),i', '\\1.\\2', $v1));
3562
	$v2 = strtolower(preg_replace(',([0-9])[\s.-]?(dev|alpha|a|beta|b|rc|pl|p),i', '\\1.\\2', $v2));
3563
	$v1 = str_replace('rc', 'RC', $v1); // certaines versions de PHP ne comprennent RC qu'en majuscule
3564
	$v2 = str_replace('rc', 'RC', $v2); // certaines versions de PHP ne comprennent RC qu'en majuscule
3565
3566
	$v1 = explode('.', $v1);
3567
	$v2 = explode('.', $v2);
3568
	// $v1 est toujours une version, donc sans etoile
3569
	while (count($v1) < count($v2)) {
3570
		$v1[] = '0';
3571
	}
3572
3573
	// $v2 peut etre une borne, donc accepte l'etoile
3574
	$etoile = false;
3575
	foreach ($v1 as $k => $v) {
3576
		if (!isset($v2[$k])) {
3577
			$v2[] = ($etoile and (is_numeric($v) or $v == 'pl' or $v == 'p')) ? $v : '0';
3578
		} else {
3579
			if ($v2[$k] == '*') {
3580
				$etoile = true;
3581
				$v2[$k] = $v;
3582
			}
3583
		}
3584
	}
3585
	$v1 = implode('.', $v1);
3586
	$v2 = implode('.', $v2);
3587
3588
	return $op ? version_compare($v1, $v2, $op) : version_compare($v1, $v2);
3589
}
3590