Completed
Push — master ( 37f24f...c12d0a )
by cam
04:22
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
 * @see  action_super_cron_dist()
1044
 * @uses cron()
1045
 **/
1046
function action_cron() {
1047
	include_spip('inc/headers');
1048
	http_status(204); // No Content
1049
	header("Connection: close");
1050
	define('_DIRECT_CRON_FORCE', true);
1051
	cron();
1052
}
1053
1054
/**
1055
 * Exécution des tâches de fond
1056
 *
1057
 * @uses inc_genie_dist()
1058
 *
1059
 * @param array $taches
1060
 *     Tâches forcées
1061
 * @param array $taches_old
1062
 *     Tâches forcées, pour compat avec ancienne syntaxe
1063
 * @return bool
1064
 *     True si la tache a pu être effectuée
1065
 */
1066
function cron($taches = array(), $taches_old = array()) {
1067
	// si pas en mode cron force, laisser tomber.
1068
	if (!defined('_DIRECT_CRON_FORCE')) {
1069
		return false;
1070
	}
1071
	if (!is_array($taches)) {
1072
		$taches = $taches_old;
1073
	} // compat anciens appels
1074
	// si taches a inserer en base et base inaccessible, laisser tomber
1075
	// sinon on ne verifie pas la connexion tout de suite, car si ca se trouve
1076
	// queue_sleep_time_to_next_job() dira qu'il n'y a rien a faire
1077
	// et on evite d'ouvrir une connexion pour rien (utilisation de _DIRECT_CRON_FORCE dans mes_options.php)
1078
	if ($taches and count($taches) and !spip_connect()) {
1079
		return false;
1080
	}
1081
	spip_log("cron !", 'jq' . _LOG_DEBUG);
1082
	if ($genie = charger_fonction('genie', 'inc', true)) {
1083
		return $genie($taches);
1084
	}
1085
1086
	return false;
1087
}
1088
1089
/**
1090
 * Ajout d'une tache dans la file d'attente
1091
 *
1092
 * @param string $function
1093
 *     Le nom de la fonction PHP qui doit être appelée.
1094
 * @param string $description
1095
 *     Une description humainement compréhensible de ce que fait la tâche
1096
 *     (essentiellement pour l’affichage dans la page de suivi de l’espace privé)
1097
 * @param array $arguments
1098
 *     Facultatif, vide par défaut : les arguments qui seront passés à la fonction, sous forme de tableau PHP
1099
 * @param string $file
1100
 *     Facultatif, vide par défaut : nom du fichier à inclure, via `include_spip($file)`
1101
 *     exemple : `'inc/mail'` : il ne faut pas indiquer .php
1102
 *     Si le nom finit par un '/' alors on considère que c’est un répertoire et SPIP fera un `charger_fonction($function, $file)`
1103
 * @param bool $no_duplicate
1104
 *     Facultatif, `false` par défaut
1105
 *
1106
 *     - 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.
1107
 *     - 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
1108
 * @param int $time
1109
 *     Facultatif, `0` par défaut : indique la date sous forme de timestamp à laquelle la tâche doit être programmée.
1110
 *     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).
1111
 * @param int $priority
1112
 *     Facultatif, `0` par défaut : indique un niveau de priorité entre -10 et +10.
1113
 *     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.
1114
 * @return int
1115
 *     Le numéro de travail ajouté ou `0` si aucun travail n’a été ajouté.
1116
 */
1117
function job_queue_add(
1118
	$function,
1119
	$description,
1120
	$arguments = array(),
1121
	$file = '',
1122
	$no_duplicate = false,
1123
	$time = 0,
1124
	$priority = 0
1125
) {
1126
	include_spip('inc/queue');
1127
1128
	return queue_add_job($function, $description, $arguments, $file, $no_duplicate, $time, $priority);
1129
}
1130
1131
/**
1132
 * Supprimer une tache de la file d'attente
1133
 *
1134
 * @param int $id_job
1135
 *  id of jonb to delete
1136
 * @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...
1137
 */
1138
function job_queue_remove($id_job) {
1139
	include_spip('inc/queue');
1140
1141
	return queue_remove_job($id_job);
1142
}
1143
1144
/**
1145
 * Associer une tache a un/des objets de SPIP
1146
 *
1147
 * @param int $id_job
1148
 *     id of job to link
1149
 * @param array $objets
1150
 *     can be a simple array('objet'=>'article', 'id_objet'=>23)
1151
 *     or an array of simple array to link multiples objet in one time
1152
 */
1153
function job_queue_link($id_job, $objets) {
1154
	include_spip('inc/queue');
1155
1156
	return queue_link_job($id_job, $objets);
1157
}
1158
1159
1160
/**
1161
 * Renvoyer le temps de repos restant jusqu'au prochain job
1162
 *
1163
 * @staticvar int $queue_next_job_time
1164
 * @see queue_set_next_job_time()
1165
 * @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...
1166
 *    Utilisée par `queue_set_next_job_time()` pour mettre à jour la valeur :
1167
 *
1168
 *    - si `true`, force la relecture depuis le fichier
1169
 *    - si int, affecte la static directement avec la valeur
1170
 * @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...
1171
 *
1172
 *  - `0` si un job est à traiter
1173
 *  - `null` si la queue n'est pas encore initialisée
1174
 */
1175
function queue_sleep_time_to_next_job($force = null) {
1176
	static $queue_next_job_time = -1;
1177
	if ($force === true) {
1178
		$queue_next_job_time = -1;
1179
	} elseif ($force) {
1180
		$queue_next_job_time = $force;
1181
	}
1182
1183
	if ($queue_next_job_time == -1) {
1184
		if (!defined('_JQ_NEXT_JOB_TIME_FILENAME')) {
1185
			define('_JQ_NEXT_JOB_TIME_FILENAME', _DIR_TMP . "job_queue_next.txt");
1186
		}
1187
		// utiliser un cache memoire si dispo
1188
		if (function_exists("cache_get") and defined('_MEMOIZE_MEMORY') and _MEMOIZE_MEMORY) {
1189
			$queue_next_job_time = cache_get(_JQ_NEXT_JOB_TIME_FILENAME);
1190
		} else {
1191
			$queue_next_job_time = null;
1192
			if (lire_fichier(_JQ_NEXT_JOB_TIME_FILENAME, $contenu)) {
1193
				$queue_next_job_time = intval($contenu);
1194
			}
1195
		}
1196
	}
1197
1198
	if (is_null($queue_next_job_time)) {
1199
		return null;
1200
	}
1201
	if (!$_SERVER['REQUEST_TIME']) {
1202
		$_SERVER['REQUEST_TIME'] = time();
1203
	}
1204
1205
	return $queue_next_job_time - $_SERVER['REQUEST_TIME'];
1206
}
1207
1208
1209
/**
1210
 * Transformation XML des `&` en `&amp;`
1211
 * 
1212
 * @pipeline post_typo
1213
 * @param string $u
1214
 * @return string
1215
 */
1216
function quote_amp($u) {
1217
	return preg_replace(
1218
		"/&(?![a-z]{0,4}\w{2,3};|#x?[0-9a-f]{2,6};)/i",
1219
		"&amp;", $u);
1220
}
1221
1222
1223
/**
1224
 * Produit une balise `<script>` valide
1225
 *
1226
 * @example
1227
 *     ```
1228
 *     echo http_script('alert("ok");');
1229
 *     echo http_script('','js/jquery.js');
1230
 *     ```
1231
 *
1232
 * @param string $script
1233
 *     Code source du script
1234
 * @param string $src
1235
 *     Permet de faire appel à un fichier javascript distant
1236
 * @param string $noscript
1237
 *     Contenu de la balise  `<noscript>`
1238
 * @return string
1239
 *     Balise HTML `<script>` et son contenu
1240
 **/
1241
function http_script($script, $src = '', $noscript = '') {
1242
	static $done = array();
1243
1244
	if ($src && !isset($done[$src])) {
1245
		$done[$src] = true;
1246
		$src = find_in_path($src, _JAVASCRIPT);
1247
		$src = " src='$src'";
1248
	} else {
1249
		$src = '';
1250
	}
1251
	if ($script) {
1252
		$script = ("/*<![CDATA[*/\n" .
1253
			preg_replace(',</([^>]*)>,', '<\/\1>', $script) .
1254
			"/*]]>*/");
1255
	}
1256
	if ($noscript) {
1257
		$noscript = "<noscript>\n\t$noscript\n</noscript>\n";
1258
	}
1259
1260
	return ($src or $script or $noscript)
1261
		? "<script type='text/javascript'$src>$script</script>$noscript"
1262
		: '';
1263
}
1264
1265
1266
/**
1267
 * Sécurise du texte à écrire dans du PHP ou du Javascript.
1268
 *
1269
 * Transforme n'importe quel texte en une chaîne utilisable
1270
 * en PHP ou Javascript en toute sécurité, à l'intérieur d'apostrophes
1271
 * simples (`'` uniquement ; pas `"`)
1272
 *
1273
 * Utile particulièrement en filtre dans un squelettes
1274
 * pour écrire un contenu dans une variable JS ou PHP.
1275
 *
1276
 * Échappe les apostrophes (') du contenu transmis.
1277
 *
1278
 * @link https://www.spip.net/4281
1279
 * @example
1280
 *     PHP dans un squelette
1281
 *     ```
1282
 *     $x = '[(#TEXTE|texte_script)]';
1283
 *     ```
1284
 *
1285
 *     JS dans un squelette (transmettre une chaîne de langue)
1286
 *     ```
1287
 *     $x = '<:afficher_calendrier|texte_script:>';
1288
 *     ```
1289
 *
1290
 * @filtre
1291
 * @param string $texte
1292
 *     Texte à échapper
1293
 * @return string
1294
 *     Texte échappé
1295
 **/
1296
function texte_script($texte) {
1297
	return str_replace('\'', '\\\'', str_replace('\\', '\\\\', $texte));
1298
}
1299
1300
1301
/**
1302
 * Gestion des chemins (ou path) de recherche de fichiers par SPIP
1303
 *
1304
 * Empile de nouveaux chemins (à la suite de ceux déjà présents, mais avant
1305
 * le répertoire `squelettes` ou les dossiers squelettes), si un répertoire
1306
 * (ou liste de répertoires séparés par `:`) lui est passé en paramètre.
1307
 *
1308
 * Ainsi, si l'argument est de la forme `dir1:dir2:dir3`, ces 3 chemins sont placés
1309
 * en tête du path, dans cet ordre (hormis `squelettes` & la globale
1310
 * `$dossier_squelette` si définie qui resteront devant)
1311
 *
1312
 * Retourne dans tous les cas la liste des chemins.
1313
 *
1314
 * @note
1315
 *     Cette fonction est appelée à plusieurs endroits et crée une liste
1316
 *     de chemins finale à peu près de la sorte :
1317
 *
1318
 *     - dossiers squelettes (si globale précisée)
1319
 *     - squelettes/
1320
 *     - plugins (en fonction de leurs dépendances) : ceux qui dépendent
1321
 *       d'un plugin sont devant eux (ils peuvent surcharger leurs fichiers)
1322
 *     - racine du site
1323
 *     - squelettes-dist/
1324
 *     - prive/
1325
 *     - ecrire/
1326
 *
1327
 * @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...
1328
 *     - Répertoire(s) à empiler au path
1329
 *     - '' provoque un recalcul des chemins.
1330
 * @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...
1331
 *     Liste des chemins, par ordre de priorité.
1332
 **/
1333
function _chemin($dir_path = null) {
1334
	static $path_base = null;
1335
	static $path_full = null;
1336
	if ($path_base == null) {
1337
		// Chemin standard depuis l'espace public
1338
		$path = defined('_SPIP_PATH') ? _SPIP_PATH :
1339
			_DIR_RACINE . ':' .
1340
			_DIR_RACINE . 'squelettes-dist/:' .
1341
			_DIR_RACINE . 'prive/:' .
1342
			_DIR_RESTREINT;
1343
		// Ajouter squelettes/
1344
		if (@is_dir(_DIR_RACINE . 'squelettes')) {
1345
			$path = _DIR_RACINE . 'squelettes/:' . $path;
1346
		}
1347
		foreach (explode(':', $path) as $dir) {
1348
			if (strlen($dir) and substr($dir, -1) != '/') {
1349
				$dir .= "/";
1350
			}
1351
			$path_base[] = $dir;
1352
		}
1353
		$path_full = $path_base;
1354
		// Et le(s) dossier(s) des squelettes nommes
1355 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...
1356
			foreach (array_reverse(explode(':', $GLOBALS['dossier_squelettes'])) as $d) {
1357
				array_unshift($path_full, ($d[0] == '/' ? '' : _DIR_RACINE) . $d . '/');
1358
			}
1359
		}
1360
		$GLOBALS['path_sig'] = md5(serialize($path_full));
1361
	}
1362
	if ($dir_path === null) {
1363
		return $path_full;
1364
	}
1365
1366
	if (is_array($dir_path) or strlen($dir_path)) {
1367
		$tete = "";
1368
		if (reset($path_base) == _DIR_RACINE . 'squelettes/') {
1369
			$tete = array_shift($path_base);
1370
		}
1371
		$dirs = (is_array($dir_path) ? $dir_path : explode(':', $dir_path));
1372
		$dirs = array_reverse($dirs);
1373
		foreach ($dirs as $dir_path) {
1374
			if (substr($dir_path, -1) != '/') {
1375
				$dir_path .= "/";
1376
			}
1377
			if (!in_array($dir_path, $path_base)) {
1378
				array_unshift($path_base, $dir_path);
1379
			}
1380
		}
1381
		if (strlen($tete)) {
1382
			array_unshift($path_base, $tete);
1383
		}
1384
	}
1385
	$path_full = $path_base;
1386
	// Et le(s) dossier(s) des squelettes nommes
1387 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...
1388
		foreach (array_reverse(explode(':', $GLOBALS['dossier_squelettes'])) as $d) {
1389
			array_unshift($path_full, ((isset($d[0]) and $d[0] == '/') ? '' : _DIR_RACINE) . $d . '/');
1390
		}
1391
	}
1392
1393
	$GLOBALS['path_sig'] = md5(serialize($path_full));
1394
1395
	return $path_full;
1396
}
1397
1398
/**
1399
 * Retourne la liste des chemins connus de SPIP, dans l'ordre de priorité
1400
 *
1401
 * Recalcule la liste si le nom ou liste de dossier squelettes a changé.
1402
 *
1403
 * @uses _chemin()
1404
 *
1405
 * @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...
1406
 **/
1407
function creer_chemin() {
1408
	$path_a = _chemin();
1409
	static $c = '';
1410
1411
	// on calcule le chemin si le dossier skel a change
1412
	if ($c != $GLOBALS['dossier_squelettes']) {
1413
		// assurer le non plantage lors de la montee de version :
1414
		$c = $GLOBALS['dossier_squelettes'];
1415
		$path_a = _chemin(''); // forcer un recalcul du chemin
1416
	}
1417
1418
	return $path_a;
1419
}
1420
1421
1422
function lister_themes_prives() {
1423
	static $themes = null;
1424
	if (is_null($themes)) {
1425
		// si pas encore definie
1426
		if (!defined('_SPIP_THEME_PRIVE')) {
1427
			define('_SPIP_THEME_PRIVE', 'spip');
1428
		}
1429
		$themes = array(_SPIP_THEME_PRIVE);
1430
		// lors d'une installation neuve, prefs n'est pas definie.
1431 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...
1432
			$prefs = $GLOBALS['visiteur_session']['prefs'];
1433
		} else {
1434
			$prefs = array();
1435
		}
1436
		if (is_string($prefs)) {
1437
			$prefs = unserialize($GLOBALS['visiteur_session']['prefs']);
1438
		}
1439
		if (
1440
			((isset($prefs['theme']) and $theme = $prefs['theme'])
1441
				or (isset($GLOBALS['theme_prive_defaut']) and $theme = $GLOBALS['theme_prive_defaut']))
1442
			and $theme != _SPIP_THEME_PRIVE
1443
		) {
1444
			array_unshift($themes, $theme);
1445
		} // placer le theme choisi en tete
1446
	}
1447
1448
	return $themes;
1449
}
1450
1451
function find_in_theme($file, $subdir = '', $include = false) {
1452
	static $themefiles = array();
1453
	if (isset($themefiles["$subdir$file"])) {
1454
		return $themefiles["$subdir$file"];
1455
	}
1456
	// on peut fournir une icone generique -xx.svg qui fera le job dans toutes les tailles, et qui est prioritaire sur le png
1457
	// si il y a un .svg a la bonne taille (-16.svg) a cote, on l'utilise en remplacement du -16.png
1458
	if (preg_match(',-(\d+)[.](png|gif|svg)$,', $file, $m)
1459
	  and $file_svg_generique = substr($file,0, -strlen($m[0])) . "-xx.svg"
1460
		and $f = find_in_theme("$file_svg_generique")) {
1461
		if ($fsize = substr($f,0,-6) . $m[1] . ".svg" and file_exists($fsize)) {
1462
			return $themefiles["$subdir$file"] = $fsize;
1463
		}
1464
		else {
1465
			return $themefiles["$subdir$file"] = "$f?".$m[1]."px";
1466
		}
1467
	}
1468
1469
	$themes = lister_themes_prives();
1470
	foreach ($themes as $theme) {
1471
		if ($f = find_in_path($file, "prive/themes/$theme/$subdir", $include)) {
1472
			return $themefiles["$subdir$file"] = $f;
1473
		}
1474
	}
1475
	spip_log("$file introuvable dans le theme prive " . reset($themes), 'theme');
1476
1477
	return $themefiles["$subdir$file"] = "";
1478
}
1479
1480
1481
/**
1482
 * Cherche une image dans les dossiers d'images
1483
 *
1484
 * Cherche en priorité dans les thèmes d'image (prive/themes/X/images)
1485
 * et si la fonction n'en trouve pas, gère le renommage des icones (ex: 'supprimer' => 'del')
1486
 * de facon temporaire le temps de la migration, et cherche de nouveau.
1487
 *
1488
 * Si l'image n'est toujours pas trouvée, on la cherche dans les chemins,
1489
 * dans le répertoire défini par la constante `_NOM_IMG_PACK`
1490
 *
1491
 * @see find_in_theme()
1492
 * @see inc_icone_renommer_dist()
1493
 *
1494
 * @param string $icone
1495
 *     Nom de l'icone cherchée
1496
 * @return string
1497
 *     Chemin complet de l'icone depuis la racine si l'icone est trouée,
1498
 *     sinon chaîne vide.
1499
 **/
1500
function chemin_image($icone) {
1501
	static $icone_renommer;
1502
	if ($p = strpos($icone, '?')) {
1503
		$icone = substr($icone,0, $p);
1504
	}
1505
	// gerer le cas d'un double appel en evitant de refaire le travail inutilement
1506
	if (strpos($icone, "/") !== false and file_exists($icone)) {
1507
		return $icone;
1508
	}
1509
1510
	// si c'est un nom d'image complet (article-24.png) essayer de le renvoyer direct
1511
	if (preg_match(',[.](png|gif|jpg|webp|svg)$,', $icone) and $f = find_in_theme("images/$icone")) {
1512
		return $f;
1513
	}
1514
	// sinon passer par le module de renommage
1515
	if (is_null($icone_renommer)) {
1516
		$icone_renommer = charger_fonction('icone_renommer', 'inc', true);
1517
	}
1518
	if ($icone_renommer) {
1519
		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...
1520
		if (file_exists($icone)) {
1521
			return $icone;
1522
		}
1523
	}
1524
1525
	return find_in_path($icone, _NOM_IMG_PACK);
1526
}
1527
1528
//
1529
// chercher un fichier $file dans le SPIP_PATH
1530
// si on donne un sous-repertoire en 2e arg optionnel, il FAUT le / final
1531
// si 3e arg vrai, on inclut si ce n'est fait.
1532
$GLOBALS['path_sig'] = '';
1533
$GLOBALS['path_files'] = null;
1534
1535
/**
1536
 * Recherche un fichier dans les chemins de SPIP (squelettes, plugins, core)
1537
 *
1538
 * Retournera le premier fichier trouvé (ayant la plus haute priorité donc),
1539
 * suivant l'ordre des chemins connus de SPIP.
1540
 *
1541
 * @api
1542
 * @see  charger_fonction()
1543
 * @uses creer_chemin() Pour la liste des chemins.
1544
 * @example
1545
 *     ```
1546
 *     $f = find_in_path('css/perso.css');
1547
 *     $f = find_in_path('perso.css', 'css');
1548
 *     ```
1549
 *
1550
 * @param string $file
1551
 *     Fichier recherché
1552
 * @param string $dirname
1553
 *     Répertoire éventuel de recherche (est aussi extrait automatiquement de $file)
1554
 * @param bool|string $include
1555
 *     - false : ne fait rien de plus
1556
 *     - true : inclut le fichier (include_once)
1557
 *     - 'require' : idem, mais tue le script avec une erreur si le fichier n'est pas trouvé.
1558
 * @return string|bool
1559
 *     - string : chemin du fichier trouvé
1560
 *     - false : fichier introuvable
1561
 **/
1562
function find_in_path($file, $dirname = '', $include = false) {
1563
	static $dirs = array();
1564
	static $inc = array(); # cf https://git.spip.net/spip/spip/commit/42e4e028e38c839121efaee84308d08aee307eec
1565
	static $c = '';
1566
1567
	if (!$file and !strlen($file)) {
1568
		return false;
1569
	}
1570
1571
	// on calcule le chemin si le dossier skel a change
1572
	if ($c != $GLOBALS['dossier_squelettes']) {
1573
		// assurer le non plantage lors de la montee de version :
1574
		$c = $GLOBALS['dossier_squelettes'];
1575
		creer_chemin(); // forcer un recalcul du chemin et la mise a jour de path_sig
1576
	}
1577
1578
	if (isset($GLOBALS['path_files'][$GLOBALS['path_sig']][$dirname][$file])) {
1579
		if (!$GLOBALS['path_files'][$GLOBALS['path_sig']][$dirname][$file]) {
1580
			return false;
1581
		}
1582
		if ($include and !isset($inc[$dirname][$file])) {
1583
			include_once _ROOT_CWD . $GLOBALS['path_files'][$GLOBALS['path_sig']][$dirname][$file];
1584
			$inc[$dirname][$file] = $inc[''][$dirname . $file] = true;
1585
		}
1586
1587
		return $GLOBALS['path_files'][$GLOBALS['path_sig']][$dirname][$file];
1588
	}
1589
1590
	$a = strrpos($file, '/');
1591
	if ($a !== false) {
1592
		$dirname .= substr($file, 0, ++$a);
1593
		$file = substr($file, $a);
1594
	}
1595
1596
	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...
1597
		if (!isset($dirs[$a = $dir . $dirname])) {
1598
			$dirs[$a] = (is_dir(_ROOT_CWD . $a) || !$a);
1599
		}
1600
		if ($dirs[$a]) {
1601
			if (file_exists(_ROOT_CWD . ($a .= $file))) {
1602
				if ($include and !isset($inc[$dirname][$file])) {
1603
					include_once _ROOT_CWD . $a;
1604
					$inc[$dirname][$file] = $inc[''][$dirname . $file] = true;
1605
				}
1606 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...
1607
					// si le chemin n'a pas encore ete charge, ne pas lever le flag, ne pas cacher
1608
					if (is_null($GLOBALS['path_files'])) {
1609
						return $a;
1610
					}
1611
					define('_SAUVER_CHEMIN', true);
1612
				}
1613
1614
				return $GLOBALS['path_files'][$GLOBALS['path_sig']][$dirname][$file] = $GLOBALS['path_files'][$GLOBALS['path_sig']][''][$dirname . $file] = $a;
1615
			}
1616
		}
1617
	}
1618
1619
	if ($include) {
1620
		spip_log("include_spip $dirname$file non trouve");
1621
		if ($include === 'required') {
1622
			echo '<pre>',
1623
			"<strong>Erreur Fatale</strong><br />";
1624
			if (function_exists('debug_print_backtrace')) {
1625
				echo debug_print_backtrace();
1626
			}
1627
			echo '</pre>';
1628
			die("Erreur interne: ne peut inclure $dirname$file");
1629
		}
1630
	}
1631
1632 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...
1633
		// si le chemin n'a pas encore ete charge, ne pas lever le flag, ne pas cacher
1634
		if (is_null($GLOBALS['path_files'])) {
1635
			return false;
1636
		}
1637
		define('_SAUVER_CHEMIN', true);
1638
	}
1639
1640
	return $GLOBALS['path_files'][$GLOBALS['path_sig']][$dirname][$file] = $GLOBALS['path_files'][$GLOBALS['path_sig']][''][$dirname . $file] = false;
1641
}
1642
1643
function clear_path_cache() {
1644
	$GLOBALS['path_files'] = array();
1645
	spip_unlink(_CACHE_CHEMIN);
1646
}
1647
1648
function load_path_cache() {
1649
	// charger le path des plugins
1650
	if (@is_readable(_CACHE_PLUGINS_PATH)) {
1651
		include_once(_CACHE_PLUGINS_PATH);
1652
	}
1653
	$GLOBALS['path_files'] = array();
1654
	// si le visiteur est admin,
1655
	// on ne recharge pas le cache pour forcer sa mise a jour
1656
	if (
1657
		// la session n'est pas encore chargee a ce moment, on ne peut donc pas s'y fier
1658
		//AND (!isset($GLOBALS['visiteur_session']['statut']) OR $GLOBALS['visiteur_session']['statut']!='0minirezo')
1659
		// utiliser le cookie est un pis aller qui marche 'en general'
1660
		// on blinde par un second test au moment de la lecture de la session
1661
		// !isset($_COOKIE[$GLOBALS['cookie_prefix'].'_admin'])
1662
		// et en ignorant ce cache en cas de recalcul explicite
1663
	!_request('var_mode')
1664
	) {
1665
		// on essaye de lire directement sans verrou pour aller plus vite
1666
		if ($contenu = spip_file_get_contents(_CACHE_CHEMIN)) {
1667
			// mais si semble corrompu on relit avec un verrou
1668
			if (!$GLOBALS['path_files'] = unserialize($contenu)) {
1669
				lire_fichier(_CACHE_CHEMIN, $contenu);
1670
				if (!$GLOBALS['path_files'] = unserialize($contenu)) {
1671
					$GLOBALS['path_files'] = array();
1672
				}
1673
			}
1674
		}
1675
	}
1676
}
1677
1678
function save_path_cache() {
1679
	if (defined('_SAUVER_CHEMIN')
1680
		and _SAUVER_CHEMIN
1681
	) {
1682
		ecrire_fichier(_CACHE_CHEMIN, serialize($GLOBALS['path_files']));
1683
	}
1684
}
1685
1686
1687
/**
1688
 * Trouve tous les fichiers du path correspondants à un pattern
1689
 *
1690
 * Pour un nom de fichier donné, ne retourne que le premier qui sera trouvé
1691
 * par un `find_in_path()`
1692
 *
1693
 * @api
1694
 * @uses creer_chemin()
1695
 * @uses preg_files()
1696
 *
1697
 * @param string $dir
1698
 * @param string $pattern
1699
 * @param bool $recurs
1700
 * @return array
1701
 */
1702
function find_all_in_path($dir, $pattern, $recurs = false) {
1703
	$liste_fichiers = array();
1704
	$maxfiles = 10000;
1705
1706
	// cas borderline si dans mes_options on appelle redirige_par_entete qui utilise _T et charge un fichier de langue
1707
	// on a pas encore inclus flock.php
1708
	if (!function_exists('preg_files')) {
1709
		include_once _ROOT_RESTREINT . 'inc/flock.php';
1710
	}
1711
1712
	// Parcourir le chemin
1713
	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...
1714
		$f = $d . $dir;
1715
		if (@is_dir($f)) {
1716
			$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...
1717
			foreach ($liste as $chemin) {
1718
				$nom = basename($chemin);
1719
				// ne prendre que les fichiers pas deja trouves
1720
				// car find_in_path prend le premier qu'il trouve,
1721
				// les autres sont donc masques
1722
				if (!isset($liste_fichiers[$nom])) {
1723
					$liste_fichiers[$nom] = $chemin;
1724
				}
1725
			}
1726
		}
1727
	}
1728
1729
	return $liste_fichiers;
1730
}
1731
1732
/**
1733
 * Prédicat sur les scripts de ecrire qui n'authentifient pas par cookie
1734
 * et beneficient d'une exception
1735
 *
1736
 * @param string $nom
1737
 * @param bool $strict
1738
 * @return bool
1739
 */
1740
function autoriser_sans_cookie($nom, $strict = false) {
1741
	static $autsanscookie = array('install', 'base_repair');
1742
1743
	if (in_array($nom, $autsanscookie)) {
1744
		if (test_espace_prive()){
1745
			include_spip('base/connect_sql');
1746
			if (!$strict or !spip_connect()){
1747
				return true;
1748
			}
1749
		}
1750
	}
1751
	return false;
1752
}
1753
1754
/**
1755
 * Fonction codant et décodant les URLs des objets SQL mis en page par SPIP
1756
 *
1757
 * @api
1758
 * @param string $id
1759
 *   numero de la cle primaire si nombre, URL a decoder si pas numerique
1760
 * @param string $entite
1761
 *   surnom de la table SQL (donne acces au nom de cle primaire)
1762
 * @param string $args
1763
 *   query_string a placer apres cle=$id&....
1764
 * @param string $ancre
1765
 *   ancre a mettre a la fin de l'URL a produire
1766
 * @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...
1767
 *   produire l'URL publique ou privee (par defaut: selon espace)
1768
 *   si string : serveur de base de donnee (nom du connect)
1769
 * @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...
1770
 *   fichier dans le repertoire ecrire/urls determinant l'apparence
1771
 * @return string|array
1772
 *   url codee ou fonction de decodage
1773
 *   array : derogatoire, la fonction d'url retourne (objet,id_objet) utilises par nettoyer_raccourcis_typo() pour generer un lien titre
1774
 *           (cas des raccourcis personalises [->spip20] : il faut implementer une fonction generer_url_spip et une fonction generer_url_ecrire_spip)
1775
 */
1776
function generer_url_entite($id = '', $entite = '', $args = '', $ancre = '', $public = null, $type = null) {
1777
	if ($public === null) {
1778
		$public = !test_espace_prive();
1779
	}
1780
	$entite = objet_type($entite); // cas particulier d'appels sur objet/id_objet...
1781
1782
	if (!$public) {
1783
		if (!$entite) {
1784
			return '';
1785
		}
1786
		if (!function_exists('generer_url_ecrire_objet')) {
1787
			include_spip('inc/urls');
1788
		}
1789
		$res = generer_url_ecrire_objet($entite, $id, $args, $ancre, false);
1790
	} else {
1791
		if ($type === null) {
1792
			$type = (isset($GLOBALS['type_urls']))
1793
				? $GLOBALS['type_urls'] // pour surcharge via fichier d'options
1794
				: ((isset($GLOBALS['meta']['type_urls'])) // sinon la config url_etendues
1795
					? ($GLOBALS['meta']['type_urls']) : "page"); // sinon type "page" par défaut
1796
		}
1797
1798
		$f = charger_fonction($type, 'urls', true);
1799
		// se rabattre sur les urls page si les urls perso non dispo
1800
		if (!$f) {
1801
			$f = charger_fonction('page', 'urls', true);
1802
		}
1803
1804
		// si $entite='', on veut la fonction de passage URL ==> id
1805
		// sinon on veut effectuer le passage id ==> URL
1806
		if (!$entite) {
1807
			return $f;
1808
		}
1809
1810
		// mais d'abord il faut tester le cas des urls sur une
1811
		// base distante
1812
		if (is_string($public)
1813
			and $g = charger_fonction('connect', 'urls', true)
1814
		) {
1815
			$f = $g;
1816
		}
1817
1818
		$res = $f(intval($id), $entite, $args, $ancre, $public);
1819
1820
	}
1821
	if ($res) {
1822
		return $res;
1823
	}
1824
	// Sinon c'est un raccourci ou compat SPIP < 2
1825
	if (!function_exists($f = 'generer_url_' . $entite)) {
1826
		if (!function_exists($f .= '_dist')) {
1827
			$f = '';
1828
		}
1829
	}
1830
	if ($f) {
1831
		$url = $f($id, $args, $ancre);
1832
		if (strlen($args)) {
1833
			$url .= strstr($url, '?')
1834
				? '&amp;' . $args
1835
				: '?' . $args;
1836
		}
1837
1838
		return $url;
1839
	}
1840
	// On a ete gentil mais la ....
1841
	spip_log("generer_url_entite: entite $entite ($f) inconnue $type $public");
1842
1843
	return '';
1844
}
1845
1846
function generer_url_ecrire_entite_edit($id, $entite, $args = '', $ancre = '') {
1847
	$exec = objet_info($entite, 'url_edit');
1848
	$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 1847 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...
1849
	if (intval($id)) {
1850
		$url = parametre_url($url, id_table_objet($entite), $id);
1851
	} else {
1852
		$url = parametre_url($url, 'new', 'oui');
1853
	}
1854
	if ($ancre) {
1855
		$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...
1856
	}
1857
1858
	return $url;
1859
}
1860
1861
// https://code.spip.net/@urls_connect_dist
1862
function urls_connect_dist($i, &$entite, $args = '', $ancre = '', $public = null) {
1863
	include_spip('base/connect_sql');
1864
	$id_type = id_table_objet($entite, $public);
1865
1866
	return _DIR_RACINE . get_spip_script('./')
1867
	. "?" . _SPIP_PAGE . "=$entite&$id_type=$i&connect=$public"
1868
	. (!$args ? '' : "&$args")
1869
	. (!$ancre ? '' : "#$ancre");
1870
}
1871
1872
1873
/**
1874
 * Transformer les caractères utf8 d'une URL (farsi par exemple) selon la RFC 1738
1875
 *
1876
 * @param string $url
1877
 * @return string
1878
 */
1879
function urlencode_1738($url) {
1880
	if (preg_match(',[^\x00-\x7E],sS', $url)) {
1881
		$uri = '';
1882
		for ($i = 0; $i < strlen($url); $i++) {
1883
			if (ord($a = $url[$i]) > 127) {
1884
				$a = rawurlencode($a);
1885
			}
1886
			$uri .= $a;
1887
		}
1888
		$url = $uri;
1889
	}
1890
1891
	return quote_amp($url);
1892
}
1893
1894
// https://code.spip.net/@generer_url_entite_absolue
1895
function generer_url_entite_absolue($id = '', $entite = '', $args = '', $ancre = '', $connect = null) {
1896
	if (!$connect) {
1897
		$connect = true;
1898
	}
1899
	$h = generer_url_entite($id, $entite, $args, $ancre, $connect);
1900
	if (!preg_match(',^\w+:,', $h)) {
1901
		include_spip('inc/filtres_mini');
1902
		$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...
1903
	}
1904
1905
	return $h;
1906
}
1907
1908
1909
/**
1910
 * Tester qu'une variable d'environnement est active
1911
 *
1912
 * Sur certains serveurs, la valeur 'Off' tient lieu de false dans certaines
1913
 * variables d'environnement comme `$_SERVER['HTTPS']` ou `ini_get('display_errors')`
1914
 *
1915
 * @param string|bool $truc
1916
 *     La valeur de la variable d'environnement
1917
 * @return bool
1918
 *     true si la valeur est considérée active ; false sinon.
1919
 **/
1920
function test_valeur_serveur($truc) {
1921
	if (!$truc) {
1922
		return false;
1923
	}
1924
1925
	return (strtolower($truc) !== 'off');
1926
}
1927
1928
//
1929
// Fonctions de fabrication des URL des scripts de Spip
1930
//
1931
/**
1932
 * Calcule l'url de base du site
1933
 *
1934
 * Calcule l'URL de base du site, en priorité sans se fier à la méta (adresse_site) qui
1935
 * peut être fausse (sites avec plusieurs noms d’hôtes, déplacements, erreurs).
1936
 * En dernier recours, lorsqu'on ne trouve rien, on utilise adresse_site comme fallback.
1937
 *
1938
 * @note
1939
 *     La globale `$profondeur_url` doit être initialisée de manière à
1940
 *     indiquer le nombre de sous-répertoires de l'url courante par rapport à la
1941
 *     racine de SPIP : par exemple, sur ecrire/ elle vaut 1, sur sedna/ 1, et à
1942
 *     la racine 0. Sur url/perso/ elle vaut 2
1943
 *
1944
 * @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...
1945
 *    - si non renseignée : retourne l'url pour la profondeur $GLOBALS['profondeur_url']
1946
 *    - si int : indique que l'on veut l'url pour la profondeur indiquée
1947
 *    - si bool : retourne le tableau static complet
1948
 *    - si array : réinitialise le tableau static complet avec la valeur fournie
1949
 * @return string|array
1950
 */
1951
function url_de_base($profondeur = null) {
1952
1953
	static $url = array();
1954
	if (is_array($profondeur)) {
1955
		return $url = $profondeur;
1956
	}
1957
	if ($profondeur === false) {
1958
		return $url;
1959
	}
1960
1961
	if (is_null($profondeur)) {
1962
		$profondeur = $GLOBALS['profondeur_url'];
1963
	}
1964
1965
	if (isset($url[$profondeur])) {
1966
		return $url[$profondeur];
1967
	}
1968
1969
	$http = 'http';
1970
1971
	if (
1972
		isset($_SERVER["SCRIPT_URI"])
1973
		and substr($_SERVER["SCRIPT_URI"], 0, 5) == 'https'
1974
	) {
1975
		$http = 'https';
1976
	} elseif (
1977
		isset($_SERVER['HTTPS'])
1978
		and test_valeur_serveur($_SERVER['HTTPS'])
1979
	) {
1980
		$http = 'https';
1981
	}
1982
1983
	// note : HTTP_HOST contient le :port si necessaire
1984
	$host = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : null;
1985
	// si on n'a pas trouvé d'hôte du tout, en dernier recours on utilise adresse_site comme fallback
1986
	if (is_null($host) and isset($GLOBALS['meta']['adresse_site'])) {
1987
		$host = $GLOBALS['meta']['adresse_site'];
1988
		if ($scheme = parse_url($host, PHP_URL_SCHEME)) {
1989
			$http = $scheme;
1990
			$host = str_replace("{$scheme}://", '', $host);
1991
		}
1992
	}
1993
	if (isset($_SERVER['SERVER_PORT'])
1994
		and $port = $_SERVER['SERVER_PORT']
1995
		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...
1996
	) {
1997
		if (!defined('_PORT_HTTP_STANDARD')) {
1998
			define('_PORT_HTTP_STANDARD', '80');
1999
		}
2000
		if (!defined('_PORT_HTTPS_STANDARD')) {
2001
			define('_PORT_HTTPS_STANDARD', '443');
2002
		}
2003
		if ($http == "http" and !in_array($port, explode(',', _PORT_HTTP_STANDARD))) {
2004
			$host .= ":$port";
2005
		}
2006
		if ($http == "https" and !in_array($port, explode(',', _PORT_HTTPS_STANDARD))) {
2007
			$host .= ":$port";
2008
		}
2009
	}
2010
2011 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...
2012
		if (isset($_SERVER['REQUEST_URI'])) {
2013
			$GLOBALS['REQUEST_URI'] = $_SERVER['REQUEST_URI'];
2014
		} else {
2015
			$GLOBALS['REQUEST_URI'] = (php_sapi_name() !== 'cli') ? $_SERVER['PHP_SELF'] : '';
2016
			if (!empty($_SERVER['QUERY_STRING'])
2017
				and !strpos($_SERVER['REQUEST_URI'], '?')
2018
			) {
2019
				$GLOBALS['REQUEST_URI'] .= '?' . $_SERVER['QUERY_STRING'];
2020
			}
2021
		}
2022
	}
2023
2024
	$url[$profondeur] = url_de_($http, $host, $GLOBALS['REQUEST_URI'], $profondeur);
2025
2026
	return $url[$profondeur];
2027
}
2028
2029
/**
2030
 * fonction testable de construction d'une url appelee par url_de_base()
2031
 *
2032
 * @param string $http
2033
 * @param string $host
2034
 * @param string $request
2035
 * @param int $prof
2036
 * @return string
2037
 */
2038
function url_de_($http, $host, $request, $prof = 0) {
2039
	$prof = max($prof, 0);
2040
2041
	$myself = ltrim($request, '/');
2042
	# supprimer la chaine de GET
2043
	list($myself) = explode('?', $myself);
2044
	// vieux mode HTTP qui envoie après le nom de la methode l'URL compléte
2045
	// protocole, "://", nom du serveur avant le path dans _SERVER["REQUEST_URI"]
2046 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...
2047
		$myself = explode('://',$myself);
2048
		array_shift($myself);
2049
		$myself = implode('://',$myself);
2050
		$myself = explode('/',$myself);
2051
		array_shift($myself);
2052
		$myself = implode('/',$myself);
2053
	}
2054
	$url = join('/', array_slice(explode('/', $myself), 0, -1 - $prof)) . '/';
2055
2056
	$url = $http . '://' . rtrim($host, '/') . '/' . ltrim($url, '/');
2057
2058
	return $url;
2059
}
2060
2061
2062
// Pour une redirection, la liste des arguments doit etre separee par "&"
2063
// Pour du code XHTML, ca doit etre &amp;
2064
// Bravo au W3C qui n'a pas ete capable de nous eviter ca
2065
// faute de separer proprement langage et meta-langage
2066
2067
// Attention, X?y=z et "X/?y=z" sont completement differents!
2068
// http://httpd.apache.org/docs/2.0/mod/mod_dir.html
2069
2070
/**
2071
 * Crée une URL vers un script de l'espace privé
2072
 *
2073
 * @example
2074
 *     ```
2075
 *     generer_url_ecrire('admin_plugin')
2076
 *     ```
2077
 *
2078
 * @param string $script
2079
 *     Nom de la page privée (xx dans exec=xx)
2080
 * @param string $args
2081
 *     Arguments à transmettre, tel que `arg1=yy&arg2=zz`
2082
 * @param bool $no_entities
2083
 *     Si false : transforme les `&` en `&amp;`
2084
 * @param bool|string $rel
2085
 *     URL relative ?
2086
 *
2087
 *     - false : l’URL sera complète et contiendra l’URL du site
2088
 *     - true : l’URL sera relavive.
2089
 *     - string : on transmet l'url à la fonction
2090
 * @return string URL
2091
 **/
2092
function generer_url_ecrire($script = '', $args = "", $no_entities = false, $rel = false) {
2093
	if (!$rel) {
2094
		$rel = url_de_base() . _DIR_RESTREINT_ABS . _SPIP_ECRIRE_SCRIPT;
2095
	} else {
2096
		if (!is_string($rel)) {
2097
			$rel = _DIR_RESTREINT ? _DIR_RESTREINT :
2098
				('./' . _SPIP_ECRIRE_SCRIPT);
2099
		}
2100
	}
2101
2102
	list($script, $ancre) = array_pad(explode('#', $script), 2, null);
2103
	if ($script and ($script <> 'accueil' or $rel)) {
2104
		$args = "?exec=$script" . (!$args ? '' : "&$args");
2105
	} elseif ($args) {
2106
		$args = "?$args";
2107
	}
2108
	if ($ancre) {
2109
		$args .= "#$ancre";
2110
	}
2111
2112
	return $rel . ($no_entities ? $args : str_replace('&', '&amp;', $args));
2113
}
2114
2115
//
2116
// Adresse des scripts publics (a passer dans inc-urls...)
2117
//
2118
2119
2120
/**
2121
 * Retourne le nom du fichier d'exécution de SPIP
2122
 *
2123
 * @see _SPIP_SCRIPT
2124
 * @note
2125
 *   Detecter le fichier de base, a la racine, comme etant spip.php ou ''
2126
 *   dans le cas de '', un $default = './' peut servir (comme dans urls/page.php)
2127
 *
2128
 * @param string $default
2129
 *     Script par défaut
2130
 * @return string
2131
 *     Nom du fichier (constante _SPIP_SCRIPT), sinon nom par défaut
2132
 **/
2133
function get_spip_script($default = '') {
2134
	# cas define('_SPIP_SCRIPT', '');
2135
	if (_SPIP_SCRIPT) {
2136
		return _SPIP_SCRIPT;
2137
	} else {
2138
		return $default;
2139
	}
2140
}
2141
2142
/**
2143
 * Crée une URL vers une page publique de SPIP
2144
 *
2145
 * @example
2146
 *     ```
2147
 *     generer_url_public("rubrique","id_rubrique=$id_rubrique")
2148
 *     ```
2149
 *
2150
 * @param string $script
2151
 *     Nom de la page
2152
 * @param string|array $args
2153
 *     Arguments à transmettre a l'URL,
2154
 *      soit sous la forme d'un string tel que `arg1=yy&arg2=zz`
2155
 *      soit sous la forme d'un array tel que array( `arg1` => `yy`, `arg2` => `zz` )
2156
 * @param bool $no_entities
2157
 *     Si false : transforme les `&` en `&amp;`
2158
 * @param bool $rel
2159
 *     URL relative ?
2160
 *
2161
 *     - false : l’URL sera complète et contiendra l’URL du site
2162
 *     - true : l’URL sera relavive.
2163
 * @param string $action
2164
 *     - Fichier d'exécution public (spip.php par défaut)
2165
 * @return string URL
2166
 **/
2167
function generer_url_public($script = '', $args = "", $no_entities = false, $rel = true, $action = '') {
2168
	// si le script est une action (spip_pass, spip_inscription),
2169
	// standardiser vers la nouvelle API
2170
2171
	if (!$action) {
2172
		$action = get_spip_script();
2173
	}
2174
	if ($script) {
2175
		$action = parametre_url($action, _SPIP_PAGE, $script, '&');
2176
	}
2177
2178
	if ($args) {
2179
		if (is_array($args)) {
2180
			$r = '';
2181
			foreach ($args as $k => $v) {
2182
				$r .= '&' . $k . '=' . $v;
2183
			}
2184
			$args = substr($r, 1);
2185
		}
2186
		$action .=
2187
			(strpos($action, '?') !== false ? '&' : '?') . $args;
2188
	}
2189
	if (!$no_entities) {
2190
		$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...
2191
	}
2192
2193
	// ne pas generer une url avec /./?page= en cas d'url absolue et de _SPIP_SCRIPT vide
2194
	return ($rel ? _DIR_RACINE . $action : rtrim(url_de_base(), '/') . preg_replace(",^/[.]/,", "/", "/$action"));
2195
}
2196
2197
// https://code.spip.net/@generer_url_prive
2198
function generer_url_prive($script, $args = "", $no_entities = false) {
2199
2200
	return generer_url_public($script, $args, $no_entities, false, _DIR_RESTREINT_ABS . 'prive.php');
2201
}
2202
2203
// Pour les formulaires en methode POST,
2204
// mettre le nom du script a la fois en input-hidden et dans le champ action:
2205
// 1) on peut ainsi memoriser le signet comme si c'etait un GET
2206
// 2) ca suit http://en.wikipedia.org/wiki/Representational_State_Transfer
2207
2208
/**
2209
 * Retourne un formulaire (POST par défaut) vers un script exec
2210
 * de l’interface privée
2211
 *
2212
 * @param string $script
2213
 *     Nom de la page exec
2214
 * @param string $corps
2215
 *     Contenu du formulaire
2216
 * @param string $atts
2217
 *     Si présent, remplace les arguments par défaut (method=post) par ceux indiqués
2218
 * @param string $submit
2219
 *     Si indiqué, un bouton de soumission est créé avec texte sa valeur.
2220
 * @return string
2221
 *     Code HTML du formulaire
2222
 **/
2223
function generer_form_ecrire($script, $corps, $atts = '', $submit = '') {
2224
2225
	$script1 = explode('&', $script);
2226
	$script1 = reset($script1);
2227
2228
	return "<form action='"
2229
	. ($script ? generer_url_ecrire($script) : '')
2230
	. "' "
2231
	. ($atts ? $atts : " method='post'")
2232
	. "><div>\n"
2233
	. "<input type='hidden' name='exec' value='$script1' />"
2234
	. $corps
2235
	. (!$submit ? '' :
2236
		("<div style='text-align: " . $GLOBALS['spip_lang_right'] . "'><input class='fondo submit btn' type='submit' value=\"" . entites_html($submit) . "\" /></div>"))
2237
	. "</div></form>\n";
2238
}
2239
2240
/**
2241
 * Générer un formulaire pour lancer une action vers $script
2242
 *
2243
 * Attention, JS/Ajax n'aime pas le melange de param GET/POST
2244
 * On n'applique pas la recommandation ci-dessus pour les scripts publics
2245
 * qui ne sont pas destines a etre mis en signets
2246
 *
2247
 * @param string $script
2248
 * @param string $corps
2249
 * @param string $atts
2250
 * @param bool $public
2251
 * @return string
2252
 */
2253
function generer_form_action($script, $corps, $atts = '', $public = false) {
2254
	// si l'on est dans l'espace prive, on garde dans l'url
2255
	// l'exec a l'origine de l'action, qui permet de savoir si il est necessaire
2256
	// ou non de proceder a l'authentification (cas typique de l'install par exemple)
2257
	$h = (_DIR_RACINE and !$public)
2258
		? generer_url_ecrire(_request('exec'))
2259
		: generer_url_public();
2260
2261
	return "\n<form action='" .
2262
	$h .
2263
	"'" .
2264
	$atts .
2265
	">\n" .
2266
	"<div>" .
2267
	"\n<input type='hidden' name='action' value='$script' />" .
2268
	$corps .
2269
	"</div></form>";
2270
}
2271
2272
/**
2273
 * Créer une URL
2274
 *
2275
 * @param  string $script
2276
 *     Nom du script à exécuter
2277
 * @param  string $args
2278
 *     Arguments à transmettre a l'URL sous la forme `arg1=yy&arg2=zz`
2279
 * @param bool $no_entities
2280
 *     Si false : transforme les & en &amp;
2281
 * @param boolean $public
2282
 *     URL relative ? false : l’URL sera complète et contiendra l’URL du site.
2283
 *     true : l’URL sera relative.
2284
 * @return string
2285
 *     URL
2286
 */
2287
function generer_url_action($script, $args = "", $no_entities = false, $public = false) {
2288
	// si l'on est dans l'espace prive, on garde dans l'url
2289
	// l'exec a l'origine de l'action, qui permet de savoir si il est necessaire
2290
	// ou non de proceder a l'authentification (cas typique de l'install par exemple)
2291
	$url = (_DIR_RACINE and !$public)
2292
		? generer_url_ecrire(_request('exec'))
2293
		: generer_url_public('', '', false, false);
2294
	$url = parametre_url($url, 'action', $script);
2295
	if ($args) {
2296
		$url .= quote_amp('&' . $args);
2297
	}
2298
2299
	if ($no_entities) {
2300
		$url = str_replace('&amp;', '&', $url);
2301
	}
2302
2303
	return $url;
2304
}
2305
2306
2307
/**
2308
 * Fonction d'initialisation groupée pour compatibilité ascendante
2309
 *
2310
 * @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...
2311
 * @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...
2312
 * @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...
2313
 * @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...
2314
 */
2315
function spip_initialisation($pi = null, $pa = null, $ti = null, $ta = null) {
2316
	spip_initialisation_core($pi, $pa, $ti, $ta);
2317
	spip_initialisation_suite();
2318
}
2319
2320
/**
2321
 * Fonction d'initialisation, appellée dans inc_version ou mes_options
2322
 *
2323
 * Elle définit les répertoires et fichiers non partageables
2324
 * et indique dans $test_dirs ceux devant être accessibles en écriture
2325
 * mais ne touche pas à cette variable si elle est déjà définie
2326
 * afin que mes_options.php puisse en spécifier d'autres.
2327
 *
2328
 * Elle définit ensuite les noms des fichiers et les droits.
2329
 * Puis simule un register_global=on sécurisé.
2330
 *
2331
 * @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...
2332
 * @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...
2333
 * @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...
2334
 * @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...
2335
 */
2336
function spip_initialisation_core($pi = null, $pa = null, $ti = null, $ta = null) {
2337
	static $too_late = 0;
2338
	if ($too_late++) {
2339
		return;
2340
	}
2341
2342
	// Declaration des repertoires
2343
2344
	// le nom du repertoire plugins/ activables/desactivables
2345
	if (!defined('_DIR_PLUGINS')) {
2346
		define('_DIR_PLUGINS', _DIR_RACINE . "plugins/");
2347
	}
2348
2349
	// le nom du repertoire des extensions/ permanentes du core, toujours actives
2350
	if (!defined('_DIR_PLUGINS_DIST')) {
2351
		define('_DIR_PLUGINS_DIST', _DIR_RACINE . "plugins-dist/");
2352
	}
2353
2354
	// le nom du repertoire des librairies
2355
	if (!defined('_DIR_LIB')) {
2356
		define('_DIR_LIB', _DIR_RACINE . "lib/");
2357
	}
2358
2359
	if (!defined('_DIR_IMG')) {
2360
		define('_DIR_IMG', $pa);
2361
	}
2362
	if (!defined('_DIR_LOGOS')) {
2363
		define('_DIR_LOGOS', $pa);
2364
	}
2365
	if (!defined('_DIR_IMG_ICONES')) {
2366
		define('_DIR_IMG_ICONES', _DIR_LOGOS . "icones/");
2367
	}
2368
2369
	if (!defined('_DIR_DUMP')) {
2370
		define('_DIR_DUMP', $ti . "dump/");
2371
	}
2372
	if (!defined('_DIR_SESSIONS')) {
2373
		define('_DIR_SESSIONS', $ti . "sessions/");
2374
	}
2375
	if (!defined('_DIR_TRANSFERT')) {
2376
		define('_DIR_TRANSFERT', $ti . "upload/");
2377
	}
2378
	if (!defined('_DIR_CACHE')) {
2379
		define('_DIR_CACHE', $ti . "cache/");
2380
	}
2381
	if (!defined('_DIR_CACHE_XML')) {
2382
		define('_DIR_CACHE_XML', _DIR_CACHE . "xml/");
2383
	}
2384
	if (!defined('_DIR_SKELS')) {
2385
		define('_DIR_SKELS', _DIR_CACHE . "skel/");
2386
	}
2387
	if (!defined('_DIR_AIDE')) {
2388
		define('_DIR_AIDE', _DIR_CACHE . "aide/");
2389
	}
2390
	if (!defined('_DIR_TMP')) {
2391
		define('_DIR_TMP', $ti);
2392
	}
2393
2394
	if (!defined('_DIR_VAR')) {
2395
		define('_DIR_VAR', $ta);
2396
	}
2397
2398
	if (!defined('_DIR_ETC')) {
2399
		define('_DIR_ETC', $pi);
2400
	}
2401
	if (!defined('_DIR_CONNECT')) {
2402
		define('_DIR_CONNECT', $pi);
2403
	}
2404
	if (!defined('_DIR_CHMOD')) {
2405
		define('_DIR_CHMOD', $pi);
2406
	}
2407
2408
	if (!isset($GLOBALS['test_dirs']))
2409
		// Pas $pi car il est bon de le mettre hors ecriture apres intstall
2410
		// il sera rajoute automatiquement si besoin a l'etape 2 de l'install
2411
	{
2412
		$GLOBALS['test_dirs'] = array($pa, $ti, $ta);
2413
	}
2414
2415
	// Declaration des fichiers
2416
2417
	if (!defined('_CACHE_PLUGINS_PATH')) {
2418
		define('_CACHE_PLUGINS_PATH', _DIR_CACHE . "charger_plugins_chemins.php");
2419
	}
2420
	if (!defined('_CACHE_PLUGINS_OPT')) {
2421
		define('_CACHE_PLUGINS_OPT', _DIR_CACHE . "charger_plugins_options.php");
2422
	}
2423
	if (!defined('_CACHE_PLUGINS_FCT')) {
2424
		define('_CACHE_PLUGINS_FCT', _DIR_CACHE . "charger_plugins_fonctions.php");
2425
	}
2426
	if (!defined('_CACHE_PIPELINES')) {
2427
		define('_CACHE_PIPELINES', _DIR_CACHE . "charger_pipelines.php");
2428
	}
2429
	if (!defined('_CACHE_CHEMIN')) {
2430
		define('_CACHE_CHEMIN', _DIR_CACHE . "chemin.txt");
2431
	}
2432
2433
	# attention .php obligatoire pour ecrire_fichier_securise
2434
	if (!defined('_FILE_META')) {
2435
		define('_FILE_META', $ti . 'meta_cache.php');
2436
	}
2437
	if (!defined('_DIR_LOG')) {
2438
		define('_DIR_LOG', _DIR_TMP . 'log/');
2439
	}
2440
	if (!defined('_FILE_LOG')) {
2441
		define('_FILE_LOG', 'spip');
2442
	}
2443
	if (!defined('_FILE_LOG_SUFFIX')) {
2444
		define('_FILE_LOG_SUFFIX', '.log');
2445
	}
2446
2447
	// Le fichier de connexion a la base de donnees
2448
	// tient compte des anciennes versions (inc_connect...)
2449
	if (!defined('_FILE_CONNECT_INS')) {
2450
		define('_FILE_CONNECT_INS', 'connect');
2451
	}
2452
	if (!defined('_FILE_CONNECT')) {
2453
		define('_FILE_CONNECT',
2454
		(@is_readable($f = _DIR_CONNECT . _FILE_CONNECT_INS . '.php') ? $f
2455
			: (@is_readable($f = _DIR_RESTREINT . 'inc_connect.php') ? $f
2456
				: false)));
2457
	}
2458
2459
	// Le fichier de reglages des droits
2460
	if (!defined('_FILE_CHMOD_INS')) {
2461
		define('_FILE_CHMOD_INS', 'chmod');
2462
	}
2463
	if (!defined('_FILE_CHMOD')) {
2464
		define('_FILE_CHMOD',
2465
		(@is_readable($f = _DIR_CHMOD . _FILE_CHMOD_INS . '.php') ? $f
2466
			: false));
2467
	}
2468
2469
	if (!defined('_FILE_LDAP')) {
2470
		define('_FILE_LDAP', 'ldap.php');
2471
	}
2472
2473
	if (!defined('_FILE_TMP_SUFFIX')) {
2474
		define('_FILE_TMP_SUFFIX', '.tmp.php');
2475
	}
2476
	if (!defined('_FILE_CONNECT_TMP')) {
2477
		define('_FILE_CONNECT_TMP', _DIR_CONNECT . _FILE_CONNECT_INS . _FILE_TMP_SUFFIX);
2478
	}
2479
	if (!defined('_FILE_CHMOD_TMP')) {
2480
		define('_FILE_CHMOD_TMP', _DIR_CHMOD . _FILE_CHMOD_INS . _FILE_TMP_SUFFIX);
2481
	}
2482
2483
	// Definition des droits d'acces en ecriture
2484
	if (!defined('_SPIP_CHMOD') and _FILE_CHMOD) {
2485
		include_once _FILE_CHMOD;
2486
	}
2487
2488
	// Se mefier des fichiers mal remplis!
2489
	if (!defined('_SPIP_CHMOD')) {
2490
		define('_SPIP_CHMOD', 0777);
2491
	}
2492
2493
	if (!defined('_DEFAULT_CHARSET')) {
2494
		/** Le charset par défaut lors de l'installation */
2495
		define('_DEFAULT_CHARSET', 'utf-8');
2496
	}
2497
	if (!defined('_ROOT_PLUGINS')) {
2498
		define('_ROOT_PLUGINS', _ROOT_RACINE . "plugins/");
2499
	}
2500
	if (!defined('_ROOT_PLUGINS_DIST')) {
2501
		define('_ROOT_PLUGINS_DIST', _ROOT_RACINE . "plugins-dist/");
2502
	}
2503
	if (!defined('_ROOT_PLUGINS_SUPPL') && defined('_DIR_PLUGINS_SUPPL') && _DIR_PLUGINS_SUPPL) {
2504
		define('_ROOT_PLUGINS_SUPPL', _ROOT_RACINE . str_replace(_DIR_RACINE, '', _DIR_PLUGINS_SUPPL));
2505
	}
2506
2507
	// La taille des Log
2508
	if (!defined('_MAX_LOG')) {
2509
		define('_MAX_LOG', 100);
2510
	}
2511
2512
	// Sommes-nous dans l'empire du Mal ?
2513
	// (ou sous le signe du Pingouin, ascendant GNU ?)
2514
	if (isset($_SERVER['SERVER_SOFTWARE']) and strpos($_SERVER['SERVER_SOFTWARE'], '(Win') !== false) {
2515
		if (!defined('_OS_SERVEUR')) {
2516
			define('_OS_SERVEUR', 'windows');
2517
		}
2518
		if (!defined('_SPIP_LOCK_MODE')) {
2519
			define('_SPIP_LOCK_MODE', 1);
2520
		} // utiliser le flock php
2521
	} else {
2522
		if (!defined('_OS_SERVEUR')) {
2523
			define('_OS_SERVEUR', '');
2524
		}
2525
		if (!defined('_SPIP_LOCK_MODE')) {
2526
			define('_SPIP_LOCK_MODE', 1);
2527
		} // utiliser le flock php
2528
		#if (!defined('_SPIP_LOCK_MODE')) define('_SPIP_LOCK_MODE',2); // utiliser le nfslock de spip mais link() est tres souvent interdite
2529
	}
2530
2531
	// Langue par defaut
2532
	if (!defined('_LANGUE_PAR_DEFAUT')) {
2533
		define('_LANGUE_PAR_DEFAUT', 'fr');
2534
	}
2535
2536
	//
2537
	// Module de lecture/ecriture/suppression de fichiers utilisant flock()
2538
	// (non surchargeable en l'etat ; attention si on utilise include_spip()
2539
	// pour le rendre surchargeable, on va provoquer un reecriture
2540
	// systematique du noyau ou une baisse de perfs => a etudier)
2541
	include_once _ROOT_RESTREINT . 'inc/flock.php';
2542
2543
	// charger tout de suite le path et son cache
2544
	load_path_cache();
2545
2546
	// *********** traiter les variables ************
2547
2548
	//
2549
	// Securite
2550
	//
2551
2552
	// Ne pas se faire manger par un bug php qui accepte ?GLOBALS[truc]=toto
2553
	if (isset($_REQUEST['GLOBALS'])) {
2554
		die();
2555
	}
2556
	// nettoyer les magic quotes \' et les caracteres nuls %00
2557
	spip_desinfecte($_GET);
2558
	spip_desinfecte($_POST);
2559
	spip_desinfecte($_COOKIE);
2560
	spip_desinfecte($_REQUEST);
2561
2562
	// appliquer le cookie_prefix
2563
	if ($GLOBALS['cookie_prefix'] != 'spip') {
2564
		include_spip('inc/cookie');
2565
		recuperer_cookies_spip($GLOBALS['cookie_prefix']);
2566
	}
2567
2568
	//
2569
	// Capacites php (en fonction de la version)
2570
	//
2571
	$GLOBALS['flag_ob'] = (function_exists("ob_start")
2572
		&& function_exists("ini_get")
2573
		&& !strstr(@ini_get('disable_functions'), 'ob_'));
2574
	$GLOBALS['flag_sapi_name'] = function_exists("php_sapi_name");
2575
	$GLOBALS['flag_get_cfg_var'] = (@get_cfg_var('error_reporting') != "");
2576
	$GLOBALS['flag_upload'] = (!$GLOBALS['flag_get_cfg_var'] ||
2577
		(get_cfg_var('upload_max_filesize') > 0));
2578
2579
2580
	// Compatibilite avec serveurs ne fournissant pas $REQUEST_URI
2581 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...
2582
		$GLOBALS['REQUEST_URI'] = $_SERVER['REQUEST_URI'];
2583
	} else {
2584
		$GLOBALS['REQUEST_URI'] = (php_sapi_name() !== 'cli') ? $_SERVER['PHP_SELF'] : '';
2585
		if (!empty($_SERVER['QUERY_STRING'])
2586
			and !strpos($_SERVER['REQUEST_URI'], '?')
2587
		) {
2588
			$GLOBALS['REQUEST_URI'] .= '?' . $_SERVER['QUERY_STRING'];
2589
		}
2590
	}
2591
2592
	// Duree de validite de l'alea pour les cookies et ce qui s'ensuit.
2593
	if (!defined('_RENOUVELLE_ALEA')) {
2594
		define('_RENOUVELLE_ALEA', 12 * 3600);
2595
	}
2596
	if (!defined('_DUREE_COOKIE_ADMIN')) {
2597
		define('_DUREE_COOKIE_ADMIN', 14 * 24 * 3600);
2598
	}
2599
2600
	// charger les meta si possible et renouveller l'alea au besoin
2601
	// charge aussi effacer_meta et ecrire_meta
2602
	$inc_meta = charger_fonction('meta', 'inc');
2603
	$inc_meta();
2604
2605
	// nombre de repertoires depuis la racine
2606
	// on compare a l'adresse de spip.php : $_SERVER["SCRIPT_NAME"]
2607
	// ou a defaut celle donnee en meta ; (mais si celle-ci est fausse
2608
	// le calcul est faux)
2609
	if (!_DIR_RESTREINT) {
2610
		$GLOBALS['profondeur_url'] = 1;
2611
	} else {
2612
		$uri = isset($_SERVER['REQUEST_URI']) ? explode('?', $_SERVER['REQUEST_URI']) : '';
2613
		$uri_ref = $_SERVER["SCRIPT_NAME"];
2614
		if (!$uri_ref
2615
			// si on est appele avec un autre ti, on est sans doute en mutu
2616
			// si jamais c'est de la mutu avec sous rep, on est perdu si on se fie
2617
			// a spip.php qui est a la racine du spip, et vue qu'on sait pas se reperer
2618
			// s'en remettre a l'adresse du site. alea jacta est.
2619
			or $ti !== _NOM_TEMPORAIRES_INACCESSIBLES
2620
		) {
2621
2622
			if (isset($GLOBALS['meta']['adresse_site'])) {
2623
				$uri_ref = parse_url($GLOBALS['meta']['adresse_site']);
2624
				$uri_ref = (isset($uri_ref['path']) ? $uri_ref['path'] : '') . '/';
2625
			} else {
2626
				$uri_ref = "";
2627
			}
2628
		}
2629
		if (!$uri or !$uri_ref) {
2630
			$GLOBALS['profondeur_url'] = 0;
2631
		} else {
2632
			$GLOBALS['profondeur_url'] = max(0,
2633
				substr_count($uri[0], '/')
2634
				- substr_count($uri_ref, '/'));
2635
		}
2636
	}
2637
	// s'il y a un cookie ou PHP_AUTH, initialiser visiteur_session
2638
	if (_FILE_CONNECT) {
2639
		if (verifier_visiteur() == '0minirezo'
2640
			// si c'est un admin sans cookie admin, il faut ignorer le cache chemin !
2641
			and !isset($_COOKIE['spip_admin'])
2642
		) {
2643
			clear_path_cache();
2644
		}
2645
	}
2646
2647
}
2648
2649
/**
2650
 * Complements d'initialisation non critiques pouvant etre realises
2651
 * par les plugins
2652
 *
2653
 */
2654
function spip_initialisation_suite() {
2655
	static $too_late = 0;
2656
	if ($too_late++) {
2657
		return;
2658
	}
2659
2660
	// taille mini des login
2661
	if (!defined('_LOGIN_TROP_COURT')) {
2662
		define('_LOGIN_TROP_COURT', 4);
2663
	}
2664
2665
	// la taille maxi des logos (0 : pas de limite) (pas de define par defaut, ce n'est pas utile)
2666
	#if (!defined('_LOGO_MAX_SIZE')) define('_LOGO_MAX_SIZE', 0); # poids en ko
2667
	#if (!defined('_LOGO_MAX_WIDTH')) define('_LOGO_MAX_WIDTH', 0); # largeur en pixels
2668
	#if (!defined('_LOGO_MAX_HEIGHT')) define('_LOGO_MAX_HEIGHT', 0); # hauteur en pixels
2669
2670
	// la taille maxi des images (0 : pas de limite) (pas de define par defaut, ce n'est pas utile)
2671
	#if (!defined('_DOC_MAX_SIZE')) define('_DOC_MAX_SIZE', 0); # poids en ko
2672
	#if (!defined('_IMG_MAX_SIZE')) define('_IMG_MAX_SIZE', 0); # poids en ko
2673
	#if (!defined('_IMG_MAX_WIDTH')) define('_IMG_MAX_WIDTH', 0); # largeur en pixels
2674
	#if (!defined('_IMG_MAX_HEIGHT')) define('_IMG_MAX_HEIGHT', 0); # hauteur en pixels
2675
2676
	if (!defined('_PASS_LONGUEUR_MINI')) {
2677
		define('_PASS_LONGUEUR_MINI', 6);
2678
	}
2679
2680
2681
	// Qualite des images calculees automatiquement. C'est un nombre entre 0 et 100, meme pour imagick (on ramene a 0..1 par la suite)
2682
	if (!defined('_IMG_QUALITE')) {
2683
		define('_IMG_QUALITE', 85);
2684
	} # valeur par defaut
2685
	if (!defined('_IMG_GD_QUALITE')) {
2686
		define('_IMG_GD_QUALITE', _IMG_QUALITE);
2687
	} # surcharge pour la lib GD
2688
	if (!defined('_IMG_CONVERT_QUALITE')) {
2689
		define('_IMG_CONVERT_QUALITE', _IMG_QUALITE);
2690
	} # surcharge pour imagick en ligne de commande
2691
	// Historiquement la valeur pour imagick semble differente. Si ca n'est pas necessaire, il serait preferable de garder _IMG_QUALITE
2692
	if (!defined('_IMG_IMAGICK_QUALITE')) {
2693
		define('_IMG_IMAGICK_QUALITE', 75);
2694
	} # surcharge pour imagick en PHP
2695
2696
	if (!defined('_COPIE_LOCALE_MAX_SIZE')) {
2697
		define('_COPIE_LOCALE_MAX_SIZE', 33554432);
2698
	} // poids en octet
2699
2700
	// qq chaines standard
2701
	if (!defined('_ACCESS_FILE_NAME')) {
2702
		define('_ACCESS_FILE_NAME', '.htaccess');
2703
	}
2704
	if (!defined('_AUTH_USER_FILE')) {
2705
		define('_AUTH_USER_FILE', '.htpasswd');
2706
	}
2707
	if (!defined('_SPIP_DUMP')) {
2708
		define('_SPIP_DUMP', 'dump@nom_site@@[email protected]');
2709
	}
2710
	if (!defined('_CACHE_RUBRIQUES')) {
2711
		/** Fichier cache pour le navigateur de rubrique du bandeau */
2712
		define('_CACHE_RUBRIQUES', _DIR_TMP . 'menu-rubriques-cache.txt');
2713
	}
2714
	if (!defined('_CACHE_RUBRIQUES_MAX')) {
2715
		/** Nombre maxi de rubriques enfants affichées pour chaque rubrique du navigateur de rubrique du bandeau */
2716
		define('_CACHE_RUBRIQUES_MAX', 500);
2717
	}
2718
2719
	if (!defined('_CACHE_CONTEXTES_AJAX_SUR_LONGUEUR')) {
2720
		/**
2721
		 * Basculer les contextes ajax en fichier si la longueur d’url est trop grande
2722
		 * @var int Nombre de caractères */
2723
		define('_CACHE_CONTEXTES_AJAX_SUR_LONGUEUR', 2000);
2724
	}
2725
2726
	if (!defined('_EXTENSION_SQUELETTES')) {
2727
		define('_EXTENSION_SQUELETTES', 'html');
2728
	}
2729
2730
	if (!defined('_DOCTYPE_ECRIRE')) {
2731
		/** Définit le doctype de l’espace privé */
2732
		define('_DOCTYPE_ECRIRE', "<!DOCTYPE html>\n");
2733
	}
2734
	if (!defined('_DOCTYPE_AIDE')) {
2735
		/** Définit le doctype de l’aide en ligne */
2736
		define('_DOCTYPE_AIDE',
2737
		"<!DOCTYPE html PUBLIC '-//W3C//DTD HTML 4.01 Frameset//EN' 'http://www.w3.org/TR/1999/REC-html401-19991224/frameset.dtd'>");
2738
	}
2739
2740
	if (!defined('_SPIP_SCRIPT')) {
2741
		/** L'adresse de base du site ; on peut mettre '' si la racine est gerée par
2742
		 * le script de l'espace public, alias index.php */
2743
		define('_SPIP_SCRIPT', 'spip.php');
2744
	}
2745
	if (!defined('_SPIP_PAGE')) {
2746
		/** Argument page, personalisable en cas de conflit avec un autre script */
2747
		define('_SPIP_PAGE', 'page');
2748
	}
2749
2750
	// le script de l'espace prive
2751
	// Mettre a "index.php" si DirectoryIndex ne le fait pas ou pb connexes:
2752
	// les anciens IIS n'acceptent pas les POST sur ecrire/ (#419)
2753
	// meme pb sur thttpd cf. https://forum.spip.net/fr_184153.html
2754
	if (!defined('_SPIP_ECRIRE_SCRIPT')) {
2755
		if (!empty($_SERVER['SERVER_SOFTWARE']) and preg_match(',IIS|thttpd,', $_SERVER['SERVER_SOFTWARE'])) {
2756
			define('_SPIP_ECRIRE_SCRIPT', 'index.php');
2757
		} else {
2758
			define('_SPIP_ECRIRE_SCRIPT', '');
2759
		}
2760
	}
2761
2762
2763
	if (!defined('_SPIP_AJAX')) {
2764
		define('_SPIP_AJAX', ((!isset($_COOKIE['spip_accepte_ajax']))
2765
			? 1
2766
			: (($_COOKIE['spip_accepte_ajax'] != -1) ? 1 : 0)));
2767
	}
2768
2769
	// La requete est-elle en ajax ?
2770
	if (!defined('_AJAX')) {
2771
		define('_AJAX',
2772
			(isset($_SERVER['HTTP_X_REQUESTED_WITH']) # ajax jQuery
2773
				or !empty($_REQUEST['var_ajax_redir']) # redirection 302 apres ajax jQuery
2774
				or !empty($_REQUEST['var_ajaxcharset']) # compat ascendante pour plugins
2775
				or !empty($_REQUEST['var_ajax']) # forms ajax & inclure ajax de spip
2776
			)
2777
			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
2778
		);
2779
	}
2780
2781
	# nombre de pixels maxi pour calcul de la vignette avec gd
2782
	# au dela de 5500000 on considere que php n'est pas limite en memoire pour cette operation
2783
	# les configurations limitees en memoire ont un seuil plutot vers 1MPixel
2784
	if (!defined('_IMG_GD_MAX_PIXELS')) {
2785
		define('_IMG_GD_MAX_PIXELS',
2786
		(isset($GLOBALS['meta']['max_taille_vignettes']) and $GLOBALS['meta']['max_taille_vignettes'])
2787
			? $GLOBALS['meta']['max_taille_vignettes']
2788
			: 0);
2789
	}
2790
2791
	if (!defined('_MEMORY_LIMIT_MIN')) {
2792
		define('_MEMORY_LIMIT_MIN', 16);
2793
	} // en Mo
2794
	// si on est dans l'espace prive et si le besoin est superieur a 8Mo (qui est vraiment le standard)
2795
	// on verifie que la memoire est suffisante pour le compactage css+js pour eviter la page blanche
2796
	// il y aura d'autres problemes et l'utilisateur n'ira pas tres loin, mais ce sera plus comprehensible qu'une page blanche
2797
	if (test_espace_prive() and _MEMORY_LIMIT_MIN > 8) {
2798
		if ($memory = trim(ini_get('memory_limit')) and $memory != -1) {
2799
			$unit = strtolower(substr($memory, -1));
2800
			$memory = substr($memory, 0, -1);
2801
			switch ($unit) {
2802
				// Le modifieur 'G' est disponible depuis PHP 5.1.0
2803
				case 'g':
2804
					$memory *= 1024;
2805
				case 'm':
2806
					$memory *= 1024;
2807
				case 'k':
2808
					$memory *= 1024;
2809
			}
2810
			if ($memory < _MEMORY_LIMIT_MIN * 1024 * 1024) {
2811
				@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...
2812
				if (trim(ini_get('memory_limit')) != $m) {
2813
					if (!defined('_INTERDIRE_COMPACTE_HEAD_ECRIRE')) {
2814
						define('_INTERDIRE_COMPACTE_HEAD_ECRIRE', true);
2815
					} // evite une page blanche car on ne saura pas calculer la css dans ce hit
2816
				}
2817
			}
2818
		} else {
2819
			if (!defined('_INTERDIRE_COMPACTE_HEAD_ECRIRE')) {
2820
				define('_INTERDIRE_COMPACTE_HEAD_ECRIRE', true);
2821
			}
2822
		} // evite une page blanche car on ne saura pas calculer la css dans ce hit
2823
	}
2824
	// Protocoles a normaliser dans les chaines de langues
2825
	if (!defined('_PROTOCOLES_STD')) {
2826
		define('_PROTOCOLES_STD', 'http|https|ftp|mailto|webcal');
2827
	}
2828
2829
	init_var_mode();
2830
}
2831
2832
/**
2833
 * Repérer les variables d'URL spéciales `var_mode` qui conditionnent
2834
 * la validité du cache ou certains affichages spéciaux.
2835
 *
2836
 * Le paramètre d'URL `var_mode` permet de
2837
 * modifier la pérennité du cache, recalculer des urls
2838
 * ou d'autres petit caches (trouver_table, css et js compactes ...),
2839
 * d'afficher un écran de débug ou des traductions non réalisées.
2840
 *
2841
 * En fonction de ces paramètres dans l'URL appelante, on définit
2842
 * da constante `_VAR_MODE` qui servira ensuite à SPIP.
2843
 *
2844
 * Le paramètre `var_mode` accepte ces valeurs :
2845
 *
2846
 * - `calcul` : force un calcul du cache de la page (sans forcément recompiler les squelettes)
2847
 * - `recalcul` : force un calcul du cache de la page en recompilant au préabable les squelettes
2848
 * - `inclure` : modifie l'affichage en ajoutant visuellement le nom de toutes les inclusions qu'elle contient
2849
 * - `debug` :  modifie l'affichage activant le mode "debug"
2850
 * - `preview` : modifie l'affichage en ajoutant aux boucles les éléments prévisualisables
2851
 * - `traduction` : modifie l'affichage en affichant des informations sur les chaînes de langues utilisées
2852
 * - `urls` : permet de recalculer les URLs des objets appelés dans la page par les balises `#URL_xx`
2853
 * - `images` : permet de recalculer les filtres d'images utilisés dans la page
2854
 *
2855
 * En dehors des modes `calcul` et `recalcul`, une autorisation 'previsualiser' ou 'debug' est testée.
2856
 *
2857
 * @note
2858
 *     Il éxiste également le paramètre `var_profile` qui modifie l'affichage pour incruster
2859
 *     le nombre de requêtes SQL utilisées dans la page, qui peut se compléter avec le paramètre
2860
 * `   var_mode` (calcul ou recalcul).
2861
 */
2862
function init_var_mode() {
2863
	static $done = false;
2864
	if (!$done) {
2865
2866
		if (isset($_GET['var_mode'])) {
2867
			$var_mode = explode(',', $_GET['var_mode']);
2868
			// tout le monde peut calcul/recalcul
2869
			if (!defined('_VAR_MODE')) {
2870
				if (in_array('recalcul', $var_mode)) {
2871
					define('_VAR_MODE', 'recalcul');
2872
				} elseif (in_array('calcul', $var_mode)) {
2873
					define('_VAR_MODE', 'calcul');
2874
				}
2875
			}
2876
			$var_mode = array_diff($var_mode, array('calcul', 'recalcul'));
2877
			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...
2878
				include_spip('inc/autoriser');
2879
				// autoriser preview si preview seulement, et sinon autoriser debug
2880
				if (autoriser(
2881
					($_GET['var_mode'] == 'preview')
2882
						? 'previsualiser'
2883
						: 'debug'
2884
				)) {
2885 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...
2886
						// forcer le calcul pour passer dans traduire
2887
						if (!defined('_VAR_MODE')) {
2888
							define('_VAR_MODE', 'calcul');
2889
						}
2890
						// et ne pas enregistrer de cache pour ne pas trainer les surlignages sur d'autres pages
2891
						if (!defined('_VAR_NOCACHE')) {
2892
							define('_VAR_NOCACHE', true);
2893
						}
2894
						$var_mode = array_diff($var_mode, array('traduction'));
2895
					}
2896 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...
2897
						// basculer sur les criteres de preview dans les boucles
2898
						if (!defined('_VAR_PREVIEW')) {
2899
							define('_VAR_PREVIEW', true);
2900
						}
2901
						// forcer le calcul
2902
						if (!defined('_VAR_MODE')) {
2903
							define('_VAR_MODE', 'calcul');
2904
						}
2905
						// et ne pas enregistrer de cache
2906
						if (!defined('_VAR_NOCACHE')) {
2907
							define('_VAR_NOCACHE', true);
2908
						}
2909
						$var_mode = array_diff($var_mode, array('preview'));
2910
					}
2911 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...
2912
						// forcer le compilo et ignorer les caches existants
2913
						if (!defined('_VAR_MODE')) {
2914
							define('_VAR_MODE', 'calcul');
2915
						}
2916
						if (!defined('_VAR_INCLURE')) {
2917
							define('_VAR_INCLURE', true);
2918
						}
2919
						// et ne pas enregistrer de cache
2920
						if (!defined('_VAR_NOCACHE')) {
2921
							define('_VAR_NOCACHE', true);
2922
						}
2923
						$var_mode = array_diff($var_mode, array('inclure'));
2924
					}
2925 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...
2926
						// forcer le compilo et ignorer les caches existants
2927
						if (!defined('_VAR_MODE')) {
2928
							define('_VAR_MODE', 'calcul');
2929
						}
2930
						if (!defined('_VAR_URLS')) {
2931
							define('_VAR_URLS', true);
2932
						}
2933
						$var_mode = array_diff($var_mode, array('urls'));
2934
					}
2935 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...
2936
						// forcer le compilo et ignorer les caches existants
2937
						if (!defined('_VAR_MODE')) {
2938
							define('_VAR_MODE', 'calcul');
2939
						}
2940
						// indiquer qu'on doit recalculer les images
2941
						if (!defined('_VAR_IMAGES')) {
2942
							define('_VAR_IMAGES', true);
2943
						}
2944
						$var_mode = array_diff($var_mode, array('images'));
2945
					}
2946 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...
2947
						if (!defined('_VAR_MODE')) {
2948
							define('_VAR_MODE', 'debug');
2949
						}
2950
						// et ne pas enregistrer de cache
2951
						if (!defined('_VAR_NOCACHE')) {
2952
							define('_VAR_NOCACHE', true);
2953
						}
2954
						$var_mode = array_diff($var_mode, array('debug'));
2955
					}
2956
					if (count($var_mode) and !defined('_VAR_MODE')) {
2957
						define('_VAR_MODE', reset($var_mode));
2958
					}
2959
					if (isset($GLOBALS['visiteur_session']['nom'])) {
2960
						spip_log($GLOBALS['visiteur_session']['nom']
2961
							. " " . _VAR_MODE);
2962
					}
2963
				} // pas autorise ?
2964
				else {
2965
					// si on n'est pas connecte on se redirige, si on est pas en cli et pas deja en train de se loger
2966
					if (!$GLOBALS['visiteur_session']
2967
					  and !empty($_SERVER['HTTP_HOST'])
2968
					  and !empty($_SERVER['REQUEST_METHOD'])
2969
					  and $_SERVER['REQUEST_METHOD'] === 'GET') {
2970
						$self = self('&', true);
2971
						if (strpos($self, 'page=login') === false) {
2972
							include_spip('inc/headers');
2973
							$redirect = parametre_url(self('&', true), 'var_mode', $_GET['var_mode'], '&');
2974
							redirige_par_entete(generer_url_public('login','url=' . rawurlencode($redirect), true));
2975
						}
2976
					}
2977
					// sinon tant pis
2978
				}
2979
			}
2980
		}
2981
		if (!defined('_VAR_MODE')) {
2982
			/**
2983
			 * Indique le mode de calcul ou d'affichage de la page.
2984
			 * @see init_var_mode()
2985
			 */
2986
			define('_VAR_MODE', false);
2987
		}
2988
		$done = true;
2989
	}
2990
}
2991
2992
// Annuler les magic quotes \' sur GET POST COOKIE et GLOBALS ;
2993
// supprimer aussi les eventuels caracteres nuls %00, qui peuvent tromper
2994
// la commande is_readable('chemin/vers/fichier/interdit%00truc_normal')
2995
// https://code.spip.net/@spip_desinfecte
2996
function spip_desinfecte(&$t, $deep = true) {
2997
	foreach ($t as $key => $val) {
2998
		if (is_string($t[$key])) {
2999
			$t[$key] = str_replace(chr(0), '-', $t[$key]);
3000
		} // traiter aussi les "texte_plus" de article_edit
3001
		else {
3002
			if ($deep and is_array($t[$key]) and $key !== 'GLOBALS') {
3003
				spip_desinfecte($t[$key], $deep);
3004
			}
3005
		}
3006
	}
3007
}
3008
3009
//  retourne le statut du visiteur s'il s'annonce
3010
3011
// https://code.spip.net/@verifier_visiteur
3012
function verifier_visiteur() {
3013
	// Rq: pour que cette fonction marche depuis mes_options
3014
	// il faut forcer l'init si ce n'est fait
3015
	// mais on risque de perturber des plugins en initialisant trop tot
3016
	// certaines constantes
3017
	@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...
3018
		(_DIR_RACINE . _NOM_PERMANENTS_INACCESSIBLES),
3019
		(_DIR_RACINE . _NOM_PERMANENTS_ACCESSIBLES),
3020
		(_DIR_RACINE . _NOM_TEMPORAIRES_INACCESSIBLES),
3021
		(_DIR_RACINE . _NOM_TEMPORAIRES_ACCESSIBLES)
3022
	);
3023
3024
	// Demarrer une session NON AUTHENTIFIEE si on donne son nom
3025
	// dans un formulaire sans login (ex: #FORMULAIRE_FORUM)
3026
	// Attention on separe bien session_nom et nom, pour eviter
3027
	// les melanges entre donnees SQL et variables plus aleatoires
3028
	$variables_session = array('session_nom', 'session_email');
3029
	foreach ($variables_session as $var) {
3030
		if (_request($var) !== null) {
3031
			$init = true;
3032
			break;
3033
		}
3034
	}
3035
	if (isset($init)) {
3036
		#@spip_initialisation_suite();
3037
		$session = charger_fonction('session', 'inc');
3038
		$session();
3039
		include_spip('inc/texte');
3040
		foreach ($variables_session as $var) {
3041
			if (($a = _request($var)) !== null) {
3042
				$GLOBALS['visiteur_session'][$var] = safehtml($a);
3043
			}
3044
		}
3045
		if (!isset($GLOBALS['visiteur_session']['id_auteur'])) {
3046
			$GLOBALS['visiteur_session']['id_auteur'] = 0;
3047
		}
3048
		$session($GLOBALS['visiteur_session']);
3049
3050
		return 0;
3051
	}
3052
3053
	$h = (isset($_SERVER['PHP_AUTH_USER']) and !$GLOBALS['ignore_auth_http']);
3054
	if ($h or isset($_COOKIE['spip_session']) or isset($_COOKIE[$GLOBALS['cookie_prefix'] . '_session'])) {
3055
3056
		$session = charger_fonction('session', 'inc');
3057
		if ($session()) {
3058
			return $GLOBALS['visiteur_session']['statut'];
3059
		}
3060
		if ($h and isset($_SERVER['PHP_AUTH_PW'])) {
3061
			include_spip('inc/auth');
3062
			$h = lire_php_auth($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW']);
3063
		}
3064
		if ($h) {
3065
			$GLOBALS['visiteur_session'] = $h;
3066
3067
			return $GLOBALS['visiteur_session']['statut'];
3068
		}
3069
	}
3070
3071
	// au moins son navigateur nous dit la langue preferee de cet inconnu
3072
	include_spip('inc/lang');
3073
	utiliser_langue_visiteur();
3074
3075
	return false;
3076
}
3077
3078
3079
/**
3080
 * Sélectionne la langue donnée en argument et mémorise la courante
3081
 *
3082
 * Restaure l'ancienne langue si appellée sans argument.
3083
 *
3084
 * @note
3085
 *     On pourrait économiser l'empilement en cas de non changemnt
3086
 *     et lui faire retourner `False` pour prevenir l'appelant
3087
 *     Le noyau de Spip sait le faire, mais pour assurer la compatibilité
3088
 *     cette fonction retourne toujours non `False`
3089
 *
3090
 * @uses changer_langue()
3091
 * @param null|string $lang
3092
 *     - string : Langue à appliquer,
3093
 *     - null : Pour restituer la dernière langue mémorisée.
3094
 * @return string
3095
 *     - string Langue utilisée.
3096
 **/
3097
function lang_select($lang = null) {
3098
	static $pile_langues = array();
3099
	if (!function_exists('changer_langue')) {
3100
		include_spip('inc/lang');
3101
	}
3102
	if ($lang === null) {
3103
		$lang = array_pop($pile_langues);
3104
	} else {
3105
		array_push($pile_langues, $GLOBALS['spip_lang']);
3106
	}
3107
	if (isset($GLOBALS['spip_lang']) and $lang == $GLOBALS['spip_lang']) {
3108
		return $lang;
3109
	}
3110
	changer_langue($lang);
3111
3112
	return $lang;
3113
}
3114
3115
/**
3116
 * Renvoie une chaîne qui identifie la session courante
3117
 *
3118
 * Permet de savoir si on peut utiliser un cache enregistré pour cette session.
3119
 * Cette chaîne est courte (8 cars) pour pouvoir être utilisée dans un nom
3120
 * de fichier cache.
3121
 *
3122
 * @pipeline_appel definir_session
3123
 *
3124
 * @param bool $force
3125
 * @return string
3126
 *     Identifiant de la session
3127
 **/
3128
function spip_session($force = false) {
3129
	static $session;
3130
	if ($force or !isset($session)) {
3131
		$s = pipeline('definir_session',
3132
			$GLOBALS['visiteur_session']
3133
				? serialize($GLOBALS['visiteur_session'])
3134
				. '_' . @$_COOKIE['spip_session']
3135
				: ''
3136
		);
3137
		$session = $s ? substr(md5($s), 0, 8) : '';
3138
	}
3139
3140
	#spip_log('session: '.$session);
3141
	return $session;
3142
}
3143
3144
3145
/**
3146
 * Retourne un lien vers une aide
3147
 *
3148
 * Aide, aussi depuis l'espace privé à présent.
3149
 * Surchargeable mais pas d'erreur fatale si indisponible.
3150
 *
3151
 * @param string $aide
3152
 *    Cle d'identification de l'aide desiree
3153
 * @param bool $distante
3154
 *    Generer une url locale (par defaut)
3155
 *    ou une url distante [directement sur spip.net]
3156
 * @return
3157
 *    Lien sur une icone d'aide
3158
 **/
3159
function aider($aide = '', $distante = false) {
3160
	$aider = charger_fonction('aide', 'inc', true);
3161
3162
	return $aider ? $aider($aide, '', array(), $distante) : '';
3163
}
3164
3165
/**
3166
 * Page `exec=info` : retourne le contenu de la fonction php `phpinfo()`
3167
 *
3168
 * Si l’utiliseur est un webmestre.
3169
 */
3170
function exec_info_dist() {
3171
3172
	include_spip('inc/autoriser');
3173
	if (autoriser('phpinfos')) {
3174
		$cookies_masques = ['spip_session', 'PHPSESSID'];
3175
		$cookies_backup = [];
3176
		foreach ($cookies_masques as $k) {
3177
			if (!empty($_COOKIE[$k])) {
3178
				$cookies_backup[$k] = $_COOKIE[$k];
3179
				$_COOKIE[$k] = '******************************';
3180
			}
3181
		}
3182
		phpinfo();
3183
		foreach ($cookies_backup as $k => $v) {
3184
			$_COOKIE[$k] = $v;
3185
		}
3186
	} else {
3187
		include_spip('inc/filtres');
3188
		sinon_interdire_acces();
3189
	}
3190
}
3191
3192
/**
3193
 * Génère une erreur de squelette
3194
 *
3195
 * Génère une erreur de squelette qui sera bien visible par un
3196
 * administrateur authentifié lors d'une visite de la page en erreur
3197
 *
3198
 * @param bool|string|array $message
3199
 *     - Message d'erreur (string|array)
3200
 *     - false pour retourner le texte des messages d'erreurs
3201
 *     - vide pour afficher les messages d'erreurs
3202
 * @param string|array|object $lieu
3203
 *     Lieu d'origine de l'erreur
3204
 * @return null|string
3205
 *     - Rien dans la plupart des cas
3206
 *     - string si $message à false.
3207
 **/
3208
function erreur_squelette($message = '', $lieu = '') {
3209
	$debusquer = charger_fonction('debusquer', 'public');
3210
	if (is_array($lieu)) {
3211
		include_spip('public/compiler');
3212
		$lieu = reconstruire_contexte_compil($lieu);
3213
	}
3214
3215
	return $debusquer($message, $lieu);
3216
}
3217
3218
/**
3219
 * Calcule un squelette avec un contexte et retourne son contenu
3220
 *
3221
 * La fonction de base de SPIP : un squelette + un contexte => une page.
3222
 * $fond peut etre un nom de squelette, ou une liste de squelette au format array.
3223
 * Dans ce dernier cas, les squelettes sont tous evalues et mis bout a bout
3224
 * $options permet de selectionner les options suivantes :
3225
 *
3226
 * - trim => true (valeur par defaut) permet de ne rien renvoyer si le fond ne produit que des espaces ;
3227
 * - raw  => true permet de recuperer la strucure $page complete avec entetes et invalideurs
3228
 *          pour chaque $fond fourni.
3229
 *
3230
 * @api
3231
 * @param string /array $fond
3232
 *     - Le ou les squelettes à utiliser, sans l'extension, {@example prive/liste/auteurs}
3233
 *     - Le fichier sera retrouvé dans la liste des chemins connus de SPIP (squelettes, plugins, spip)
3234
 * @param array $contexte
3235
 *     - Informations de contexte envoyées au squelette, {@example array('id_rubrique' => 8)}
3236
 *     - La langue est transmise automatiquement (sauf option étoile).
3237
 * @param array $options
3238
 *     Options complémentaires :
3239
 *
3240
 *     - trim   : applique un trim sur le résultat (true par défaut)
3241
 *     - raw    : retourne un tableau d'information sur le squelette (false par défaut)
3242
 *     - etoile : ne pas transmettre la langue au contexte automatiquement (false par défaut),
3243
 *                équivalent de INCLURE*
3244
 *     - ajax   : gere les liens internes du squelette en ajax (équivalent du paramètre {ajax})
3245
 * @param string $connect
3246
 *     Non du connecteur de bdd a utiliser
3247
 * @return string|array
3248
 *     - Contenu du squelette calculé
3249
 *     - ou tableau d'information sur le squelette.
3250
 */
3251
function recuperer_fond($fond, $contexte = array(), $options = array(), $connect = '') {
3252
	if (!function_exists('evaluer_fond')) {
3253
		include_spip('public/assembler');
3254
	}
3255
	// assurer la compat avec l'ancienne syntaxe
3256
	// (trim etait le 3eme argument, par defaut a true)
3257
	if (!is_array($options)) {
3258
		$options = array('trim' => $options);
3259
	}
3260
	if (!isset($options['trim'])) {
3261
		$options['trim'] = true;
3262
	}
3263
3264
	if (isset($contexte['connect'])) {
3265
		$connect = $contexte['connect'];
3266
		unset($contexte['connect']);
3267
	}
3268
3269
	$texte = "";
3270
	$pages = array();
3271
	$lang_select = '';
3272 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...
3273
		// Si on a inclus sans fixer le critere de lang, on prend la langue courante
3274
		if (!isset($contexte['lang'])) {
3275
			$contexte['lang'] = $GLOBALS['spip_lang'];
3276
		}
3277
3278
		if ($contexte['lang'] != $GLOBALS['meta']['langue_site']) {
3279
			$lang_select = lang_select($contexte['lang']);
3280
		}
3281
	}
3282
3283
	if (!isset($GLOBALS['_INC_PUBLIC'])) {
3284
		$GLOBALS['_INC_PUBLIC'] = 0;
3285
	}
3286
3287
	$GLOBALS['_INC_PUBLIC']++;
3288
3289
	// fix #4235
3290
	$cache_utilise_session_appelant	= (isset($GLOBALS['cache_utilise_session']) ? $GLOBALS['cache_utilise_session'] : null);
3291
3292
3293
	foreach (is_array($fond) ? $fond : array($fond) as $f) {
3294
		
3295
		unset($GLOBALS['cache_utilise_session']);	// fix #4235
3296
3297
		$page = evaluer_fond($f, $contexte, $connect);
3298
		if ($page === '') {
3299
			$c = isset($options['compil']) ? $options['compil'] : '';
3300
			$a = array('fichier' => $f);
3301
			$erreur = _T('info_erreur_squelette2', $a); // squelette introuvable
3302
			erreur_squelette($erreur, $c);
3303
			// eviter des erreurs strictes ensuite sur $page['cle'] en PHP >= 5.4
3304
			$page = array('texte' => '', 'erreur' => $erreur);
3305
		}
3306
3307
		$page = pipeline('recuperer_fond', array(
3308
			'args' => array('fond' => $f, 'contexte' => $contexte, 'options' => $options, 'connect' => $connect),
3309
			'data' => $page
3310
		));
3311
		if (isset($options['ajax']) and $options['ajax']) {
3312
			if (!function_exists('encoder_contexte_ajax')) {
3313
				include_spip('inc/filtres');
3314
			}
3315
			$page['texte'] = encoder_contexte_ajax(
3316
				array_merge(
3317
					$contexte,
3318
					array('fond' => $f),
3319
					($connect ? array('connect' => $connect) : array())
3320
				),
3321
				'',
3322
				$page['texte'],
3323
				$options['ajax']
3324
			);
3325
		}
3326
3327
		if (isset($options['raw']) and $options['raw']) {
3328
			$pages[] = $page;
3329
		} else {
3330
			$texte .= $options['trim'] ? rtrim($page['texte']) : $page['texte'];
3331
		}
3332
		
3333
		// contamination de la session appelante, pour les inclusions statiques
3334
		if (isset($page['invalideurs']['session'])){
3335
			$cache_utilise_session_appelant = $page['invalideurs']['session'];
3336
		}
3337
	}
3338
3339
	// restaurer le sessionnement du contexte appelant, 
3340
	// éventuellement contaminé si on vient de récupérer une inclusion statique sessionnée
3341
	if (isset($cache_utilise_session_appelant)) {
3342
		$GLOBALS['cache_utilise_session'] = $cache_utilise_session_appelant;
3343
	}
3344
3345
	$GLOBALS['_INC_PUBLIC']--;
3346
3347
	if ($lang_select) {
3348
		lang_select();
3349
	}
3350
	if (isset($options['raw']) and $options['raw']) {
3351
		return is_array($fond) ? $pages : reset($pages);
3352
	} else {
3353
		return $options['trim'] ? ltrim($texte) : $texte;
3354
	}
3355
}
3356
3357
/**
3358
 * Trouve un squelette dans le repertoire modeles/
3359
 *
3360
 * @param  $nom
3361
 * @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...
3362
 */
3363
function trouve_modele($nom) {
3364
	return trouver_fond($nom, 'modeles/');
3365
}
3366
3367
/**
3368
 * Trouver un squelette dans le chemin
3369
 * on peut specifier un sous-dossier dans $dir
3370
 * si $pathinfo est a true, retourne un tableau avec
3371
 * les composantes du fichier trouve
3372
 * + le chemin complet sans son extension dans fond
3373
 *
3374
 * @param string $nom
3375
 * @param string $dir
3376
 * @param bool $pathinfo
3377
 * @return array|string
3378
 */
3379
function trouver_fond($nom, $dir = '', $pathinfo = false) {
3380
	$f = find_in_path($nom . '.' . _EXTENSION_SQUELETTES, $dir ? rtrim($dir, '/') . '/' : '');
3381
	if (!$pathinfo) {
3382
		return $f;
3383
	}
3384
	// renvoyer un tableau detaille si $pathinfo==true
3385
	$p = pathinfo($f);
3386
	if (!isset($p['extension']) or !$p['extension']) {
3387
		$p['extension'] = _EXTENSION_SQUELETTES;
3388
	}
3389
	if (!isset($p['extension']) or !$p['filename']) {
3390
		$p['filename'] = ($p['basename'] ? substr($p['basename'], 0, -strlen($p['extension']) - 1) : '');
3391
	}
3392
	$p['fond'] = ($f ? substr($f, 0, -strlen($p['extension']) - 1) : '');
3393
3394
	return $p;
3395
}
3396
3397
/**
3398
 * Teste, pour un nom de page de l'espace privé, s'il est possible
3399
 * de générer son contenu.
3400
 *
3401
 * Dans ce cas, on retourne la fonction d'exécution correspondante à utiliser
3402
 * (du répertoire `ecrire/exec`). Deux cas particuliers et prioritaires :
3403
 * `fond` est retourné si des squelettes existent.
3404
 *
3405
 * - `fond` : pour des squelettes de `prive/squelettes/contenu`
3406
 *          ou pour des objets éditoriaux dont les squelettes seront échaffaudés
3407
 *
3408
 * @param string $nom
3409
 *     Nom de la page
3410
 * @return string
3411
 *     Nom de l'exec, sinon chaîne vide.
3412
 **/
3413
function tester_url_ecrire($nom) {
3414
	static $exec = array();
3415
	if (isset($exec[$nom])) {
3416
		return $exec[$nom];
3417
	}
3418
	// tester si c'est une page en squelette
3419
	if (trouver_fond($nom, 'prive/squelettes/contenu/')) {
3420
		return $exec[$nom] = 'fond';
3421
	} // echafaudage d'un fond !
3422
	elseif (include_spip('public/styliser_par_z') and z_echafaudable($nom)) {
3423
		return $exec[$nom] = 'fond';
3424
	}
3425
	// attention, il ne faut pas inclure l'exec ici
3426
	// car sinon #URL_ECRIRE provoque des inclusions
3427
	// et des define intrusifs potentiels
3428
	return $exec[$nom] = ((find_in_path("{$nom}.php", 'exec/') or charger_fonction($nom, 'exec', true)) ? $nom : '');
3429
}
3430
3431
/**
3432
 * Indique si le code HTML5 est permis sur le site public
3433
 *
3434
 * @return bool
3435
 *     true si la constante _VERSION_HTML n'est pas définie ou égale à html5
3436
 **/
3437
function html5_permis() {
3438
	return (!defined('_VERSION_HTML')
3439
		or _VERSION_HTML !== 'html4');
3440
}
3441
3442
/**
3443
 * Lister les formats image acceptes par les lib et fonctions images
3444
 * @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...
3445
 * @param bool $svg_allowed
3446
 * @return array
3447
 */
3448
function formats_image_acceptables($gd = null, $svg_allowed = true) {
3449
	$formats = null;
3450
	if (!is_null($gd)) {
3451
		$config = ($gd ? "gd_formats" : "formats_graphiques");
3452
		if (isset($GLOBALS['meta'][$config])) {
3453
			$formats = $GLOBALS['meta'][$config];
3454
			$formats = explode(',', $formats);
3455
			$formats = array_filter($formats);
3456
			$formats = array_map('trim', $formats);
3457
		}
3458
	}
3459
	if (is_null($formats)) {
3460
		include_spip('inc/filtres_images_lib_mini');
3461
		$formats = _image_extensions_acceptees_en_entree();
3462
	}
3463
3464
	if ($svg_allowed) {
3465
		if (!in_array('svg', $formats)) {
3466
			$formats[] = 'svg';
3467
		}
3468
	}
3469
	else {
3470
		$formats = array_diff($formats, ['svg']);
3471
	}
3472
	return $formats;
3473
}
3474
3475
/**
3476
 * Extension de la fonction getimagesize pour supporter aussi les images SVG
3477
 * @param string $fichier
3478
 * @return array|bool
3479
 */
3480
function spip_getimagesize($fichier) {
3481
	if (!$imagesize = @getimagesize($fichier)) {
3482
3483
		include_spip("inc/svg");
3484
		if ($attrs = svg_lire_attributs($fichier)) {
3485
			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...
3486
			$imagesize = [
3487
				$width,
3488
				$height,
3489
				IMAGETYPE_SVG,
3490
				"width=\"{$width}\" height=\"{$height}\"",
3491
				"mime" => "image/svg+xml"
3492
			];
3493
		}
3494
	}
3495
	return $imagesize;
3496
}
3497
3498
3499
3500
/*
3501
 * Bloc de compatibilite : quasiment tous les plugins utilisent ces fonctions
3502
 * desormais depreciees ; plutot que d'obliger tout le monde a charger
3503
 * vieilles_defs, on va assumer l'histoire de ces 3 fonctions ubiquitaires
3504
 */
3505
3506
/**
3507
 * lire_meta : fonction dépréciée
3508
 *
3509
 * @deprecated Utiliser `$GLOBALS['meta'][$nom]` ou `lire_config('nom')`
3510
 * @see lire_config()
3511
 * @param string $nom Clé de meta à lire
3512
 * @return mixed Valeur de la meta.
3513
 **/
3514
function lire_meta($nom) {
3515
	return isset($GLOBALS['meta'][$nom]) ? $GLOBALS['meta'][$nom] : null;
3516
}
3517
3518
/**
3519
 * Poser une alerte qui sera affiche aux auteurs de bon statut ('' = tous)
3520
 * au prochain passage dans l'espace prive
3521
 * chaque alerte doit avoir un nom pour eviter duplication a chaque hit
3522
 * les alertes affichees une fois sont effacees
3523
 *
3524
 * @param string $nom
3525
 * @param string $message
3526
 * @param string $statut
3527
 */
3528
function avertir_auteurs($nom, $message, $statut = '') {
3529
	$alertes = $GLOBALS['meta']['message_alertes_auteurs'];
3530
	if (!$alertes
3531
		or !is_array($alertes = unserialize($alertes))
3532
	) {
3533
		$alertes = array();
3534
	}
3535
3536
	if (!isset($alertes[$statut])) {
3537
		$alertes[$statut] = array();
3538
	}
3539
	$alertes[$statut][$nom] = $message;
3540
	ecrire_meta("message_alertes_auteurs", serialize($alertes));
3541
}
3542
3543
/**
3544
 * Nettoie une chaine pour servir comme classes CSS.
3545
 *
3546
 * @note
3547
 *     les classes CSS acceptent théoriquement tous les caractères sauf NUL.
3548
 *     Ici, on limite (enlève) les caractères autres qu’alphanumérique, espace, - + _ @
3549
 *
3550
 * @param string|string[] $classes
3551
 * @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...
3552
 */
3553
function spip_sanitize_classname($classes) {
3554
	if (is_array($classes)) {
3555
		return array_map('spip_sanitize_classname', $classes);
3556
	}
3557
	return preg_replace("/[^ 0-9a-z_\-+@]/i", "", $classes);
3558
}
3559
3560
3561
/**
3562
 * Compare 2 numéros de version entre elles.
3563
 *
3564
 * Cette fonction est identique (arguments et retours) a la fonction PHP
3565
 * version_compare() qu'elle appelle. Cependant, cette fonction reformate
3566
 * les numeros de versions pour ameliorer certains usages dans SPIP ou bugs
3567
 * dans PHP. On permet ainsi de comparer 3.0.4 à 3.0.* par exemple.
3568
 *
3569
 * @param string $v1
3570
 *    Numero de version servant de base a la comparaison.
3571
 *    Ce numero ne peut pas comporter d'etoile.
3572
 * @param string $v2
3573
 *    Numero de version a comparer.
3574
 *    Il peut posseder des etoiles tel que 3.0.*
3575
 * @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...
3576
 *    Un operateur eventuel (<, >, <=, >=, =, == ...)
3577
 * @return int|bool
3578
 *    Sans operateur : int. -1 pour inferieur, 0 pour egal, 1 pour superieur
3579
 *    Avec operateur : bool.
3580
 **/
3581
function spip_version_compare($v1, $v2, $op = null) {
3582
	$v1 = strtolower(preg_replace(',([0-9])[\s.-]?(dev|alpha|a|beta|b|rc|pl|p),i', '\\1.\\2', $v1));
3583
	$v2 = strtolower(preg_replace(',([0-9])[\s.-]?(dev|alpha|a|beta|b|rc|pl|p),i', '\\1.\\2', $v2));
3584
	$v1 = str_replace('rc', 'RC', $v1); // certaines versions de PHP ne comprennent RC qu'en majuscule
3585
	$v2 = str_replace('rc', 'RC', $v2); // certaines versions de PHP ne comprennent RC qu'en majuscule
3586
3587
	$v1 = explode('.', $v1);
3588
	$v2 = explode('.', $v2);
3589
	// $v1 est toujours une version, donc sans etoile
3590
	while (count($v1) < count($v2)) {
3591
		$v1[] = '0';
3592
	}
3593
3594
	// $v2 peut etre une borne, donc accepte l'etoile
3595
	$etoile = false;
3596
	foreach ($v1 as $k => $v) {
3597
		if (!isset($v2[$k])) {
3598
			$v2[] = ($etoile and (is_numeric($v) or $v == 'pl' or $v == 'p')) ? $v : '0';
3599
		} else {
3600
			if ($v2[$k] == '*') {
3601
				$etoile = true;
3602
				$v2[$k] = $v;
3603
			}
3604
		}
3605
	}
3606
	$v1 = implode('.', $v1);
3607
	$v2 = implode('.', $v2);
3608
3609
	return $op ? version_compare($v1, $v2, $op) : version_compare($v1, $v2);
3610
}
3611