Completed
Push — master ( 476e4b...b6af38 )
by cam
04:14
created

filtres.php ➔ singulier_ou_pluriel()   B

Complexity

Conditions 9
Paths 11

Size

Total Lines 33

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
nc 11
nop 5
dl 0
loc 33
rs 8.0555
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
 * Déclaration de filtres pour les squelettes
15
 *
16
 * @package SPIP\Core\Filtres
17
 **/
18
if (!defined('_ECRIRE_INC_VERSION')) {
19
	return;
20
}
21
22
include_spip('inc/charsets');
23
include_spip('inc/filtres_mini');
24
include_spip('inc/filtres_dates');
25
include_spip('inc/filtres_selecteur_generique');
26
include_spip('base/objets');
27
include_spip('public/parametrer'); // charger les fichiers fonctions
28
29
/**
30
 * Charger un filtre depuis le php
31
 *
32
 * - on inclue tous les fichiers fonctions des plugins et du skel
33
 * - on appelle chercher_filtre
34
 *
35
 * Pour éviter de perdre le texte si le filtre demandé est introuvable,
36
 * on transmet `filtre_identite_dist` en filtre par défaut.
37
 *
38
 * @uses filtre_identite_dist() Comme fonction par défaut
39
 *
40
 * @param string $fonc Nom du filtre
41
 * @param string $default Filtre par défaut
42
 * @return string Fonction PHP correspondante du filtre
0 ignored issues
show
Documentation introduced by
Should the return type not be null|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...
43
 */
44
function charger_filtre($fonc, $default = 'filtre_identite_dist') {
45
	include_spip('public/parametrer'); // inclure les fichiers fonctions
46
	return chercher_filtre($fonc, $default);
47
}
48
49
/**
50
 * Retourne le texte tel quel
51
 *
52
 * @param string $texte Texte
53
 * @return string Texte
54
 **/
55
function filtre_identite_dist($texte) { return $texte; }
56
57
/**
58
 * Cherche un filtre
59
 *
60
 * Pour une filtre `F` retourne la première fonction trouvée parmis :
61
 *
62
 * - filtre_F
63
 * - filtre_F_dist
64
 * - F
65
 *
66
 * Peut gérer des appels par des fonctions statiques de classes tel que `Foo::Bar`
67
 *
68
 * En absence de fonction trouvée, retourne la fonction par défaut indiquée.
69
 *
70
 * @param string $fonc
71
 *     Nom du filtre
72
 * @param null $default
73
 *     Nom du filtre appliqué par défaut si celui demandé n'est pas trouvé
74
 * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be null|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...
75
 *     Fonction PHP correspondante du filtre demandé
76
 */
77
function chercher_filtre($fonc, $default = null) {
78
	if (!$fonc) {
79
		return $default;
80
	}
81
	// Cas des types mime, sans confondre avec les appels de fonction de classe
82
	// Foo::Bar
83
	// qui peuvent etre avec un namespace : space\Foo::Bar
84
	if (preg_match(',^[\w]+/,', $fonc)) {
85
		$nom = preg_replace(',\W,', '_', $fonc);
86
		$f = chercher_filtre($nom);
87
		// cas du sous-type MIME sans filtre associe, passer au type:
88
		// si filtre_text_plain pas defini, passe a filtre_text
89
		if (!$f and $nom !== $fonc) {
90
			$f = chercher_filtre(preg_replace(',\W.*$,', '', $fonc));
91
		}
92
93
		return $f;
94
	}
95
96
	include_fichiers_fonctions();
97
	foreach (array('filtre_' . $fonc, 'filtre_' . $fonc . '_dist', $fonc) as $f) {
98
		trouver_filtre_matrice($f); // charge des fichiers spécifiques éventuels
99
		// fonction ou name\space\fonction
100
		if (is_callable($f)) {
101
			return $f;
102
		}
103
		// méthode statique d'une classe Classe::methode ou name\space\Classe::methode
104
		elseif (false === strpos($f, '::') and is_callable(array($f))) {
105
			return $f;
106
		}
107
	}
108
109
	return $default;
110
}
111
112
/**
113
 * Applique un filtre
114
 *
115
 * Fonction générique qui prend en argument l’objet (texte, etc) à modifier
116
 * et le nom du filtre. Retrouve les arguments du filtre demandé dans les arguments
117
 * transmis à cette fonction, via func_get_args().
118
 *
119
 * @see filtrer() Assez proche
120
 *
121
 * @param mixed $arg
122
 *     Texte (le plus souvent) sur lequel appliquer le filtre
123
 * @param string $filtre
124
 *     Nom du filtre à appliquer
125
 * @param bool $force
0 ignored issues
show
Documentation introduced by
Should the type for parameter $force 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...
126
 *     La fonction doit-elle retourner le texte ou rien si le filtre est absent ?
127
 * @return string
128
 *     Texte traité par le filtre si le filtre existe,
129
 *     Texte d'origine si le filtre est introuvable et si $force à `true`
130
 *     Chaîne vide sinon (filtre introuvable).
131
 **/
132
function appliquer_filtre($arg, $filtre, $force = null) {
133
	$f = chercher_filtre($filtre);
134
	if (!$f) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $f of type null|string is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
135
		if (!$force) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $force of type boolean|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
136
			return '';
137
		} else {
138
			return $arg;
139
		}
140
	}
141
142
	$args = func_get_args();
143
	array_shift($args); // enlever $arg
144
	array_shift($args); // enlever $filtre
145
	array_unshift($args, $arg); // remettre $arg
146
	return call_user_func_array($f, $args);
147
}
148
149
/**
150
 * Retourne la version de SPIP
151
 *
152
 * Si l'on retrouve un numéro de révision GIT ou SVN, il est ajouté entre crochets.
153
 * Si effectivement le SPIP est installé par Git ou Svn, 'GIT' ou 'SVN' est ajouté avant sa révision.
154
 *
155
 * @global spip_version_affichee Contient la version de SPIP
156
 * @uses version_vcs_courante() Pour trouver le numéro de révision
157
 *
158
 * @return string
159
 *     Version de SPIP
160
 **/
161
function spip_version() {
162
	$version = $GLOBALS['spip_version_affichee'];
163
	if ($vcs_version = version_vcs_courante(_DIR_RACINE)) {
164
		$version .= " $vcs_version";
165
	}
166
167
	return $version;
168
}
169
170
/**
171
 * Retourne une courte description d’une révision VCS d’un répertoire
172
 *
173
 * @param string $dir Le répertoire à tester
174
 * @param array $raw True pour avoir les données brutes, false pour un texte à afficher
0 ignored issues
show
Documentation introduced by
Should the type for parameter $raw not be false|array? 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...
175
 * @retun string|array|null
176
 *    - array|null si $raw = true,
177
 *    - string|null si $raw = false
178
 */
179
function version_vcs_courante($dir, $raw = false) {
180
	$desc = decrire_version_git($dir);
181
	if ($desc === null) {
182
		$desc = decrire_version_svn($dir);
183
	}
184
	if ($desc === null or $raw) {
185
		return $desc;
186
	}
187
	// affichage "GIT [master: abcdef]"
188
	$commit = isset($desc['commit_short']) ? $desc['commit_short'] : $desc['commit'];
189
	if ($desc['branch']) {
190
		$commit = $desc['branch'] . ': ' . $commit;
191
	}
192
	return "{$desc['vcs']} [$commit]";
193
}
194
195
/**
196
 * Retrouve un numéro de révision Git d'un répertoire
197
 *
198
 * @param string $dir Chemin du répertoire
199
 * @return array|null
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array<string,string>|null.

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...
200
 *      null si aucune info trouvée
201
 *      array ['branch' => xx, 'commit' => yy] sinon.
202
 **/
203
function decrire_version_git($dir) {
204
	if (!$dir) {
205
		$dir = '.';
206
	}
207
208
	// version installee par GIT
209
	if (lire_fichier($dir . '/.git/HEAD', $c)) {
210
		$currentHead = trim(substr($c, 4));
211
		if (lire_fichier($dir . '/.git/' . $currentHead, $hash)) {
212
			return [
213
				'vcs' => 'GIT',
214
				'branch' => basename($currentHead),
215
				'commit' => trim($hash),
216
				'commit_short' => substr(trim($hash), 0, 8),
217
			];
218
		}
219
	}
220
221
	return null;
222
}
223
224
225
/**
226
 * Retrouve un numéro de révision Svn d'un répertoire
227
 *
228
 * @param string $dir Chemin du répertoire
229
 * @return array|null
230
 *      null si aucune info trouvée
231
 *      array ['commit' => yy, 'date' => xx, 'author' => xx] sinon.
232
 **/
233
function decrire_version_svn($dir) {
234
	if (!$dir) {
235
		$dir = '.';
236
	}
237
	// version installee par SVN
238
	if (file_exists($dir . '/.svn/wc.db') && class_exists('SQLite3')) {
239
		$db = new SQLite3($dir . '/.svn/wc.db');
240
		$result = $db->query('SELECT changed_revision FROM nodes WHERE local_relpath = "" LIMIT 1');
241
		if ($result) {
242
			$row = $result->fetchArray();
243
			if ($row['changed_revision'] != "") {
244
				return [
245
					'vcs' => 'SVN',
246
					'branch' => '',
247
					'commit' => $row['changed_revision'],
248
				];
249
			}
250
		}
251
	}
252
	return null;
253
}
254
255
/**
256
 * Retrouve un numéro de révision SVN d'un répertoire
257
 *
258
 * Mention de la révision SVN courante d'un répertoire
259
 * /!\ Retourne un nombre négatif si on est sur .svn
260
 *
261
 * @deprecated Utiliser version_vcs_courante()
262
 * @param string $dir Chemin du répertoire
263
 * @return int
0 ignored issues
show
Documentation introduced by
Should the return type not be 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...
264
 *
265
 *     - 0 si aucune info trouvée
266
 *     - -NN (entier) si info trouvée par .svn/wc.db
267
 *
268
 **/
269
function version_svn_courante($dir) {
270
	if ($desc = decrire_version_svn($dir)) {
271
		return -$desc['commit'];
272
	}
273
	return 0;
274
}
275
276
// La matrice est necessaire pour ne filtrer _que_ des fonctions definies dans filtres_images
277
// et laisser passer les fonctions personnelles baptisees image_...
278
$GLOBALS['spip_matrice']['image_graver'] = true;//'inc/filtres_images_mini.php';
279
$GLOBALS['spip_matrice']['image_select'] = true;//'inc/filtres_images_mini.php';
280
$GLOBALS['spip_matrice']['image_reduire'] = true;//'inc/filtres_images_mini.php';
281
$GLOBALS['spip_matrice']['image_reduire_par'] = true;//'inc/filtres_images_mini.php';
282
$GLOBALS['spip_matrice']['image_passe_partout'] = true;//'inc/filtres_images_mini.php';
283
284
$GLOBALS['spip_matrice']['couleur_html_to_hex'] = 'inc/filtres_images_mini.php';
285
$GLOBALS['spip_matrice']['couleur_foncer'] = 'inc/filtres_images_mini.php';
286
$GLOBALS['spip_matrice']['couleur_eclaircir'] = 'inc/filtres_images_mini.php';
287
288
// ou pour inclure un script au moment ou l'on cherche le filtre
289
$GLOBALS['spip_matrice']['filtre_image_dist'] = 'inc/filtres_mime.php';
290
$GLOBALS['spip_matrice']['filtre_audio_dist'] = 'inc/filtres_mime.php';
291
$GLOBALS['spip_matrice']['filtre_video_dist'] = 'inc/filtres_mime.php';
292
$GLOBALS['spip_matrice']['filtre_application_dist'] = 'inc/filtres_mime.php';
293
$GLOBALS['spip_matrice']['filtre_message_dist'] = 'inc/filtres_mime.php';
294
$GLOBALS['spip_matrice']['filtre_multipart_dist'] = 'inc/filtres_mime.php';
295
$GLOBALS['spip_matrice']['filtre_text_dist'] = 'inc/filtres_mime.php';
296
$GLOBALS['spip_matrice']['filtre_text_csv_dist'] = 'inc/filtres_mime.php';
297
$GLOBALS['spip_matrice']['filtre_text_html_dist'] = 'inc/filtres_mime.php';
298
$GLOBALS['spip_matrice']['filtre_audio_x_pn_realaudio'] = 'inc/filtres_mime.php';
299
300
301
/**
302
 * Charge et exécute un filtre (graphique ou non)
303
 *
304
 * Recherche la fonction prévue pour un filtre (qui peut être un filtre graphique `image_*`)
305
 * et l'exécute avec les arguments transmis à la fonction, obtenus avec `func_get_args()`
306
 *
307
 * @api
308
 * @uses image_filtrer() Pour un filtre image
309
 * @uses chercher_filtre() Pour un autre filtre
310
 *
311
 * @param string $filtre
312
 *     Nom du filtre à appliquer
313
 * @return string
314
 *     Code HTML retourné par le filtre
315
 **/
316
function filtrer($filtre) {
317
	$tous = func_get_args();
318
	if (trouver_filtre_matrice($filtre) and substr($filtre, 0, 6) == 'image_') {
319
		return image_filtrer($tous);
320
	} elseif ($f = chercher_filtre($filtre)) {
321
		array_shift($tous);
322
		return call_user_func_array($f, $tous);
323
	} else {
324
		// le filtre n'existe pas, on provoque une erreur
325
		$msg = array('zbug_erreur_filtre', array('filtre' => texte_script($filtre)));
326
		erreur_squelette($msg);
327
		return '';
328
	}
329
}
330
331
/**
332
 * Cherche un filtre spécial indiqué dans la globale `spip_matrice`
333
 * et charge le fichier éventuellement associé contenant le filtre.
334
 *
335
 * Les filtres d'images par exemple sont déclarés de la sorte, tel que :
336
 * ```
337
 * $GLOBALS['spip_matrice']['image_reduire'] = true;
338
 * $GLOBALS['spip_matrice']['image_monochrome'] = 'filtres/images_complements.php';
339
 * ```
340
 *
341
 * @param string $filtre
342
 * @return bool true si on trouve le filtre dans la matrice, false sinon.
343
 */
344
function trouver_filtre_matrice($filtre) {
345
	if (isset($GLOBALS['spip_matrice'][$filtre]) and is_string($f = $GLOBALS['spip_matrice'][$filtre])) {
346
		find_in_path($f, '', true);
347
		$GLOBALS['spip_matrice'][$filtre] = true;
348
	}
349
	return !empty($GLOBALS['spip_matrice'][$filtre]);
350
}
351
352
353
/**
354
 * Filtre `set` qui sauve la valeur en entrée dans une variable
355
 *
356
 * La valeur pourra être retrouvée avec `#GET{variable}`.
357
 *
358
 * @example
359
 *     `[(#CALCUL|set{toto})]` enregistre le résultat de `#CALCUL`
360
 *     dans la variable `toto` et renvoie vide.
361
 *     C'est équivalent à `[(#SET{toto, #CALCUL})]` dans ce cas.
362
 *     `#GET{toto}` retourne la valeur sauvegardée.
363
 *
364
 * @example
365
 *     `[(#CALCUL|set{toto,1})]` enregistre le résultat de `#CALCUL`
366
 *      dans la variable toto et renvoie la valeur. Cela permet d'utiliser
367
 *      d'autres filtres ensuite. `#GET{toto}` retourne la valeur.
368
 *
369
 * @filtre
370
 * @param array $Pile Pile de données
371
 * @param mixed $val Valeur à sauver
372
 * @param string $key Clé d'enregistrement
373
 * @param bool $continue True pour retourner la valeur
0 ignored issues
show
Documentation introduced by
Should the type for parameter $continue 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...
374
 * @return mixed
375
 */
376
function filtre_set(&$Pile, $val, $key, $continue = null) {
377
	$Pile['vars'][$key] = $val;
378
	return $continue ? $val : '';
379
}
380
381
/**
382
 * Filtre `setenv` qui enregistre une valeur dans l'environnement du squelette
383
 *
384
 * La valeur pourra être retrouvée avec `#ENV{variable}`.
385
 * 
386
 * @example
387
 *     `[(#CALCUL|setenv{toto})]` enregistre le résultat de `#CALCUL`
388
 *      dans l'environnement toto et renvoie vide.
389
 *      `#ENV{toto}` retourne la valeur.
390
 *
391
 *      `[(#CALCUL|setenv{toto,1})]` enregistre le résultat de `#CALCUL`
392
 *      dans l'environnement toto et renvoie la valeur.
393
 *      `#ENV{toto}` retourne la valeur.
394
 *
395
 * @filtre
396
 *
397
 * @param array $Pile
398
 * @param mixed $val Valeur à enregistrer
399
 * @param mixed $key Nom de la variable
400
 * @param null|mixed $continue Si présent, retourne la valeur en sortie
401
 * @return string|mixed Retourne `$val` si `$continue` présent, sinon ''.
402
 */
403
function filtre_setenv(&$Pile, $val, $key, $continue = null) {
404
	$Pile[0][$key] = $val;
405
	return $continue ? $val : '';
406
}
407
408
/**
409
 * @param array $Pile
410
 * @param array|string $keys
411
 * @return string
412
 */
413
function filtre_sanitize_env(&$Pile, $keys) {
414
	$Pile[0] = spip_sanitize_from_request($Pile[0], $keys);
415
	return '';
416
}
417
418
419
/**
420
 * Filtre `debug` qui affiche un debug de la valeur en entrée
421
 *
422
 * Log la valeur dans `debug.log` et l'affiche si on est webmestre.
423
 *
424
 * @example
425
 *     `[(#TRUC|debug)]` affiche et log la valeur de `#TRUC`
426
 * @example
427
 *     `[(#TRUC|debug{avant}|calcul|debug{apres}|etc)]`
428
 *     affiche la valeur de `#TRUC` avant et après le calcul,
429
 *     en précisant "avant" et "apres".
430
 *
431
 * @filtre
432
 * @link https://www.spip.net/5695
433
 * @param mixed $val La valeur à debugguer
434
 * @param mixed|null $key Clé pour s'y retrouver
435
 * @return mixed Retourne la valeur (sans la modifier).
436
 */
437
function filtre_debug($val, $key = null) {
438
	$debug = (
439
		is_null($key) ? '' : (var_export($key, true) . " = ")
440
		) . var_export($val, true);
441
442
	include_spip('inc/autoriser');
443
	if (autoriser('webmestre')) {
444
		echo "<div class='spip_debug'>\n", $debug, "</div>\n";
445
	}
446
447
	spip_log($debug, 'debug');
448
449
	return $val;
450
}
451
452
453
/**
454
 * Exécute un filtre image
455
 *
456
 * Fonction générique d'entrée des filtres images.
457
 * Accepte en entrée :
458
 *
459
 * - un texte complet,
460
 * - un img-log (produit par #LOGO_XX),
461
 * - un tag `<img ...>` complet,
462
 * - un nom de fichier *local* (passer le filtre `|copie_locale` si on veut
463
 *   l'appliquer à un document distant).
464
 *
465
 * Applique le filtre demande à chacune des occurrences
466
 *
467
 * @param array $args
468
 *     Liste des arguments :
469
 *
470
 *     - le premier est le nom du filtre image à appliquer
471
 *     - le second est le texte sur lequel on applique le filtre
472
 *     - les suivants sont les arguments du filtre image souhaité.
473
 * @return string
474
 *     Texte qui a reçu les filtres
475
 **/
476
function image_filtrer($args) {
477
	$filtre = array_shift($args); # enlever $filtre
478
	$texte = array_shift($args);
479
	if (!strlen($texte)) {
480
		return;
481
	}
482
	find_in_path('filtres_images_mini.php', 'inc/', true);
483
	statut_effacer_images_temporaires(true); // activer la suppression des images temporaires car le compilo finit la chaine par un image_graver
0 ignored issues
show
Unused Code introduced by
The call to the function statut_effacer_images_temporaires() seems unnecessary as the function has no side-effects.
Loading history...
484
	// Cas du nom de fichier local
485
	$is_file = trim($texte);
486
	if (strpos(substr($is_file, strlen(_DIR_RACINE)), '..') !== false
487
		  or strpbrk($is_file, "<>\n\r\t") !== false
488
		  or strpos($is_file, '/') === 0
489
	) {
490
		$is_file = false;
491
	}
492
	if ($is_file
493
		and (
494
			file_exists(supprimer_timestamp($is_file))
495
			or tester_url_absolue($is_file)
496
		)
497
	) {
498
		array_unshift($args, "<img src='$is_file' />");
499
		$res = call_user_func_array($filtre, $args);
500
		statut_effacer_images_temporaires(false); // desactiver pour les appels hors compilo
0 ignored issues
show
Unused Code introduced by
The call to the function statut_effacer_images_temporaires() seems unnecessary as the function has no side-effects.
Loading history...
501
		return $res;
502
	}
503
504
	// Cas general : trier toutes les images, avec eventuellement leur <span>
505
	if (preg_match_all(
506
		',(<([a-z]+) [^<>]*spip_documents[^<>]*>)?\s*(<img\s.*>),UimsS',
507
		$texte, $tags, PREG_SET_ORDER)) {
508
		foreach ($tags as $tag) {
509
			$class = extraire_attribut($tag[3], 'class');
510
			if (!$class or
511
				(strpos($class, 'filtre_inactif') === false
512
					// compat historique a virer en 3.2
513
					and strpos($class, 'no_image_filtrer') === false)
514
			) {
515
				array_unshift($args, $tag[3]);
516
				if ($reduit = call_user_func_array($filtre, $args)) {
517
					// En cas de span spip_documents, modifier le style=...width:
518
					if ($tag[1]) {
519
						$w = extraire_attribut($reduit, 'width');
520
						if (!$w and preg_match(",width:\s*(\d+)px,S", extraire_attribut($reduit, 'style'), $regs)) {
521
							$w = $regs[1];
522
						}
523
						if ($w and ($style = extraire_attribut($tag[1], 'style'))) {
524
							$style = preg_replace(",width:\s*\d+px,S", "width:${w}px", $style);
525
							$replace = inserer_attribut($tag[1], 'style', $style);
0 ignored issues
show
Bug introduced by
It seems like $style defined by preg_replace(',width:\\s..."width:{$w}px", $style) on line 524 can also be of type array<integer,string>; however, inserer_attribut() 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...
526
							$texte = str_replace($tag[1], $replace, $texte);
527
						}
528
					}
529
					// traiter aussi un eventuel mouseover
530
					if ($mouseover = extraire_attribut($reduit, 'onmouseover')) {
531
						if (preg_match(",this[.]src=['\"]([^'\"]+)['\"],ims", $mouseover, $match)) {
532
							$srcover = $match[1];
533
							array_shift($args);
534
							array_unshift($args, "<img src='" . $match[1] . "' />");
535
							$srcover_filter = call_user_func_array($filtre, $args);
536
							$srcover_filter = extraire_attribut($srcover_filter, 'src');
537
							$reduit = str_replace($srcover, $srcover_filter, $reduit);
538
						}
539
					}
540
					$texte = str_replace($tag[3], $reduit, $texte);
541
				}
542
				array_shift($args);
543
			}
544
		}
545
	}
546
	statut_effacer_images_temporaires(false); // desactiver pour les appels hors compilo
0 ignored issues
show
Unused Code introduced by
The call to the function statut_effacer_images_temporaires() seems unnecessary as the function has no side-effects.
Loading history...
547
	return $texte;
548
}
549
550
/**
551
 * Retourne les tailles d'une image
552
 *
553
 * Pour les filtres `largeur` et `hauteur`
554
 *
555
 * @param string $img
556
 *     Balise HTML `<img ... />` ou chemin de l'image (qui peut être une URL distante).
557
 * @return array
558
 *     Liste (hauteur, largeur) en pixels
559
 **/
560
function taille_image($img, $force_refresh = false) {
561
562
	static $largeur_img = array(), $hauteur_img = array();
563
	$srcWidth = 0;
564
	$srcHeight = 0;
565
566
	$src = extraire_attribut($img, 'src');
567
568
	if (!$src) {
569
		$src = $img;
570
	} else {
571
		$srcWidth = extraire_attribut($img, 'width');
572
		$srcHeight = extraire_attribut($img, 'height');
573
	}
574
575
	// ne jamais operer directement sur une image distante pour des raisons de perfo
576
	// la copie locale a toutes les chances d'etre la ou de resservir
577
	if (tester_url_absolue($src)) {
0 ignored issues
show
Bug introduced by
It seems like $src defined by extraire_attribut($img, 'src') on line 566 can also be of type array; however, tester_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...
578
		include_spip('inc/distant');
579
		$fichier = copie_locale($src);
0 ignored issues
show
Bug introduced by
It seems like $src defined by extraire_attribut($img, 'src') on line 566 can also be of type array; however, copie_locale() 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...
580
		$src = $fichier ? _DIR_RACINE . $fichier : $src;
581
	}
582 View Code Duplication
	if (($p = strpos($src, '?')) !== 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...
583
		$src = substr($src, 0, $p);
584
	}
585
586
	$srcsize = false;
0 ignored issues
show
Unused Code introduced by
$srcsize is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
587
	if (isset($largeur_img[$src]) and !$force_refresh) {
588
		$srcWidth = $largeur_img[$src];
589
	}
590
	if (isset($hauteur_img[$src]) and !$force_refresh) {
591
		$srcHeight = $hauteur_img[$src];
592
	}
593
	if (!$srcWidth or !$srcHeight) {
594
595
		if (file_exists($src)
596
			and $srcsize = spip_getimagesize($src)
0 ignored issues
show
Bug introduced by
It seems like $src can also be of type array; however, spip_getimagesize() 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...
597
		) {
598
			if (!$srcWidth) {
599
				$largeur_img[$src] = $srcWidth = $srcsize[0];
600
			}
601
			if (!$srcHeight) {
602
				$hauteur_img[$src] = $srcHeight = $srcsize[1];
603
			}
604
		}
605
		// $src peut etre une reference a une image temporaire dont a n'a que le log .src
606
		// on s'y refere, l'image sera reconstruite en temps utile si necessaire
607
		elseif (@file_exists($f = "$src.src")
608
			and lire_fichier($f, $valeurs)
609
			and $valeurs = unserialize($valeurs)
610
		) {
611
			if (!$srcWidth) {
612
				$largeur_img[$src] = $srcWidth = $valeurs["largeur_dest"];
613
			}
614
			if (!$srcHeight) {
615
				$hauteur_img[$src] = $srcHeight = $valeurs["hauteur_dest"];
616
			}
617
		}
618
	}
619
620
	return array($srcHeight, $srcWidth);
621
}
622
623
624
/**
625
 * Retourne la largeur d'une image
626
 *
627
 * @filtre
628
 * @link https://www.spip.net/4296
629
 * @uses taille_image()
630
 * @see  hauteur()
631
 *
632
 * @param string $img
633
 *     Balise HTML `<img ... />` ou chemin de l'image (qui peut être une URL distante).
634
 * @return int|null
635
 *     Largeur en pixels, NULL ou 0 si aucune image.
636
 **/
637
function largeur($img) {
638
	if (!$img) {
639
		return;
640
	}
641
	list($h, $l) = taille_image($img);
0 ignored issues
show
Unused Code introduced by
The assignment to $h 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...
642
643
	return $l;
644
}
645
646
/**
647
 * Retourne la hauteur d'une image
648
 *
649
 * @filtre
650
 * @link https://www.spip.net/4291
651
 * @uses taille_image()
652
 * @see  largeur()
653
 *
654
 * @param string $img
655
 *     Balise HTML `<img ... />` ou chemin de l'image (qui peut être une URL distante).
656
 * @return int|null
657
 *     Hauteur en pixels, NULL ou 0 si aucune image.
658
 **/
659
function hauteur($img) {
660
	if (!$img) {
661
		return;
662
	}
663
	list($h, $l) = taille_image($img);
0 ignored issues
show
Unused Code introduced by
The assignment to $l 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...
664
665
	return $h;
666
}
667
668
669
/**
670
 * Échappement des entités HTML avec correction des entités « brutes »
671
 *
672
 * Ces entités peuvent être générées par les butineurs lorsqu'on rentre des
673
 * caractères n'appartenant pas au charset de la page [iso-8859-1 par défaut]
674
 *
675
 * Attention on limite cette correction aux caracteres « hauts » (en fait > 99
676
 * pour aller plus vite que le > 127 qui serait logique), de manière à
677
 * préserver des eéhappements de caractères « bas » (par exemple `[` ou `"`)
678
 * et au cas particulier de `&amp;` qui devient `&amp;amp;` dans les URL
679
 *
680
 * @see corriger_toutes_entites_html()
681
 * @param string $texte
682
 * @return string
683
 **/
684
function corriger_entites_html($texte) {
685
	if (strpos($texte, '&amp;') === false) {
686
		return $texte;
687
	}
688
689
	return preg_replace(',&amp;(#[0-9][0-9][0-9]+;|amp;),iS', '&\1', $texte);
690
}
691
692
/**
693
 * Échappement des entités HTML avec correction des entités « brutes » ainsi
694
 * que les `&amp;eacute;` en `&eacute;`
695
 *
696
 * Identique à `corriger_entites_html()` en corrigeant aussi les
697
 * `&amp;eacute;` en `&eacute;`
698
 *
699
 * @see corriger_entites_html()
700
 * @param string $texte
701
 * @return string
702
 **/
703
function corriger_toutes_entites_html($texte) {
704
	if (strpos($texte, '&amp;') === false) {
705
		return $texte;
706
	}
707
708
	return preg_replace(',&amp;(#?[a-z0-9]+;),iS', '&\1', $texte);
709
}
710
711
/**
712
 * Échappe les `&` en `&amp;`
713
 *
714
 * @param string $texte
715
 * @return string
716
 **/
717
function proteger_amp($texte) {
718
	return str_replace('&', '&amp;', $texte);
719
}
720
721
722
/**
723
 * Échappe en entités HTML certains caractères d'un texte
724
 *
725
 * Traduira un code HTML en transformant en entités HTML les caractères
726
 * en dehors du charset de la page ainsi que les `"`, `<` et `>`.
727
 *
728
 * Ceci permet d’insérer le texte d’une balise dans un `<textarea> </textarea>`
729
 * sans dommages.
730
 *
731
 * @filtre
732
 * @link https://www.spip.net/4280
733
 *
734
 * @uses echappe_html()
735
 * @uses echappe_retour()
736
 * @uses proteger_amp()
737
 * @uses corriger_entites_html()
738
 * @uses corriger_toutes_entites_html()
739
 *
740
 * @param string $texte
741
 *   chaine a echapper
742
 * @param bool $tout
743
 *   corriger toutes les `&amp;xx;` en `&xx;`
744
 * @param bool $quote
745
 *   Échapper aussi les simples quotes en `&#039;`
746
 * @return mixed|string
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use string.

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

Loading history...
747
 */
748
function entites_html($texte, $tout = false, $quote = true) {
749 View Code Duplication
	if (!is_string($texte) or !$texte
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...
750
		or strpbrk($texte, "&\"'<>") == false
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing strpbrk($texte, '&"\'<>') of type string to the boolean false. If you are specifically checking for an empty string, consider using the more explicit === '' instead.
Loading history...
751
	) {
752
		return $texte;
753
	}
754
	include_spip('inc/texte');
755
	$flags = ($quote ? ENT_QUOTES : ENT_NOQUOTES);
756
	$flags |= ENT_HTML401;
757
	$texte = spip_htmlspecialchars(echappe_retour(echappe_html($texte, '', true), '', 'proteger_amp'), $flags);
758
	if ($tout) {
759
		return corriger_toutes_entites_html($texte);
760
	} else {
761
		return corriger_entites_html($texte);
762
	}
763
}
764
765
/**
766
 * Convertit les caractères spéciaux HTML dans le charset du site.
767
 *
768
 * @exemple
769
 *     Si le charset de votre site est `utf-8`, `&eacute;` ou `&#233;`
770
 *     sera transformé en `é`
771
 *
772
 * @filtre
773
 * @link https://www.spip.net/5513
774
 *
775
 * @param string $texte
776
 *     Texte à convertir
777
 * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be string|null?

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...
778
 *     Texte converti
779
 **/
780
function filtrer_entites($texte) {
781
	if (strpos($texte, '&') === false) {
782
		return $texte;
783
	}
784
	// filtrer
785
	$texte = html2unicode($texte);
786
	// remettre le tout dans le charset cible
787
	$texte = unicode2charset($texte);
788
	// cas particulier des " et ' qu'il faut filtrer aussi
789
	// (on le faisait deja avec un &quot;)
790 View Code Duplication
	if (strpos($texte, "&#") !== 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...
791
		$texte = str_replace(array("&#039;", "&#39;", "&#034;", "&#34;"), array("'", "'", '"', '"'), $texte);
792
	}
793
794
	return $texte;
795
}
796
797
798
if (!function_exists('filtre_filtrer_entites_dist')) {
799
	/**
800
	 * Version sécurisée de filtrer_entites
801
	 * 
802
	 * @uses interdire_scripts()
803
	 * @uses filtrer_entites()
804
	 * 
805
	 * @param string $t
806
	 * @return string
807
	 */
808
	function filtre_filtrer_entites_dist($t) {
0 ignored issues
show
Best Practice introduced by
The function filtre_filtrer_entites_dist() has been defined more than once; this definition is ignored, only the first definition in config/ecran_securite.php (L658-661) is considered.

This check looks for functions that have already been defined in other files.

Some Codebases, like WordPress, make a practice of defining functions multiple times. This may lead to problems with the detection of function parameters and types. If you really need to do this, you can mark the duplicate definition with the @ignore annotation.

/**
 * @ignore
 */
function getUser() {

}

function getUser($id, $realm) {

}

See also the PhpDoc documentation for @ignore.

Loading history...
809
		include_spip('inc/texte');
810
		return interdire_scripts(filtrer_entites($t));
811
	}
812
}
813
814
815
/**
816
 * Supprime des caractères illégaux
817
 *
818
 * Remplace les caractères de controle par le caractère `-`
819
 *
820
 * @link http://www.w3.org/TR/REC-xml/#charsets
821
 *
822
 * @param string|array $texte
823
 * @return string|array
824
 **/
825
function supprimer_caracteres_illegaux($texte) {
826
	static $from = "\x0\x1\x2\x3\x4\x5\x6\x7\x8\xB\xC\xE\xF\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F";
827
	static $to = null;
828
829
	if (is_array($texte)) {
830
		return array_map('supprimer_caracteres_illegaux', $texte);
831
	}
832
833
	if (!$to) {
834
		$to = str_repeat('-', strlen($from));
835
	}
836
837
	return strtr($texte, $from, $to);
838
}
839
840
/**
841
 * Correction de caractères
842
 *
843
 * Supprimer les caracteres windows non conformes et les caracteres de controle illégaux
844
 *
845
 * @param string|array $texte
846
 * @return string|array
847
 **/
848
function corriger_caracteres($texte) {
849
	$texte = corriger_caracteres_windows($texte);
850
	$texte = supprimer_caracteres_illegaux($texte);
851
852
	return $texte;
853
}
854
855
/**
856
 * Encode du HTML pour transmission XML notamment dans les flux RSS
857
 *
858
 * Ce filtre transforme les liens en liens absolus, importe les entitées html et échappe les tags html.
859
 *
860
 * @filtre
861
 * @link https://www.spip.net/4287
862
 *
863
 * @param string $texte
864
 *     Texte à transformer
865
 * @return string
866
 *     Texte encodé pour XML
867
 */
868
function texte_backend($texte) {
869
870
	static $apostrophe = array("&#8217;", "'"); # n'allouer qu'une fois
871
872
	// si on a des liens ou des images, les passer en absolu
873
	$texte = liens_absolus($texte);
874
875
	// echapper les tags &gt; &lt;
876
	$texte = preg_replace(',&(gt|lt);,S', '&amp;\1;', $texte);
877
878
	// importer les &eacute;
879
	$texte = filtrer_entites($texte);
880
881
	// " -> &quot; et tout ce genre de choses
882
	$u = $GLOBALS['meta']['pcre_u'];
883
	$texte = str_replace("&nbsp;", " ", $texte);
884
	$texte = preg_replace('/\s{2,}/S' . $u, " ", $texte);
885
	// ne pas echapper les sinqle quotes car certains outils de syndication gerent mal
886
	$texte = entites_html($texte, false, false);
887
	// mais bien echapper les double quotes !
888
	$texte = str_replace('"', '&#034;', $texte);
889
890
	// verifier le charset
891
	$texte = charset2unicode($texte);
892
893
	// Caracteres problematiques en iso-latin 1
894
	if (isset($GLOBALS['meta']['charset']) and $GLOBALS['meta']['charset'] == 'iso-8859-1') {
895
		$texte = str_replace(chr(156), '&#156;', $texte);
896
		$texte = str_replace(chr(140), '&#140;', $texte);
897
		$texte = str_replace(chr(159), '&#159;', $texte);
898
	}
899
900
	// l'apostrophe curly pose probleme a certains lecteure de RSS
901
	// et le caractere apostrophe alourdit les squelettes avec PHP
902
	// ==> on les remplace par l'entite HTML
903
	return str_replace($apostrophe, "'", $texte);
904
}
905
906
/**
907
 * Encode et quote du HTML pour transmission XML notamment dans les flux RSS
908
 *
909
 * Comme texte_backend(), mais avec addslashes final pour squelettes avec PHP (rss)
910
 *
911
 * @uses texte_backend()
912
 * @filtre
913
 *
914
 * @param string $texte
915
 *     Texte à transformer
916
 * @return string
917
 *     Texte encodé et quote pour XML
918
 */
919
function texte_backendq($texte) {
920
	return addslashes(texte_backend($texte));
921
}
922
923
924
/**
925
 * Enlève un numéro préfixant un texte
926
 *
927
 * Supprime `10. ` dans la chaine `10. Titre`
928
 *
929
 * @filtre
930
 * @link https://www.spip.net/4314
931
 * @see recuperer_numero() Pour obtenir le numéro
932
 * @example
933
 *     ```
934
 *     [<h1>(#TITRE|supprimer_numero)</h1>]
935
 *     ```
936
 *
937
 * @param string $texte
938
 *     Texte
939
 * @return int|string
940
 *     Numéro de titre, sinon chaîne vide
941
 **/
942
function supprimer_numero($texte) {
943
	return preg_replace(
944
		",^[[:space:]]*([0-9]+)([.)]|" . chr(194) . '?' . chr(176) . ")[[:space:]]+,S",
945
		"", $texte);
946
}
947
948
/**
949
 * Récupère un numéro préfixant un texte
950
 *
951
 * Récupère le numéro `10` dans la chaine `10. Titre`
952
 *
953
 * @filtre
954
 * @link https://www.spip.net/5514
955
 * @see supprimer_numero() Pour supprimer le numéro
956
 * @see balise_RANG_dist() Pour obtenir un numéro de titre
957
 * @example
958
 *     ```
959
 *     [(#TITRE|recuperer_numero)]
960
 *     ```
961
 *
962
 * @param string $texte
963
 *     Texte
964
 * @return int|string
965
 *     Numéro de titre, sinon chaîne vide
966
 **/
967
function recuperer_numero($texte) {
968
	if (preg_match(
969
		",^[[:space:]]*([0-9]+)([.)]|" . chr(194) . '?' . chr(176) . ")[[:space:]]+,S",
970
		$texte, $regs)) {
971
		return strval($regs[1]);
972
	} else {
973
		return '';
974
	}
975
}
976
977
/**
978
 * Suppression basique et brutale de tous les tags
979
 *
980
 * Supprime tous les tags `<...>`.
981
 * Utilisé fréquemment pour écrire des RSS.
982
 *
983
 * @filtre
984
 * @link https://www.spip.net/4315
985
 * @example
986
 *     ```
987
 *     <title>[(#TITRE|supprimer_tags|texte_backend)]</title>
988
 *     ```
989
 *
990
 * @note
991
 *     Ce filtre supprime aussi les signes inférieurs `<` rencontrés.
992
 *
993
 * @param string $texte
994
 *     Texte à échapper
995
 * @param string $rempl
996
 *     Inutilisé.
997
 * @return string
998
 *     Texte converti
999
 **/
1000
function supprimer_tags($texte, $rempl = "") {
1001
	$texte = preg_replace(",<(!--|\w|/|!\[endif|!\[if)[^>]*>,US", $rempl, $texte);
1002
	// ne pas oublier un < final non ferme car coupe
1003
	$texte = preg_replace(",<(!--|\w|/).*$,US", $rempl, $texte);
1004
	// mais qui peut aussi etre un simple signe plus petit que
1005
	$texte = str_replace('<', '&lt;', $texte);
1006
1007
	return $texte;
1008
}
1009
1010
/**
1011
 * Convertit les chevrons de tag en version lisible en HTML
1012
 *
1013
 * Transforme les chevrons de tag `<...>` en entité HTML.
1014
 *
1015
 * @filtre
1016
 * @link https://www.spip.net/5515
1017
 * @example
1018
 *     ```
1019
 *     <pre>[(#TEXTE|echapper_tags)]</pre>
1020
 *     ```
1021
 *
1022
 * @param string $texte
1023
 *     Texte à échapper
1024
 * @param string $rempl
1025
 *     Inutilisé.
1026
 * @return string
1027
 *     Texte converti
1028
 **/
1029
function echapper_tags($texte, $rempl = "") {
0 ignored issues
show
Unused Code introduced by
The parameter $rempl is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1030
	$texte = preg_replace("/<([^>]*)>/", "&lt;\\1&gt;", $texte);
1031
1032
	return $texte;
1033
}
1034
1035
/**
1036
 * Convertit un texte HTML en texte brut
1037
 *
1038
 * Enlève les tags d'un code HTML, élimine les doubles espaces.
1039
 *
1040
 * @filtre
1041
 * @link https://www.spip.net/4317
1042
 * @example
1043
 *     ```
1044
 *     <title>[(#TITRE|textebrut) - ][(#NOM_SITE_SPIP|textebrut)]</title>
1045
 *     ```
1046
 *
1047
 * @param string $texte
1048
 *     Texte à convertir
1049
 * @return string
1050
 *     Texte converti
1051
 **/
1052
function textebrut($texte) {
1053
	$u = $GLOBALS['meta']['pcre_u'];
1054
	$texte = preg_replace('/\s+/S' . $u, " ", $texte);
1055
	$texte = preg_replace("/<(p|br)( [^>]*)?" . ">/iS", "\n\n", $texte);
1056
	$texte = preg_replace("/^\n+/", "", $texte);
1057
	$texte = preg_replace("/\n+$/", "", $texte);
1058
	$texte = preg_replace("/\n +/", "\n", $texte);
1059
	$texte = supprimer_tags($texte);
1060
	$texte = preg_replace("/(&nbsp;| )+/S", " ", $texte);
1061
	// nettoyer l'apostrophe curly qui pose probleme a certains rss-readers, lecteurs de mail...
1062
	$texte = str_replace("&#8217;", "'", $texte);
1063
1064
	return $texte;
1065
}
1066
1067
1068
/**
1069
 * Remplace les liens SPIP en liens ouvrant dans une nouvelle fenetre (target=blank)
1070
 *
1071
 * @filtre
1072
 * @link https://www.spip.net/4297
1073
 *
1074
 * @param string $texte
1075
 *     Texte avec des liens
1076
 * @return string
1077
 *     Texte avec liens ouvrants
1078
 **/
1079
function liens_ouvrants($texte) {
1080
	if (preg_match_all(",(<a\s+[^>]*https?://[^>]*class=[\"']spip_(out|url)\b[^>]+>),imsS",
1081
		$texte, $liens, PREG_PATTERN_ORDER)) {
1082
		foreach ($liens[0] as $a) {
1083
			$rel = 'noopener noreferrer ' . extraire_attribut($a, 'rel');
1084
			$ablank = inserer_attribut($a, 'rel', $rel);
1085
			$ablank = inserer_attribut($ablank, 'target', '_blank');
1086
			$texte = str_replace($a, $ablank, $texte);
1087
		}
1088
	}
1089
1090
	return $texte;
1091
}
1092
1093
/**
1094
 * Ajouter un attribut rel="nofollow" sur tous les liens d'un texte
1095
 *
1096
 * @param string $texte
1097
 * @return string
1098
 */
1099
function liens_nofollow($texte) {
1100
	if (stripos($texte, "<a") === false) {
1101
		return $texte;
1102
	}
1103
1104
	if (preg_match_all(",<a\b[^>]*>,UimsS", $texte, $regs, PREG_PATTERN_ORDER)) {
1105
		foreach ($regs[0] as $a) {
1106
			$rel = extraire_attribut($a, "rel");
1107
			if (strpos($rel, "nofollow") === false) {
1108
				$rel = "nofollow" . ($rel ? " $rel" : "");
1109
				$anofollow = inserer_attribut($a, "rel", $rel);
1110
				$texte = str_replace($a, $anofollow, $texte);
1111
			}
1112
		}
1113
	}
1114
1115
	return $texte;
1116
}
1117
1118
/**
1119
 * Transforme les sauts de paragraphe HTML `p` en simples passages à la ligne `br`
1120
 *
1121
 * @filtre
1122
 * @link https://www.spip.net/4308
1123
 * @example
1124
 *     ```
1125
 *     [<div>(#DESCRIPTIF|PtoBR)[(#NOTES|PtoBR)]</div>]
1126
 *     ```
1127
 *
1128
 * @param string $texte
1129
 *     Texte à transformer
1130
 * @return string
1131
 *     Texte sans paraghaphes
1132
 **/
1133
function PtoBR($texte) {
1134
	$u = $GLOBALS['meta']['pcre_u'];
1135
	$texte = preg_replace("@</p>@iS", "\n", $texte);
1136
	$texte = preg_replace("@<p\b.*>@UiS", "<br />", $texte);
1137
	$texte = preg_replace("@^\s*<br />@S" . $u, "", $texte);
1138
1139
	return $texte;
1140
}
1141
1142
1143
/**
1144
 * Assure qu'un texte ne vas pas déborder d'un bloc
1145
 * par la faute d'un mot trop long (souvent des URLs)
1146
 *
1147
 * Ne devrait plus être utilisé et fait directement en CSS par un style
1148
 * `word-wrap:break-word;`
1149
 *
1150
 * @note
1151
 *   Pour assurer la compatibilité du filtre, on encapsule le contenu par
1152
 *   un `div` ou `span` portant ce style CSS inline.
1153
 *
1154
 * @filtre
1155
 * @link https://www.spip.net/4298
1156
 * @link http://www.alsacreations.com/tuto/lire/1038-gerer-debordement-contenu-css.html
1157
 * @deprecated Utiliser le style CSS `word-wrap:break-word;`
1158
 *
1159
 * @param string $texte Texte
1160
 * @return string Texte encadré du style CSS
1161
 */
1162
function lignes_longues($texte) {
1163
	if (!strlen(trim($texte))) {
1164
		return $texte;
1165
	}
1166
	include_spip('inc/texte');
1167
	$tag = preg_match(',</?(' . _BALISES_BLOCS . ')[>[:space:]],iS', $texte) ?
1168
		'div' : 'span';
1169
1170
	return "<$tag style='word-wrap:break-word;'>$texte</$tag>";
1171
}
1172
1173
/**
1174
 * Passe un texte en majuscules, y compris les accents, en HTML
1175
 *
1176
 * Encadre le texte du style CSS `text-transform: uppercase;`.
1177
 * Le cas spécifique du i turc est géré.
1178
 *
1179
 * @filtre
1180
 * @example
1181
 *     ```
1182
 *     [(#EXTENSION|majuscules)]
1183
 *     ```
1184
 *
1185
 * @param string $texte Texte
1186
 * @return string Texte en majuscule
1187
 */
1188
function majuscules($texte) {
1189
	if (!strlen($texte)) {
1190
		return '';
1191
	}
1192
1193
	// Cas du turc
1194
	if ($GLOBALS['spip_lang'] == 'tr') {
1195
		# remplacer hors des tags et des entites
1196 View Code Duplication
		if (preg_match_all(',<[^<>]+>|&[^;]+;,S', $texte, $regs, PREG_SET_ORDER)) {
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...
1197
			foreach ($regs as $n => $match) {
1198
				$texte = str_replace($match[0], "@@SPIP_TURC$n@@", $texte);
1199
			}
1200
		}
1201
1202
		$texte = str_replace('i', '&#304;', $texte);
1203
1204 View Code Duplication
		if ($regs) {
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...
1205
			foreach ($regs as $n => $match) {
1206
				$texte = str_replace("@@SPIP_TURC$n@@", $match[0], $texte);
1207
			}
1208
		}
1209
	}
1210
1211
	// Cas general
1212
	return "<span style='text-transform: uppercase;'>$texte</span>";
1213
}
1214
1215
/**
1216
 * Retourne une taille en octets humainement lisible
1217
 *
1218
 * Tel que "127.4 ko" ou "3.1 Mo"
1219
 *
1220
 * @example
1221
 *     - `[(#TAILLE|taille_en_octets)]`
1222
 *     - `[(#VAL{123456789}|taille_en_octets)]` affiche `117.7 Mo`
1223
 *
1224
 * @filtre
1225
 * @link https://www.spip.net/4316
1226
 * @param int $taille
1227
 * @return string
1228
 **/
1229
function taille_en_octets($taille) {
1230
	if (!defined('_KILOBYTE')) {
1231
		/**
1232
		 * Définit le nombre d'octets dans un Kilobyte
1233
		 *
1234
		 * @var int
1235
		 **/
1236
		define('_KILOBYTE', 1024);
1237
	}
1238
1239
	if ($taille < 1) {
1240
		return '';
1241
	}
1242
	if ($taille < _KILOBYTE) {
1243
		$taille = _T('taille_octets', array('taille' => $taille));
1244 View Code Duplication
	} elseif ($taille < _KILOBYTE * _KILOBYTE) {
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...
1245
		$taille = _T('taille_ko', array('taille' => round($taille / _KILOBYTE, 1)));
1246
	} elseif ($taille < _KILOBYTE * _KILOBYTE * _KILOBYTE) {
1247
		$taille = _T('taille_mo', array('taille' => round($taille / _KILOBYTE / _KILOBYTE, 1)));
1248 View Code Duplication
	} else {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1249
		$taille = _T('taille_go', array('taille' => round($taille / _KILOBYTE / _KILOBYTE / _KILOBYTE, 2)));
1250
	}
1251
1252
	return $taille;
1253
}
1254
1255
1256
/**
1257
 * Rend une chaine utilisable sans dommage comme attribut HTML
1258
 *
1259
 * @example `<a href="#URL_ARTICLE" title="[(#TITRE|attribut_html)]">#TITRE</a>`
1260
 *
1261
 * @filtre
1262
 * @link https://www.spip.net/4282
1263
 * @uses textebrut()
1264
 * @uses texte_backend()
1265
 *
1266
 * @param string $texte
1267
 *     Texte à mettre en attribut
1268
 * @param bool $textebrut
1269
 *     Passe le texte en texte brut (enlève les balises html) ?
1270
 * @return string
1271
 *     Texte prêt pour être utilisé en attribut HTML
1272
 **/
1273
function attribut_html($texte, $textebrut = true) {
1274
	$u = $GLOBALS['meta']['pcre_u'];
1275
	if ($textebrut) {
1276
		$texte = preg_replace(array(",\n,", ",\s(?=\s),msS" . $u), array(" ", ""), textebrut($texte));
1277
	}
1278
	$texte = texte_backend($texte);
1279
	$texte = str_replace(array("'", '"'), array('&#039;', '&#034;'), $texte);
1280
1281
	return preg_replace(array("/&(amp;|#38;)/", "/&(?![A-Za-z]{0,4}\w{2,3};|#[0-9]{2,5};)/"), array("&", "&#38;"),
1282
		$texte);
1283
}
1284
1285
1286
/**
1287
 * Vider les URL nulles
1288
 *
1289
 * - Vide les URL vides comme `http://` ou `mailto:` (sans rien d'autre)
1290
 * - échappe les entités et gère les `&amp;`
1291
 *
1292
 * @uses entites_html()
1293
 *
1294
 * @param string $url
1295
 *     URL à vérifier et échapper
1296
 * @param bool $entites
1297
 *     `true` pour échapper les entités HTML.
1298
 * @return string
1299
 *     URL ou chaîne vide
1300
 **/
1301
function vider_url($url, $entites = true) {
1302
	# un message pour abs_url
1303
	$GLOBALS['mode_abs_url'] = 'url';
1304
	$url = trim($url);
1305
	$r = ",^(?:" . _PROTOCOLES_STD . '):?/?/?$,iS';
1306
1307
	return preg_match($r, $url) ? '' : ($entites ? entites_html($url) : $url);
1308
}
1309
1310
1311
/**
1312
 * Maquiller une adresse e-mail
1313
 *
1314
 * Remplace `@` par 3 caractères aléatoires.
1315
 *
1316
 * @uses creer_pass_aleatoire()
1317
 *
1318
 * @param string $texte Adresse email
1319
 * @return string Adresse email maquillée
1320
 **/
1321
function antispam($texte) {
1322
	include_spip('inc/acces');
1323
	$masque = creer_pass_aleatoire(3);
1324
1325
	return preg_replace("/@/", " $masque ", $texte);
1326
}
1327
1328
/**
1329
 * Vérifie un accès à faible sécurité
1330
 *
1331
 * Vérifie qu'un visiteur peut accéder à la page demandée,
1332
 * qui est protégée par une clé, calculée à partir du low_sec de l'auteur,
1333
 * et des paramètres le composant l'appel (op, args)
1334
 *
1335
 * @example
1336
 *     `[(#ID_AUTEUR|securiser_acces{#ENV{cle}, rss, #ENV{op}, #ENV{args}}|sinon_interdire_acces)]`
1337
 *
1338
 * @see  bouton_spip_rss() pour générer un lien de faible sécurité pour les RSS privés
1339
 * @see  afficher_low_sec() pour calculer une clé valide
1340
 * @uses verifier_low_sec()
1341
 *
1342
 * @filtre
1343
 * @param int $id_auteur
1344
 *     L'auteur qui demande la page
1345
 * @param string $cle
1346
 *     La clé à tester
1347
 * @param string $dir
1348
 *     Un type d'accès (nom du répertoire dans lequel sont rangés les squelettes demandés, tel que 'rss')
1349
 * @param string $op
1350
 *     Nom de l'opération éventuelle
1351
 * @param string $args
1352
 *     Nom de l'argument calculé
1353
 * @return bool
1354
 *     True si on a le droit d'accès, false sinon.
1355
 **/
1356
function securiser_acces($id_auteur, $cle, $dir, $op = '', $args = '') {
1357
	include_spip('inc/acces');
1358
	if ($op) {
1359
		$dir .= " $op $args";
1360
	}
1361
1362
	return verifier_low_sec($id_auteur, $cle, $dir);
1363
}
1364
1365
/**
1366
 * Retourne le second paramètre lorsque
1367
 * le premier est considere vide, sinon retourne le premier paramètre.
1368
 *
1369
 * En php `sinon($a, 'rien')` retourne `$a`, ou `'rien'` si `$a` est vide.
1370
 * En filtre SPIP `|sinon{#TEXTE, rien}` : affiche `#TEXTE` ou `rien` si `#TEXTE` est vide,
1371
 *
1372
 * @filtre
1373
 * @see filtre_logique() pour la compilation du filtre dans un squelette
1374
 * @link https://www.spip.net/4313
1375
 * @note
1376
 *     L'utilisation de `|sinon` en tant que filtre de squelette
1377
 *     est directement compilé dans `public/references` par la fonction `filtre_logique()`
1378
 *
1379
 * @param mixed $texte
1380
 *     Contenu de reference a tester
1381
 * @param mixed $sinon
1382
 *     Contenu a retourner si le contenu de reference est vide
1383
 * @return mixed
1384
 *     Retourne $texte, sinon $sinon.
1385
 **/
1386
function sinon($texte, $sinon = '') {
1387
	if ($texte or (!is_array($texte) and strlen($texte))) {
1388
		return $texte;
1389
	} else {
1390
		return $sinon;
1391
	}
1392
}
1393
1394
/**
1395
 * Filtre `|choixsivide{vide, pas vide}` alias de `|?{si oui, si non}` avec les arguments inversés
1396
 *
1397
 * @example
1398
 *     `[(#TEXTE|choixsivide{vide, plein})]` affiche vide si le `#TEXTE`
1399
 *     est considéré vide par PHP (chaîne vide, false, 0, tableau vide, etc…).
1400
 *     C'est l'équivalent de `[(#TEXTE|?{plein, vide})]`
1401
 *
1402
 * @filtre
1403
 * @see choixsiegal()
1404
 * @link https://www.spip.net/4189
1405
 *
1406
 * @param mixed $a
1407
 *     La valeur à tester
1408
 * @param mixed $vide
1409
 *     Ce qui est retourné si `$a` est considéré vide
1410
 * @param mixed $pasvide
1411
 *     Ce qui est retourné sinon
1412
 * @return mixed
1413
 **/
1414
function choixsivide($a, $vide, $pasvide) {
1415
	return $a ? $pasvide : $vide;
1416
}
1417
1418
/**
1419
 * Filtre `|choixsiegal{valeur, sioui, sinon}`
1420
 *
1421
 * @example
1422
 *     `#LANG_DIR|choixsiegal{ltr,left,right}` retourne `left` si
1423
 *      `#LANG_DIR` vaut `ltr` et `right` sinon.
1424
 *
1425
 * @filtre
1426
 * @link https://www.spip.net/4148
1427
 *
1428
 * @param mixed $a1
1429
 *     La valeur à tester
1430
 * @param mixed $a2
1431
 *     La valeur de comparaison
1432
 * @param mixed $v
1433
 *     Ce qui est retourné si la comparaison est vraie
1434
 * @param mixed $f
1435
 *     Ce qui est retourné sinon
1436
 * @return mixed
1437
 **/
1438
function choixsiegal($a1, $a2, $v, $f) {
1439
	return ($a1 == $a2) ? $v : $f;
1440
}
1441
1442
//
1443
// Export iCal
1444
//
1445
1446
/**
1447
 * Adapte un texte pour être inséré dans une valeur d'un export ICAL
1448
 *
1449
 * Passe le texte en utf8, enlève les sauts de lignes et échappe les virgules.
1450
 *
1451
 * @example `SUMMARY:[(#TITRE|filtrer_ical)]`
1452
 * @filtre
1453
 *
1454
 * @param string $texte
1455
 * @return string
1456
 **/
1457
function filtrer_ical($texte) {
1458
	#include_spip('inc/charsets');
1459
	$texte = html2unicode($texte);
1460
	$texte = unicode2charset(charset2unicode($texte, $GLOBALS['meta']['charset'], 1), 'utf-8');
0 ignored issues
show
Unused Code introduced by
The call to charset2unicode() has too many arguments starting with 1.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1461
	$texte = preg_replace("/\n/", " ", $texte);
1462
	$texte = preg_replace("/,/", "\,", $texte);
1463
1464
	return $texte;
1465
}
1466
1467
1468
/**
1469
 * Transforme les sauts de ligne simples en sauts forcés avec `_ `
1470
 *
1471
 * Ne modifie pas les sauts de paragraphe (2 sauts consécutifs au moins),
1472
 * ou les retours à l'intérieur de modèles ou de certaines balises html.
1473
 *
1474
 * @note
1475
 *     Cette fonction pouvait être utilisée pour forcer les alinéas,
1476
 *     (retours à la ligne sans saut de paragraphe), mais ce traitement
1477
 *     est maintenant automatique.
1478
 *     Cf. plugin Textwheel et la constante _AUTOBR
1479
 *
1480
 * @uses echappe_html()
1481
 * @uses echappe_retour()
1482
 *
1483
 * @param string $texte
1484
 * @param string $delim
1485
 *      Ce par quoi sont remplacés les sauts
1486
 * @return string
1487
 **/
1488
function post_autobr($texte, $delim = "\n_ ") {
1489
	if (!function_exists('echappe_html')) {
1490
		include_spip('inc/texte_mini');
1491
	}
1492
	$texte = str_replace("\r\n", "\r", $texte);
1493
	$texte = str_replace("\r", "\n", $texte);
1494
1495 View Code Duplication
	if (preg_match(",\n+$,", $texte, $fin)) {
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...
1496
		$texte = substr($texte, 0, -strlen($fin = $fin[0]));
1497
	} else {
1498
		$fin = '';
1499
	}
1500
1501
	$texte = echappe_html($texte, '', true);
1502
1503
	// echapper les modeles
1504
	if (strpos($texte, "<") !== false) {
1505
		include_spip('inc/lien');
1506
		if (defined('_PREG_MODELE')) {
1507
			$preg_modeles = "@" . _PREG_MODELE . "@imsS";
1508
			$texte = echappe_html($texte, '', true, $preg_modeles);
1509
		}
1510
	}
1511
1512
	$debut = '';
1513
	$suite = $texte;
1514
	while ($t = strpos('-' . $suite, "\n", 1)) {
1515
		$debut .= substr($suite, 0, $t - 1);
1516
		$suite = substr($suite, $t);
1517
		$car = substr($suite, 0, 1);
1518
		if (($car <> '-') and ($car <> '_') and ($car <> "\n") and ($car <> "|") and ($car <> "}")
1519
			and !preg_match(',^\s*(\n|</?(quote|div|dl|dt|dd)|$),S', ($suite))
1520
			and !preg_match(',</?(quote|div|dl|dt|dd)> *$,iS', $debut)
1521
		) {
1522
			$debut .= $delim;
1523
		} else {
1524
			$debut .= "\n";
1525
		}
1526 View Code Duplication
		if (preg_match(",^\n+,", $suite, $regs)) {
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...
1527
			$debut .= $regs[0];
1528
			$suite = substr($suite, strlen($regs[0]));
1529
		}
1530
	}
1531
	$texte = $debut . $suite;
1532
1533
	$texte = echappe_retour($texte);
1534
1535
	return $texte . $fin;
1536
}
1537
1538
1539
/**
1540
 * Expression régulière pour obtenir le contenu des extraits idiomes `<:module:cle:>`
1541
 *
1542
 * @var string
1543
 */
1544
define('_EXTRAIRE_IDIOME', '@<:(?:([a-z0-9_]+):)?([a-z0-9_]+):>@isS');
1545
1546
/**
1547
 * Extrait une langue des extraits idiomes (`<:module:cle_de_langue:>`)
1548
 *
1549
 * Retrouve les balises `<:cle_de_langue:>` d'un texte et remplace son contenu
1550
 * par l'extrait correspondant à la langue demandée (si possible), sinon dans la
1551
 * langue par défaut du site.
1552
 *
1553
 * Ne pas mettre de span@lang=fr si on est déjà en fr.
1554
 *
1555
 * @filtre
1556
 * @uses inc_traduire_dist()
1557
 * @uses code_echappement()
1558
 * @uses echappe_retour()
1559
 *
1560
 * @param string $letexte
1561
 * @param string $lang
0 ignored issues
show
Documentation introduced by
Should the type for parameter $lang 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...
1562
 *     Langue à retrouver (si vide, utilise la langue en cours).
1563
 * @param array $options Options {
1564
 * @type bool $echappe_span
1565
 *         True pour échapper les balises span (false par défaut)
1566
 * @type string $lang_defaut
1567
 *         Code de langue : permet de définir la langue utilisée par défaut,
1568
 *         en cas d'absence de traduction dans la langue demandée.
1569
 *         Par défaut la langue du site.
1570
 *         Indiquer 'aucune' pour ne pas retourner de texte si la langue
1571
 *         exacte n'a pas été trouvée.
1572
 * }
1573
 * @return string
1574
 **/
1575
function extraire_idiome($letexte, $lang = null, $options = array()) {
1576
	static $traduire = false;
1577
	if ($letexte
1578
		and preg_match_all(_EXTRAIRE_IDIOME, $letexte, $regs, PREG_SET_ORDER)
1579
	) {
1580
		if (!$traduire) {
1581
			$traduire = charger_fonction('traduire', 'inc');
1582
			include_spip('inc/lang');
1583
		}
1584
		if (!$lang) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $lang of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1585
			$lang = $GLOBALS['spip_lang'];
1586
		}
1587
		// Compatibilité avec le prototype de fonction précédente qui utilisait un boolean
1588
		if (is_bool($options)) {
1589
			$options = array('echappe_span' => $options);
1590
		}
1591 View Code Duplication
		if (!isset($options['echappe_span'])) {
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...
1592
			$options = array_merge($options, array('echappe_span' => false));
1593
		}
1594
1595
		foreach ($regs as $reg) {
1596
			$cle = ($reg[1] ? $reg[1] . ':' : '') . $reg[2];
1597
			$desc = $traduire($cle, $lang, true);
1598
			$l = $desc->langue;
1599
			// si pas de traduction, on laissera l'écriture de l'idiome entier dans le texte.
1600
			if (strlen($desc->texte)) {
1601
				$trad = code_echappement($desc->texte, 'idiome', false);
1602
				if ($l !== $lang) {
1603
					$trad = str_replace("'", '"', inserer_attribut($trad, 'lang', $l));
1604
				}
1605 View Code Duplication
				if (lang_dir($l) !== lang_dir($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...
1606
					$trad = str_replace("'", '"', inserer_attribut($trad, 'dir', lang_dir($l)));
1607
				}
1608
				if (!$options['echappe_span']) {
1609
					$trad = echappe_retour($trad, 'idiome');
1610
				}
1611
				$letexte = str_replace($reg[0], $trad, $letexte);
1612
			}
1613
		}
1614
	}
1615
	return $letexte;
1616
}
1617
1618
/**
1619
 * Expression régulière pour obtenir le contenu des extraits polyglottes `<multi>`
1620
 *
1621
 * @var string
1622
 */
1623
define('_EXTRAIRE_MULTI', "@<multi>(.*?)</multi>@sS");
1624
1625
1626
/**
1627
 * Extrait une langue des extraits polyglottes (`<multi>`)
1628
 *
1629
 * Retrouve les balises `<multi>` d'un texte et remplace son contenu
1630
 * par l'extrait correspondant à la langue demandée.
1631
 *
1632
 * Si la langue demandée n'est pas trouvée dans le multi, ni une langue
1633
 * approchante (exemple `fr` si on demande `fr_TU`), on retourne l'extrait
1634
 * correspondant à la langue par défaut (option 'lang_defaut'), qui est
1635
 * par défaut la langue du site. Et si l'extrait n'existe toujours pas
1636
 * dans cette langue, ça utilisera la première langue utilisée
1637
 * dans la balise `<multi>`.
1638
 *
1639
 * Ne pas mettre de span@lang=fr si on est déjà en fr.
1640
 *
1641
 * @filtre
1642
 * @link https://www.spip.net/5332
1643
 *
1644
 * @uses extraire_trads()
1645
 * @uses approcher_langue()
1646
 * @uses lang_typo()
1647
 * @uses code_echappement()
1648
 * @uses echappe_retour()
1649
 *
1650
 * @param string $letexte
1651
 * @param string $lang
0 ignored issues
show
Documentation introduced by
Should the type for parameter $lang 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...
1652
 *     Langue à retrouver (si vide, utilise la langue en cours).
1653
 * @param array $options Options {
1654
 * @type bool $echappe_span
1655
 *         True pour échapper les balises span (false par défaut)
1656
 * @type string $lang_defaut
1657
 *         Code de langue : permet de définir la langue utilisée par défaut,
1658
 *         en cas d'absence de traduction dans la langue demandée.
1659
 *         Par défaut la langue du site.
1660
 *         Indiquer 'aucune' pour ne pas retourner de texte si la langue
1661
 *         exacte n'a pas été trouvée.
1662
 * }
1663
 * @return string
1664
 **/
1665
function extraire_multi($letexte, $lang = null, $options = array()) {
1666
1667
	if ($letexte
1668
		and preg_match_all(_EXTRAIRE_MULTI, $letexte, $regs, PREG_SET_ORDER)
1669
	) {
1670
		if (!$lang) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $lang of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1671
			$lang = $GLOBALS['spip_lang'];
1672
		}
1673
1674
		// Compatibilité avec le prototype de fonction précédente qui utilisait un boolean
1675
		if (is_bool($options)) {
1676
			$options = array('echappe_span' => $options, 'lang_defaut' => _LANGUE_PAR_DEFAUT);
1677
		}
1678 View Code Duplication
		if (!isset($options['echappe_span'])) {
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...
1679
			$options = array_merge($options, array('echappe_span' => false));
1680
		}
1681
		if (!isset($options['lang_defaut'])) {
1682
			$options = array_merge($options, array('lang_defaut' => _LANGUE_PAR_DEFAUT));
1683
		}
1684
1685
		include_spip('inc/lang');
1686
		foreach ($regs as $reg) {
1687
			// chercher la version de la langue courante
1688
			$trads = extraire_trads($reg[1]);
1689
			if ($l = approcher_langue($trads, $lang)) {
1690
				$trad = $trads[$l];
1691
			} else {
1692
				if ($options['lang_defaut'] == 'aucune') {
1693
					$trad = '';
1694
				} else {
1695
					// langue absente, prendre le fr ou une langue précisée (meme comportement que inc/traduire.php)
1696
					// ou la premiere dispo
1697
					// mais typographier le texte selon les regles de celle-ci
1698
					// Attention aux blocs multi sur plusieurs lignes
1699
					if (!$l = approcher_langue($trads, $options['lang_defaut'])) {
1700
						$l = key($trads);
1701
					}
1702
					$trad = $trads[$l];
1703
					$typographie = charger_fonction(lang_typo($l), 'typographie');
1704
					$trad = $typographie($trad);
1705
					// Tester si on echappe en span ou en div
1706
					// il ne faut pas echapper en div si propre produit un seul paragraphe
1707
					include_spip('inc/texte');
1708
					$trad_propre = preg_replace(",(^<p[^>]*>|</p>$),Uims", "", propre($trad));
1709
					$mode = preg_match(',</?(' . _BALISES_BLOCS . ')[>[:space:]],iS', $trad_propre) ? 'div' : 'span';
1710
					$trad = code_echappement($trad, 'multi', false, $mode);
1711
					$trad = str_replace("'", '"', inserer_attribut($trad, 'lang', $l));
1712 View Code Duplication
					if (lang_dir($l) !== lang_dir($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...
1713
						$trad = str_replace("'", '"', inserer_attribut($trad, 'dir', lang_dir($l)));
1714
					}
1715
					if (!$options['echappe_span']) {
1716
						$trad = echappe_retour($trad, 'multi');
1717
					}
1718
				}
1719
			}
1720
			$letexte = str_replace($reg[0], $trad, $letexte);
1721
		}
1722
	}
1723
1724
	return $letexte;
1725
}
1726
1727
/**
1728
 * Convertit le contenu d'une balise `<multi>` en un tableau
1729
 *
1730
 * Exemple de blocs.
1731
 * - `texte par défaut [fr] en français [en] en anglais`
1732
 * - `[fr] en français [en] en anglais`
1733
 *
1734
 * @param string $bloc
1735
 *     Le contenu intérieur d'un bloc multi
1736
 * @return array [code de langue => texte]
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array<string,string>.

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

Loading history...
1737
 *     Peut retourner un code de langue vide, lorsqu'un texte par défaut est indiqué.
1738
 **/
1739
function extraire_trads($bloc) {
1740
	$lang = '';
1741
// ce reg fait planter l'analyse multi s'il y a de l'{italique} dans le champ
1742
//	while (preg_match("/^(.*?)[{\[]([a-z_]+)[}\]]/siS", $bloc, $regs)) {
1743
	while (preg_match("/^(.*?)[\[]([a-z_]+)[\]]/siS", $bloc, $regs)) {
1744
		$texte = trim($regs[1]);
1745
		if ($texte or $lang) {
1746
			$trads[$lang] = $texte;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$trads was never initialized. Although not strictly required by PHP, it is generally a good practice to add $trads = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
1747
		}
1748
		$bloc = substr($bloc, strlen($regs[0]));
1749
		$lang = $regs[2];
1750
	}
1751
	$trads[$lang] = $bloc;
0 ignored issues
show
Bug introduced by
The variable $trads does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1752
1753
	return $trads;
1754
}
1755
1756
1757
/**
1758
 * Calculer l'initiale d'un nom
1759
 *
1760
 * @param string $nom
1761
 * @return string L'initiale en majuscule
1762
 */
1763
function filtre_initiale($nom) {
1764
	return spip_substr(trim(strtoupper(extraire_multi($nom))), 0, 1);
1765
}
1766
1767
1768
/**
1769
 * Retourne la donnée si c'est la première fois qu'il la voit
1770
 *
1771
 * Il est possible de gérer différentes "familles" de données avec
1772
 * le second paramètre.
1773
 *
1774
 * @filtre
1775
 * @link https://www.spip.net/4320
1776
 * @example
1777
 *     ```
1778
 *     [(#ID_SECTEUR|unique)]
1779
 *     [(#ID_SECTEUR|unique{tete})] n'a pas d'incidence sur
1780
 *     [(#ID_SECTEUR|unique{pied})]
1781
 *     [(#ID_SECTEUR|unique{pied,1})] affiche le nombre d'éléments.
1782
 *     Préférer totefois #TOTAL_UNIQUE{pied}
1783
 *     ```
1784
 *
1785
 * @todo
1786
 *    Ameliorations possibles :
1787
 *
1788
 *    1) si la donnée est grosse, mettre son md5 comme clé
1789
 *    2) purger $mem quand on change de squelette (sinon bug inclusions)
1790
 *
1791
 * @param string $donnee
1792
 *      Donnée que l'on souhaite unique
1793
 * @param string $famille
1794
 *      Famille de stockage (1 unique donnée par famille)
1795
 *
1796
 *      - _spip_raz_ : (interne) Vide la pile de mémoire et la retourne
1797
 *      - _spip_set_ : (interne) Affecte la pile de mémoire avec la donnée
1798
 * @param bool $cpt
1799
 *      True pour obtenir le nombre d'éléments différents stockés
1800
 * @return string|int|array|null|void
1801
 *
1802
 *      - string : Donnée si c'est la première fois qu'elle est vue
1803
 *      - void : si la donnée a déjà été vue
1804
 *      - int : si l'on demande le nombre d'éléments
1805
 *      - array (interne) : si on dépile
1806
 *      - null (interne) : si on empile
1807
 **/
1808
function unique($donnee, $famille = '', $cpt = false) {
1809
	static $mem = array();
1810
	// permettre de vider la pile et de la restaurer
1811
	// pour le calcul de introduction...
1812
	if ($famille == '_spip_raz_') {
1813
		$tmp = $mem;
1814
		$mem = array();
1815
1816
		return $tmp;
1817
	} elseif ($famille == '_spip_set_') {
1818
		$mem = $donnee;
1819
1820
		return;
1821
	}
1822
	// eviter une notice
1823
	if (!isset($mem[$famille])) {
1824
		$mem[$famille] = array();
1825
	}
1826
	if ($cpt) {
1827
		return count($mem[$famille]);
1828
	}
1829
	// eviter une notice
1830
	if (!isset($mem[$famille][$donnee])) {
1831
		$mem[$famille][$donnee] = 0;
1832
	}
1833
	if (!($mem[$famille][$donnee]++)) {
1834
		return $donnee;
1835
	}
1836
}
1837
1838
1839
/**
1840
 * Filtre qui alterne des valeurs en fonction d'un compteur
1841
 *
1842
 * Affiche à tour de rôle et dans l'ordre, un des arguments transmis
1843
 * à chaque incrément du compteur.
1844
 *
1845
 * S'il n'y a qu'un seul argument, et que c'est un tableau,
1846
 * l'alternance se fait sur les valeurs du tableau.
1847
 *
1848
 * Souvent appliqué à l'intérieur d'une boucle, avec le compteur `#COMPTEUR_BOUCLE`
1849
 *
1850
 * @example
1851
 *     - `[(#COMPTEUR_BOUCLE|alterner{bleu,vert,rouge})]`
1852
 *     - `[(#COMPTEUR_BOUCLE|alterner{#LISTE{bleu,vert,rouge}})]`
1853
 *
1854
 * @filtre
1855
 * @link https://www.spip.net/4145
1856
 *
1857
 * @param int $i
1858
 *     Le compteur
1859
 * @return mixed
1860
 *     Une des valeurs en fonction du compteur.
1861
 **/
1862
function alterner($i) {
1863
	// recuperer les arguments (attention fonctions un peu space)
1864
	$num = func_num_args();
1865
	$args = func_get_args();
1866
1867
	if ($num == 2 && is_array($args[1])) {
1868
		$args = $args[1];
1869
		array_unshift($args, '');
1870
		$num = count($args);
1871
	}
1872
1873
	// renvoyer le i-ieme argument, modulo le nombre d'arguments
1874
	return $args[(intval($i) - 1) % ($num - 1) + 1];
1875
}
1876
1877
1878
/**
1879
 * Récupérer un attribut d'une balise HTML
1880
 *
1881
 * la regexp est mortelle : cf. `tests/unit/filtres/extraire_attribut.php`
1882
 * Si on a passé un tableau de balises, renvoyer un tableau de résultats
1883
 * (dans ce cas l'option `$complet` n'est pas disponible)
1884
 *
1885
 * @param string|array $balise
1886
 *     Texte ou liste de textes dont on veut extraire des balises
1887
 * @param string $attribut
1888
 *     Nom de l'attribut désiré
1889
 * @param bool $complet
1890
 *     True pour retourner un tableau avec
1891
 *     - le texte de la balise
1892
 *     - l'ensemble des résultats de la regexp ($r)
1893
 * @return string|array
1894
 *     - Texte de l'attribut retourné, ou tableau des texte d'attributs
1895
 *       (si 1er argument tableau)
1896
 *     - Tableau complet (si 2e argument)
1897
 **/
1898
function extraire_attribut($balise, $attribut, $complet = false) {
1899
	if (is_array($balise)) {
1900
		array_walk(
1901
			$balise,
1902
			function(&$a, $key, $t){
1903
				$a = extraire_attribut($a, $t);
1904
			},
1905
			$attribut
1906
		);
1907
1908
		return $balise;
1909
	}
1910
	if (preg_match(
1911
		',(^.*?<(?:(?>\s*)(?>[\w:.-]+)(?>(?:=(?:"[^"]*"|\'[^\']*\'|[^\'"]\S*))?))*?)(\s+'
1912
		. $attribut
1913
		. '(?:=\s*("[^"]*"|\'[^\']*\'|[^\'"]\S*))?)()((?:[\s/][^>]*)?>.*),isS',
1914
1915
		$balise, $r)) {
1916
		if (isset($r[3][0]) and ($r[3][0] == '"' || $r[3][0] == "'")) {
1917
			$r[4] = substr($r[3], 1, -1);
1918
			$r[3] = $r[3][0];
1919
		} elseif ($r[3] !== '') {
1920
			$r[4] = $r[3];
1921
			$r[3] = '';
1922
		} else {
1923
			$r[4] = trim($r[2]);
1924
		}
1925
		$att = $r[4];
1926 View Code Duplication
		if (strpos($att, "&#") !== 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...
1927
			$att = str_replace(array("&#039;", "&#39;", "&#034;", "&#34;"), array("'", "'", '"', '"'), $att);
1928
		}
1929
		$att = filtrer_entites($att);
1930
	} else {
1931
		$att = null;
1932
	}
1933
1934
	if ($complet) {
1935
		return array($att, $r);
1936
	} else {
1937
		return $att;
1938
	}
1939
}
1940
1941
/**
1942
 * Insérer (ou modifier) un attribut html dans une balise
1943
 *
1944
 * @example
1945
 *     - `[(#LOGO_ARTICLE|inserer_attribut{class, logo article})]`
1946
 *     - `[(#LOGO_ARTICLE|inserer_attribut{alt, #TTTRE|attribut_html|couper{60}})]`
1947
 *     - `[(#FICHIER|image_reduire{40}|inserer_attribut{data-description, #DESCRIPTIF})]`
1948
 *       Laissera les balises HTML de la valeur (ici `#DESCRIPTIF`) si on n'applique pas le
1949
 *       filtre `attribut_html` dessus.
1950
 *
1951
 * @filtre
1952
 * @link https://www.spip.net/4294
1953
 * @uses attribut_html()
1954
 * @uses extraire_attribut()
1955
 *
1956
 * @param string $balise
1957
 *     Code html de la balise (ou contenant une balise)
1958
 * @param string $attribut
1959
 *     Nom de l'attribut html à modifier
1960
 * @param string $val
1961
 *     Valeur de l'attribut à appliquer
1962
 * @param bool $proteger
1963
 *     Prépare la valeur en tant qu'attribut de balise (mais conserve les balises html).
1964
 * @param bool $vider
1965
 *     True pour vider l'attribut. Une chaîne vide pour `$val` fera pareil.
1966
 * @return string
1967
 *     Code html modifié
1968
 **/
1969
function inserer_attribut($balise, $attribut, $val, $proteger = true, $vider = false) {
1970
	// preparer l'attribut
1971
	// supprimer les &nbsp; etc mais pas les balises html
1972
	// qui ont un sens dans un attribut value d'un input
1973
	if ($proteger) {
1974
		$val = attribut_html($val, false);
1975
	}
1976
1977
	// echapper les ' pour eviter tout bug
1978
	$val = str_replace("'", "&#039;", $val);
1979
	if ($vider and strlen($val) == 0) {
1980
		$insert = '';
1981
	} else {
1982
		$insert = " $attribut='$val'";
1983
	}
1984
1985
	list($old, $r) = extraire_attribut($balise, $attribut, true);
1986
1987
	if ($old !== null) {
1988
		// Remplacer l'ancien attribut du meme nom
1989
		$balise = $r[1] . $insert . $r[5];
1990
	} else {
1991
		// preferer une balise " />" (comme <img />)
1992
		if (preg_match(',/>,', $balise)) {
1993
			$balise = preg_replace(",\s?/>,S", $insert . " />", $balise, 1);
1994
		} // sinon une balise <a ...> ... </a>
1995
		else {
1996
			$balise = preg_replace(",\s?>,S", $insert . ">", $balise, 1);
1997
		}
1998
	}
1999
2000
	return $balise;
2001
}
2002
2003
/**
2004
 * Supprime un attribut HTML
2005
 *
2006
 * @example `[(#LOGO_ARTICLE|vider_attribut{class})]`
2007
 *
2008
 * @filtre
2009
 * @link https://www.spip.net/4142
2010
 * @uses inserer_attribut()
2011
 * @see  extraire_attribut()
2012
 *
2013
 * @param string $balise Code HTML de l'élément
2014
 * @param string $attribut Nom de l'attribut à enlever
2015
 * @return string Code HTML sans l'attribut
2016
 **/
2017
function vider_attribut($balise, $attribut) {
2018
	return inserer_attribut($balise, $attribut, '', false, true);
2019
}
2020
2021
2022
/**
2023
 * Un filtre pour déterminer le nom du statut des inscrits
2024
 *
2025
 * @param void|int $id
2026
 * @param string $mode
2027
 * @return string
2028
 */
2029
function tester_config($id, $mode = '') {
2030
	include_spip('action/inscrire_auteur');
2031
2032
	return tester_statut_inscription($mode, $id);
2033
}
2034
2035
//
2036
// Quelques fonctions de calcul arithmetique
2037
//
2038
function floatstr($a) { return str_replace(',','.',(string)floatval($a)); }
2039
function strize($f, $a, $b) { return floatstr($f(floatstr($a),floatstr($b))); }
2040
2041
/**
2042
 * Additionne 2 nombres
2043
 *
2044
 * @filtre
2045
 * @link https://www.spip.net/4307
2046
 * @see moins()
2047
 * @example
2048
 *     ```
2049
 *     [(#VAL{28}|plus{14})]
2050
 *     ```
2051
 *
2052
 * @param int $a
2053
 * @param int $b
2054
 * @return int $a+$b
2055
 **/
2056
function plus($a, $b) {
2057
	return $a + $b;
2058
}
2059
function strplus($a, $b) {return strize('plus', $a, $b);}
2060
/**
2061
 * Soustrait 2 nombres
2062
 *
2063
 * @filtre
2064
 * @link https://www.spip.net/4302
2065
 * @see plus()
2066
 * @example
2067
 *     ```
2068
 *     [(#VAL{28}|moins{14})]
2069
 *     ```
2070
 *
2071
 * @param int $a
2072
 * @param int $b
2073
 * @return int $a-$b
2074
 **/
2075
function moins($a, $b) {
2076
	return $a - $b;
2077
}
2078
function strmoins($a, $b) {return strize('moins', $a, $b);}
2079
2080
/**
2081
 * Multiplie 2 nombres
2082
 *
2083
 * @filtre
2084
 * @link https://www.spip.net/4304
2085
 * @see div()
2086
 * @see modulo()
2087
 * @example
2088
 *     ```
2089
 *     [(#VAL{28}|mult{14})]
2090
 *     ```
2091
 *
2092
 * @param int $a
2093
 * @param int $b
2094
 * @return int $a*$b
2095
 **/
2096
function mult($a, $b) {
2097
	return $a * $b;
2098
}
2099
function strmult($a, $b) {return strize('mult', $a, $b);}
2100
2101
/**
2102
 * Divise 2 nombres
2103
 *
2104
 * @filtre
2105
 * @link https://www.spip.net/4279
2106
 * @see mult()
2107
 * @see modulo()
2108
 * @example
2109
 *     ```
2110
 *     [(#VAL{28}|div{14})]
2111
 *     ```
2112
 *
2113
 * @param int $a
2114
 * @param int $b
2115
 * @return int $a/$b (ou 0 si $b est nul)
2116
 **/
2117
function div($a, $b) {
2118
	return $b ? $a / $b : 0;
2119
}
2120
function strdiv($a, $b) {return strize('div', $a, $b);}
2121
2122
/**
2123
 * Retourne le modulo 2 nombres
2124
 *
2125
 * @filtre
2126
 * @link https://www.spip.net/4301
2127
 * @see mult()
2128
 * @see div()
2129
 * @example
2130
 *     ```
2131
 *     [(#VAL{28}|modulo{14})]
2132
 *     ```
2133
 *
2134
 * @param int $nb
2135
 * @param int $mod
2136
 * @param int $add
2137
 * @return int ($nb % $mod) + $add
2138
 **/
2139
function modulo($nb, $mod, $add = 0) {
2140
	return ($mod ? $nb % $mod : 0) + $add;
2141
}
2142
2143
2144
/**
2145
 * Vérifie qu'un nom (d'auteur) ne comporte pas d'autres tags que <multi>
2146
 * et ceux volontairement spécifiés dans la constante
2147
 *
2148
 * @param string $nom
2149
 *      Nom (signature) proposé
2150
 * @return bool
2151
 *      - false si pas conforme,
2152
 *      - true sinon
2153
 **/
2154
function nom_acceptable($nom) {
2155
	if (!is_string($nom)) {
2156
		return false;
2157
	}
2158
	if (!defined('_TAGS_NOM_AUTEUR')) {
2159
		define('_TAGS_NOM_AUTEUR', '');
2160
	}
2161
	$tags_acceptes = array_unique(explode(',', 'multi,' . _TAGS_NOM_AUTEUR));
2162
	foreach ($tags_acceptes as $tag) {
2163
		if (strlen($tag)) {
2164
			$remp1[] = '<' . trim($tag) . '>';
0 ignored issues
show
Coding Style Comprehensibility introduced by
$remp1 was never initialized. Although not strictly required by PHP, it is generally a good practice to add $remp1 = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
2165
			$remp1[] = '</' . trim($tag) . '>';
0 ignored issues
show
Bug introduced by
The variable $remp1 does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
2166
			$remp2[] = '\x60' . trim($tag) . '\x61';
0 ignored issues
show
Coding Style Comprehensibility introduced by
$remp2 was never initialized. Although not strictly required by PHP, it is generally a good practice to add $remp2 = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
2167
			$remp2[] = '\x60/' . trim($tag) . '\x61';
2168
		}
2169
	}
2170
	$v_nom = str_replace($remp2, $remp1, supprimer_tags(str_replace($remp1, $remp2, $nom)));
0 ignored issues
show
Bug introduced by
The variable $remp2 does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
2171
2172
	return str_replace('&lt;', '<', $v_nom) == $nom;
2173
}
2174
2175
2176
/**
2177
 * Vérifier la conformité d'une ou plusieurs adresses email (suivant RFC 822)
2178
 *
2179
 * @param string|array $adresses
2180
 *      Adresse ou liste d'adresse
2181
 *      si on fournit un tableau, il est filtre et la fonction renvoie avec uniquement les adresses email valides (donc possiblement vide)
2182
 * @return bool|string|array
2183
 *      - false si une des adresses n'est pas conforme,
2184
 *      - la normalisation de la dernière adresse donnée sinon
2185
 *      - renvoie un tableau si l'entree est un tableau
2186
 **/
2187
function email_valide($adresses) {
2188
	if (is_array($adresses)) {
2189
		$adresses = array_map('email_valide', $adresses);
2190
		$adresses = array_filter($adresses);
2191
		return $adresses;
2192
	}
2193
2194
	$email_valide = charger_fonction('email_valide', 'inc');
2195
	return $email_valide($adresses);
2196
}
2197
2198
/**
2199
 * Permet d'afficher un symbole à côté des liens pointant vers les
2200
 * documents attachés d'un article (liens ayant `rel=enclosure`).
2201
 *
2202
 * @filtre
2203
 * @link https://www.spip.net/4134
2204
 *
2205
 * @param string $tags Texte
2206
 * @return string Texte
2207
 **/
2208
function afficher_enclosures($tags) {
2209
	$s = array();
2210
	foreach (extraire_balises($tags, 'a') as $tag) {
2211
		if (extraire_attribut($tag, 'rel') == 'enclosure'
2212
			and $t = extraire_attribut($tag, 'href')
2213
		) {
2214
			$s[] = preg_replace(',>[^<]+</a>,S',
2215
				'>'
2216
				. http_img_pack('attachment-16.png', $t,
0 ignored issues
show
Bug introduced by
It seems like $t defined by extraire_attribut($tag, 'href') on line 2212 can also be of type array; however, http_img_pack() 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...
2217
					'title="' . attribut_html($t) . '"')
0 ignored issues
show
Bug introduced by
It seems like $t defined by extraire_attribut($tag, 'href') on line 2212 can also be of type array; however, attribut_html() 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...
2218
				. '</a>', $tag);
2219
		}
2220
	}
2221
2222
	return join('&nbsp;', $s);
2223
}
2224
2225
/**
2226
 * Filtre des liens HTML `<a>` selon la valeur de leur attribut `rel`
2227
 * et ne retourne que ceux là.
2228
 *
2229
 * @filtre
2230
 * @link https://www.spip.net/4187
2231
 *
2232
 * @param string $tags Texte
2233
 * @param string $rels Attribut `rel` à capturer (ou plusieurs séparés par des virgules)
2234
 * @return string Liens trouvés
2235
 **/
2236
function afficher_tags($tags, $rels = 'tag,directory') {
2237
	$s = array();
2238
	foreach (extraire_balises($tags, 'a') as $tag) {
2239
		$rel = extraire_attribut($tag, 'rel');
2240
		if (strstr(",$rels,", ",$rel,")) {
2241
			$s[] = $tag;
2242
		}
2243
	}
2244
2245
	return join(', ', $s);
2246
}
2247
2248
2249
/**
2250
 * Convertir les médias fournis par un flux RSS (podcasts)
2251
 * en liens conformes aux microformats
2252
 *
2253
 * Passe un `<enclosure url="fichier" length="5588242" type="audio/mpeg"/>`
2254
 * au format microformat `<a rel="enclosure" href="fichier" ...>fichier</a>`.
2255
 *
2256
 * Peut recevoir un `<link` ou un `<media:content` parfois.
2257
 *
2258
 * Attention : `length="zz"` devient `title="zz"`, pour rester conforme.
2259
 *
2260
 * @filtre
2261
 * @see microformat2enclosure() Pour l'inverse
2262
 *
2263
 * @param string $e Tag RSS `<enclosure>`
2264
 * @return string Tag HTML `<a>` avec microformat.
2265
 **/
2266
function enclosure2microformat($e) {
2267
	if (!$url = filtrer_entites(extraire_attribut($e, 'url'))) {
0 ignored issues
show
Bug introduced by
It seems like extraire_attribut($e, 'url') targeting extraire_attribut() can also be of type array; however, filtrer_entites() does only seem to accept string, maybe add an additional type check?

This check looks at variables that 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...
2268
		$url = filtrer_entites(extraire_attribut($e, 'href'));
0 ignored issues
show
Bug introduced by
It seems like extraire_attribut($e, 'href') targeting extraire_attribut() can also be of type array; however, filtrer_entites() does only seem to accept string, maybe add an additional type check?

This check looks at variables that 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...
2269
	}
2270
	$type = extraire_attribut($e, 'type');
2271
	if (!$length = extraire_attribut($e, 'length')) {
2272
		# <media:content : longeur dans fileSize. On tente.
2273
		$length = extraire_attribut($e, 'fileSize');
2274
	}
2275
	$fichier = basename($url);
2276
2277
	return '<a rel="enclosure"'
2278
	. ($url ? ' href="' . spip_htmlspecialchars($url) . '"' : '')
2279
	. ($type ? ' type="' . spip_htmlspecialchars($type) . '"' : '')
0 ignored issues
show
Bug introduced by
It seems like $type defined by extraire_attribut($e, 'type') on line 2270 can also be of type array; 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...
2280
	. ($length ? ' title="' . spip_htmlspecialchars($length) . '"' : '')
0 ignored issues
show
Bug introduced by
It seems like $length can also be of type array; 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...
2281
	. '>' . $fichier . '</a>';
2282
}
2283
2284
/**
2285
 * Convertir les liens conformes aux microformats en médias pour flux RSS,
2286
 * par exemple pour les podcasts
2287
 *
2288
 * Passe un texte ayant des liens avec microformat
2289
 * `<a rel="enclosure" href="fichier" ...>fichier</a>`
2290
 * au format RSS `<enclosure url="fichier" ... />`.
2291
 *
2292
 * @filtre
2293
 * @see enclosure2microformat() Pour l'inverse
2294
 *
2295
 * @param string $tags Texte HTML ayant des tag `<a>` avec microformat
2296
 * @return string Tags RSS `<enclosure>`.
2297
 **/
2298
function microformat2enclosure($tags) {
2299
	$enclosures = array();
2300
	foreach (extraire_balises($tags, 'a') as $e) {
2301
		if (extraire_attribut($e, 'rel') == 'enclosure') {
2302
			$url = filtrer_entites(extraire_attribut($e, 'href'));
0 ignored issues
show
Bug introduced by
It seems like extraire_attribut($e, 'href') targeting extraire_attribut() can also be of type array; however, filtrer_entites() does only seem to accept string, maybe add an additional type check?

This check looks at variables that 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...
2303
			$type = extraire_attribut($e, 'type');
2304
			if (!$length = intval(extraire_attribut($e, 'title'))) {
2305
				$length = intval(extraire_attribut($e, 'length'));
2306
			} # vieux data
2307
			$fichier = basename($url);
0 ignored issues
show
Unused Code introduced by
$fichier is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
2308
			$enclosures[] = '<enclosure'
2309
				. ($url ? ' url="' . spip_htmlspecialchars($url) . '"' : '')
2310
				. ($type ? ' type="' . spip_htmlspecialchars($type) . '"' : '')
0 ignored issues
show
Bug introduced by
It seems like $type defined by extraire_attribut($e, 'type') on line 2303 can also be of type array; 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...
2311
				. ($length ? ' length="' . $length . '"' : '')
2312
				. ' />';
2313
		}
2314
	}
2315
2316
	return join("\n", $enclosures);
2317
}
2318
2319
2320
/**
2321
 * Créer les éléments ATOM `<dc:subject>` à partir des tags
2322
 *
2323
 * Convertit les liens avec attribut `rel="tag"`
2324
 * en balise `<dc:subject></dc:subject>` pour les flux RSS au format Atom.
2325
 *
2326
 * @filtre
2327
 *
2328
 * @param string $tags Texte
2329
 * @return string Tags RSS Atom `<dc:subject>`.
2330
 **/
2331
function tags2dcsubject($tags) {
2332
	$subjects = '';
2333
	foreach (extraire_balises($tags, 'a') as $e) {
2334
		if (extraire_attribut($e, rel) == 'tag') {
2335
			$subjects .= '<dc:subject>'
2336
				. texte_backend(textebrut($e))
2337
				. '</dc:subject>' . "\n";
2338
		}
2339
	}
2340
2341
	return $subjects;
2342
}
2343
2344
/**
2345
 * Retourne la premiere balise html du type demandé
2346
 *
2347
 * Retourne le contenu d'une balise jusqu'à la première fermeture rencontrée
2348
 * du même type.
2349
 * Si on a passe un tableau de textes, retourne un tableau de resultats.
2350
 *
2351
 * @example `[(#DESCRIPTIF|extraire_balise{img})]`
2352
 *
2353
 * @filtre
2354
 * @link https://www.spip.net/4289
2355
 * @see extraire_balises()
2356
 * @note
2357
 *     Attention : les résultats peuvent être incohérents sur des balises imbricables,
2358
 *     tel que demander à extraire `div` dans le texte `<div> un <div> mot </div> absent </div>`,
2359
 *     ce qui retournerait `<div> un <div> mot </div>` donc.
2360
 *
2361
 * @param string|array $texte
2362
 *     Texte(s) dont on souhaite extraire une balise html
2363
 * @param string $tag
2364
 *     Nom de la balise html à extraire
2365
 * @return void|string|array
2366
 *     - Code html de la balise, sinon rien
2367
 *     - Tableau de résultats, si tableau en entrée.
2368
 **/
2369 View Code Duplication
function extraire_balise($texte, $tag = 'a') {
0 ignored issues
show
Duplication introduced by
This function seems to be duplicated in your project.

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

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

Loading history...
2370
	if (is_array($texte)) {
2371
		array_walk(
2372
			$texte,
2373
			function(&$a, $key, $t){
2374
				$a = extraire_balise($a, $t);
2375
			},
2376
			$tag
2377
		);
2378
2379
		return $texte;
2380
	}
2381
2382
	if (preg_match(
2383
		",<$tag\b[^>]*(/>|>.*</$tag\b[^>]*>|>),UimsS",
2384
		$texte, $regs)) {
2385
		return $regs[0];
2386
	}
2387
}
2388
2389
/**
2390
 * Extrait toutes les balises html du type demandé
2391
 *
2392
 * Retourne dans un tableau le contenu de chaque balise jusqu'à la première
2393
 * fermeture rencontrée du même type.
2394
 * Si on a passe un tableau de textes, retourne un tableau de resultats.
2395
 *
2396
 * @example `[(#TEXTE|extraire_balises{img}|implode{" - "})]`
2397
 *
2398
 * @filtre
2399
 * @link https://www.spip.net/5618
2400
 * @see extraire_balise()
2401
 * @note
2402
 *     Attention : les résultats peuvent être incohérents sur des balises imbricables,
2403
 *     tel que demander à extraire `div` dans un texte.
2404
 *
2405
 * @param string|array $texte
2406
 *     Texte(s) dont on souhaite extraire une balise html
2407
 * @param string $tag
2408
 *     Nom de la balise html à extraire
2409
 * @return array
2410
 *     - Liste des codes html des occurrences de la balise, sinon tableau vide
2411
 *     - Tableau de résultats, si tableau en entrée.
2412
 **/
2413 View Code Duplication
function extraire_balises($texte, $tag = 'a') {
0 ignored issues
show
Duplication introduced by
This function seems to be duplicated in your project.

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

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

Loading history...
2414
	if (is_array($texte)) {
2415
		array_walk(
2416
			$texte,
2417
			function(&$a, $key, $t){
2418
				$a = extraire_balises($a, $t);
2419
			},
2420
			$tag
2421
		);
2422
2423
		return $texte;
2424
	}
2425
2426
	if (preg_match_all(
2427
		",<${tag}\b[^>]*(/>|>.*</${tag}\b[^>]*>|>),UimsS",
2428
		$texte, $regs, PREG_PATTERN_ORDER)) {
2429
		return $regs[0];
2430
	} else {
2431
		return array();
2432
	}
2433
}
2434
2435
/**
2436
 * Indique si le premier argument est contenu dans le second
2437
 *
2438
 * Cette fonction est proche de `in_array()` en PHP avec comme principale
2439
 * différence qu'elle ne crée pas d'erreur si le second argument n'est pas
2440
 * un tableau (dans ce cas elle tentera de le désérialiser, et sinon retournera
2441
 * la valeur par défaut transmise).
2442
 *
2443
 * @example `[(#VAL{deux}|in_any{#LISTE{un,deux,trois}}|oui) ... ]`
2444
 *
2445
 * @filtre
2446
 * @see filtre_find() Assez proche, avec les arguments valeur et tableau inversés.
2447
 *
2448
 * @param string $val
2449
 *     Valeur à chercher dans le tableau
2450
 * @param array|string $vals
2451
 *     Tableau des valeurs. S'il ce n'est pas un tableau qui est transmis,
2452
 *     la fonction tente de la désérialiser.
2453
 * @param string $def
2454
 *     Valeur par défaut retournée si `$vals` n'est pas un tableau.
2455
 * @return string
2456
 *     - ' ' si la valeur cherchée est dans le tableau
2457
 *     - '' si la valeur n'est pas dans le tableau
2458
 *     - `$def` si on n'a pas transmis de tableau
2459
 **/
2460
function in_any($val, $vals, $def = '') {
2461
	if (!is_array($vals) and $v = unserialize($vals)) {
2462
		$vals = $v;
2463
	}
2464
2465
	return (!is_array($vals) ? $def : (in_array($val, $vals) ? ' ' : ''));
2466
}
2467
2468
2469
/**
2470
 * Retourne le résultat d'une expression mathématique simple
2471
 *
2472
 * N'accepte que les *, + et - (à ameliorer si on l'utilise vraiment).
2473
 *
2474
 * @filtre
2475
 * @example
2476
 *      ```
2477
 *      valeur_numerique("3*2") retourne 6
2478
 *      ```
2479
 *
2480
 * @param string $expr
2481
 *     Expression mathématique `nombre operateur nombre` comme `3*2`
2482
 * @return int
2483
 *     Résultat du calcul
2484
 **/
2485
function valeur_numerique($expr) {
2486
	$a = 0;
2487
	if (preg_match(',^[0-9]+(\s*[+*-]\s*[0-9]+)*$,S', trim($expr))) {
2488
		eval("\$a = $expr;");
2489
	}
2490
2491
	return intval($a);
2492
}
2493
2494
/**
2495
 * Retourne un calcul de règle de trois
2496
 *
2497
 * @filtre
2498
 * @example
2499
 *     ```
2500
 *     [(#VAL{6}|regledetrois{4,3})] retourne 8
2501
 *     ```
2502
 *
2503
 * @param int $a
2504
 * @param int $b
2505
 * @param int $c
2506
 * @return int
0 ignored issues
show
Documentation introduced by
Should the return type not be 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...
2507
 *      Retourne `$a*$b/$c`
2508
 **/
2509
function regledetrois($a, $b, $c) {
2510
	return round($a * $b / $c);
2511
}
2512
2513
2514
/**
2515
 * Crée des tags HTML input hidden pour chaque paramètre et valeur d'une URL
2516
 *
2517
 * Fournit la suite de Input-Hidden correspondant aux paramètres de
2518
 * l'URL donnée en argument, compatible avec les types_urls
2519
 *
2520
 * @filtre
2521
 * @link https://www.spip.net/4286
2522
 * @see balise_ACTION_FORMULAIRE()
2523
 *     Également pour transmettre les actions à un formulaire
2524
 * @example
2525
 *     ```
2526
 *     [(#ENV{action}|form_hidden)] dans un formulaire
2527
 *     ```
2528
 *
2529
 * @param string $action URL
2530
 * @return string Suite de champs input hidden
2531
 **/
2532
function form_hidden($action) {
2533
2534
	$contexte = array();
2535
	include_spip('inc/urls');
2536
	if ($p = urls_decoder_url($action, '')
2537
		and reset($p)
2538
	) {
2539
		$fond = array_shift($p);
2540
		if ($fond != '404') {
2541
			$contexte = array_shift($p);
2542
			$contexte['page'] = $fond;
2543
			$action = preg_replace('/([?]' . preg_quote($fond) . '[^&=]*[0-9]+)(&|$)/', '?&', $action);
2544
		}
2545
	}
2546
	// defaire ce qu'a injecte urls_decoder_url : a revoir en modifiant la signature de urls_decoder_url
2547
	if (defined('_DEFINIR_CONTEXTE_TYPE') and _DEFINIR_CONTEXTE_TYPE) {
2548
		unset($contexte['type']);
2549
	}
2550
	if (defined('_DEFINIR_CONTEXTE_TYPE_PAGE') and _DEFINIR_CONTEXTE_TYPE_PAGE) {
2551
		unset($contexte['type-page']);
2552
	}
2553
2554
	// on va remplir un tableau de valeurs en prenant bien soin de ne pas
2555
	// ecraser les elements de la forme mots[]=1&mots[]=2
2556
	$values = array();
2557
2558
	// d'abord avec celles de l'url
2559
	if (false !== ($p = strpos($action, '?'))) {
2560
		foreach (preg_split('/&(amp;)?/S', substr($action, $p + 1)) as $c) {
2561
			$c = explode('=', $c, 2);
2562
			$var = array_shift($c);
2563
			$val = array_shift($c);
2564
			if ($var) {
2565
				$val = rawurldecode($val);
2566
				$var = rawurldecode($var); // decoder les [] eventuels
2567 View Code Duplication
				if (preg_match(',\[\]$,S', $var)) {
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...
2568
					$values[] = array($var, $val);
2569
				} else {
2570
					if (!isset($values[$var])) {
2571
						$values[$var] = array($var, $val);
2572
					}
2573
				}
2574
			}
2575
		}
2576
	}
2577
2578
	// ensuite avec celles du contexte, sans doublonner !
2579
	foreach ($contexte as $var => $val) {
2580 View Code Duplication
		if (preg_match(',\[\]$,S', $var)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
2581
			$values[] = array($var, $val);
2582
		} else {
2583
			if (!isset($values[$var])) {
2584
				$values[$var] = array($var, $val);
2585
			}
2586
		}
2587
	}
2588
2589
	// puis on rassemble le tout
2590
	$hidden = array();
2591
	foreach ($values as $value) {
2592
		list($var, $val) = $value;
2593
		$hidden[] = '<input name="'
2594
			. entites_html($var)
2595
			. '"'
2596
			. (is_null($val)
2597
				? ''
2598
				: ' value="' . entites_html($val) . '"'
2599
			)
2600
			. ' type="hidden"' . "\n/>";
2601
	}
2602
2603
	return join("", $hidden);
2604
}
2605
2606
/**
2607
 * Calcule les bornes d'une pagination
2608
 *
2609
 * @filtre
2610
 *
2611
 * @param int $courante
2612
 *     Page courante
2613
 * @param int $nombre
2614
 *     Nombre de pages
2615
 * @param int $max
2616
 *     Nombre d'éléments par page
2617
 * @return int[]
0 ignored issues
show
Documentation introduced by
Should the return type not be array<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...
2618
 *     Liste (première page, dernière page).
2619
 **/
2620
function filtre_bornes_pagination_dist($courante, $nombre, $max = 10) {
2621
	if ($max <= 0 or $max >= $nombre) {
2622
		return array(1, $nombre);
2623
	}
2624
2625
	$premiere = max(1, $courante - floor(($max - 1) / 2));
2626
	$derniere = min($nombre, $premiere + $max - 2);
2627
	$premiere = $derniere == $nombre ? $derniere - $max + 1 : $premiere;
2628
2629
	return array($premiere, $derniere);
2630
}
2631
2632
2633
/**
2634
 * Retourne la première valeur d'un tableau
2635
 *
2636
 * Plus précisément déplace le pointeur du tableau sur la première valeur et la retourne.
2637
 *
2638
 * @example `[(#LISTE{un,deux,trois}|reset)]` retourne 'un'
2639
 *
2640
 * @filtre
2641
 * @link http://php.net/manual/fr/function.reset.php
2642
 * @see filtre_end()
2643
 *
2644
 * @param array $array
2645
 * @return mixed|null|false
2646
 *    - null si $array n'est pas un tableau,
2647
 *    - false si le tableau est vide
2648
 *    - la première valeur du tableau sinon.
2649
 **/
2650
function filtre_reset($array) {
2651
	return !is_array($array) ? null : reset($array);
2652
}
2653
2654
/**
2655
 * Retourne la dernière valeur d'un tableau
2656
 *
2657
 * Plus précisément déplace le pointeur du tableau sur la dernière valeur et la retourne.
2658
 *
2659
 * @example `[(#LISTE{un,deux,trois}|end)]` retourne 'trois'
2660
 *
2661
 * @filtre
2662
 * @link http://php.net/manual/fr/function.end.php
2663
 * @see filtre_reset()
2664
 *
2665
 * @param array $array
2666
 * @return mixed|null|false
2667
 *    - null si $array n'est pas un tableau,
2668
 *    - false si le tableau est vide
2669
 *    - la dernière valeur du tableau sinon.
2670
 **/
2671
function filtre_end($array) {
2672
	return !is_array($array) ? null : end($array);
2673
}
2674
2675
/**
2676
 * Empile une valeur à la fin d'un tableau
2677
 *
2678
 * @example `[(#LISTE{un,deux,trois}|push{quatre}|print)]`
2679
 *
2680
 * @filtre
2681
 * @link https://www.spip.net/4571
2682
 * @link http://php.net/manual/fr/function.array-push.php
2683
 *
2684
 * @param array $array
2685
 * @param mixed $val
2686
 * @return array|string
2687
 *     - '' si $array n'est pas un tableau ou si echec.
2688
 *     - le tableau complété de la valeur sinon.
2689
 *
2690
 **/
2691
function filtre_push($array, $val) {
2692
	if (!is_array($array) or !array_push($array, $val)) {
2693
		return '';
2694
	}
2695
2696
	return $array;
2697
}
2698
2699
/**
2700
 * Indique si une valeur est contenue dans un tableau
2701
 *
2702
 * @example `[(#LISTE{un,deux,trois}|find{quatre}|oui) ... ]`
2703
 *
2704
 * @filtre
2705
 * @link https://www.spip.net/4575
2706
 * @see in_any() Assez proche, avec les paramètres tableau et valeur inversés.
2707
 *
2708
 * @param array $array
2709
 * @param mixed $val
2710
 * @return bool
2711
 *     - `false` si `$array` n'est pas un tableau
2712
 *     - `true` si la valeur existe dans le tableau, `false` sinon.
2713
 **/
2714
function filtre_find($array, $val) {
2715
	return (is_array($array) and in_array($val, $array));
2716
}
2717
2718
2719
/**
2720
 * Passer les url relatives à la css d'origine en url absolues
2721
 *
2722
 * @uses suivre_lien()
2723
 *
2724
 * @param string $contenu
2725
 *     Contenu du fichier CSS
2726
 * @param string $source
2727
 *     Chemin du fichier CSS
2728
 * @return string
2729
 *     Contenu avec urls en absolus
2730
 **/
2731
function urls_absolues_css($contenu, $source) {
2732
	$path = suivre_lien(url_absolue($source), './');
2733
2734
	return preg_replace_callback(
2735
		",url\s*\(\s*['\"]?([^'\"/#\s][^:]*)['\"]?\s*\),Uims",
2736
		function($x) use ($path) {
2737
			return "url('" . suivre_lien($path, $x[1]) . "')";
2738
		},
2739
		$contenu
2740
	);
2741
}
2742
2743
2744
/**
2745
 * Inverse le code CSS (left <--> right) d'une feuille de style CSS
2746
 *
2747
 * Récupère le chemin d'une CSS existante et :
2748
 *
2749
 * 1. regarde si une CSS inversée droite-gauche existe dans le meme répertoire
2750
 * 2. sinon la crée (ou la recrée) dans `_DIR_VAR/cache_css/`
2751
 *
2752
 * Si on lui donne à manger une feuille nommée `*_rtl.css` il va faire l'inverse.
2753
 *
2754
 * @filtre
2755
 * @example
2756
 *     ```
2757
 *     [<link rel="stylesheet" href="(#CHEMIN{css/perso.css}|direction_css)" type="text/css" />]
2758
 *     ```
2759
 * @param string $css
2760
 *     Chemin vers le fichier CSS
2761
 * @param string $voulue
2762
 *     Permet de forcer le sens voulu (en indiquant `ltr`, `rtl` ou un
2763
 *     code de langue). En absence, prend le sens de la langue en cours.
2764
 *
2765
 * @return string
2766
 *     Chemin du fichier CSS inversé
2767
 **/
2768
function direction_css($css, $voulue = '') {
2769
	if (!preg_match(',(_rtl)?\.css$,i', $css, $r)) {
2770
		return $css;
2771
	}
2772
2773
	// si on a precise le sens voulu en argument, le prendre en compte
2774
	if ($voulue = strtolower($voulue)) {
2775
		if ($voulue != 'rtl' and $voulue != 'ltr') {
2776
			$voulue = lang_dir($voulue);
2777
		}
2778
	} else {
2779
		$voulue = lang_dir();
2780
	}
2781
2782
	$r = count($r) > 1;
2783
	$right = $r ? 'left' : 'right'; // 'right' de la css lue en entree
0 ignored issues
show
Unused Code introduced by
$right is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
2784
	$dir = $r ? 'rtl' : 'ltr';
2785
	$ndir = $r ? 'ltr' : 'rtl';
2786
2787
	if ($voulue == $dir) {
2788
		return $css;
2789
	}
2790
2791
	if (
2792
		// url absolue
2793
		preg_match(",^https?:,i", $css)
2794
		// ou qui contient un ?
2795
		or (($p = strpos($css, '?')) !== false)
2796
	) {
2797
		$distant = true;
2798
		$cssf = parse_url($css);
2799
		$cssf = $cssf['path'] . ($cssf['query'] ? "?" . $cssf['query'] : "");
2800
		$cssf = preg_replace(',[?:&=],', "_", $cssf);
2801
	} else {
2802
		$distant = false;
2803
		$cssf = $css;
2804
		// 1. regarder d'abord si un fichier avec la bonne direction n'est pas aussi
2805
		//propose (rien a faire dans ce cas)
2806
		$f = preg_replace(',(_rtl)?\.css$,i', '_' . $ndir . '.css', $css);
2807
		if (@file_exists($f)) {
2808
			return $f;
2809
		}
2810
	}
2811
2812
	// 2.
2813
	$dir_var = sous_repertoire(_DIR_VAR, 'cache-css');
2814
	$f = $dir_var
2815
		. preg_replace(',.*/(.*?)(_rtl)?\.css,', '\1', $cssf)
2816
		. '.' . substr(md5($cssf), 0, 4) . '_' . $ndir . '.css';
2817
2818
	// la css peut etre distante (url absolue !)
2819
	if ($distant) {
2820
		include_spip('inc/distant');
2821
		$res = recuperer_url($css);
2822
		if (!$res or !$contenu = $res['page']) {
2823
			return $css;
2824
		}
2825
	} else {
2826 View Code Duplication
		if ((@filemtime($f) > @filemtime($css))
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...
2827
			and (_VAR_MODE != 'recalcul')
2828
		) {
2829
			return $f;
2830
		}
2831
		if (!lire_fichier($css, $contenu)) {
2832
			return $css;
2833
		}
2834
	}
2835
2836
2837
	// Inverser la direction gauche-droite en utilisant CSSTidy qui gere aussi les shorthands
2838
	include_spip("lib/csstidy/class.csstidy");
2839
	$parser = new csstidy();
2840
	$parser->set_cfg('optimise_shorthands', 0);
2841
	$parser->set_cfg('reverse_left_and_right', true);
2842
	$parser->parse($contenu);
2843
2844
	$contenu = $parser->print->plain();
2845
2846
2847
	// reperer les @import auxquels il faut propager le direction_css
2848
	preg_match_all(",\@import\s*url\s*\(\s*['\"]?([^'\"/][^:]*)['\"]?\s*\),Uims", $contenu, $regs);
2849
	$src = array();
2850
	$src_direction_css = array();
2851
	$src_faux_abs = array();
2852
	$d = dirname($css);
2853
	foreach ($regs[1] as $k => $import_css) {
2854
		$css_direction = direction_css("$d/$import_css", $voulue);
2855
		// si la css_direction est dans le meme path que la css d'origine, on tronque le path, elle sera passee en absolue
2856
		if (substr($css_direction, 0, strlen($d) + 1) == "$d/") {
2857
			$css_direction = substr($css_direction, strlen($d) + 1);
2858
		} // si la css_direction commence par $dir_var on la fait passer pour une absolue
2859
		elseif (substr($css_direction, 0, strlen($dir_var)) == $dir_var) {
2860
			$css_direction = substr($css_direction, strlen($dir_var));
2861
			$src_faux_abs["/@@@@@@/" . $css_direction] = $css_direction;
2862
			$css_direction = "/@@@@@@/" . $css_direction;
2863
		}
2864
		$src[] = $regs[0][$k];
2865
		$src_direction_css[] = str_replace($import_css, $css_direction, $regs[0][$k]);
2866
	}
2867
	$contenu = str_replace($src, $src_direction_css, $contenu);
2868
2869
	$contenu = urls_absolues_css($contenu, $css);
2870
2871
	// virer les fausses url absolues que l'on a mis dans les import
2872
	if (count($src_faux_abs)) {
2873
		$contenu = str_replace(array_keys($src_faux_abs), $src_faux_abs, $contenu);
2874
	}
2875
2876
	if (!ecrire_fichier($f, $contenu)) {
2877
		return $css;
2878
	}
2879
2880
	return $f;
2881
}
2882
2883
2884
/**
2885
 * Transforme les urls relatives d'un fichier CSS en absolues
2886
 *
2887
 * Récupère le chemin d'une css existante et crée (ou recrée) dans `_DIR_VAR/cache_css/`
2888
 * une css dont les url relatives sont passées en url absolues
2889
 *
2890
 * Le calcul n'est pas refait si le fichier cache existe déjà et que
2891
 * la source n'a pas été modifiée depuis.
2892
 *
2893
 * @uses recuperer_page() si l'URL source n'est pas sur le même site
2894
 * @uses urls_absolues_css()
2895
 *
2896
 * @param string $css
2897
 *     Chemin ou URL du fichier CSS source
2898
 * @return string
2899
 *     - Chemin du fichier CSS transformé (si source lisible et mise en cache réussie)
2900
 *     - Chemin ou URL du fichier CSS source sinon.
2901
 **/
2902
function url_absolue_css($css) {
2903
	if (!preg_match(',\.css$,i', $css, $r)) {
2904
		return $css;
2905
	}
2906
2907
	$url_absolue_css = url_absolue($css);
2908
2909
	$f = basename($css, '.css');
2910
	$f = sous_repertoire(_DIR_VAR, 'cache-css')
2911
		. preg_replace(",(.*?)(_rtl|_ltr)?$,", "\\1-urlabs-" . substr(md5("$css-urlabs"), 0, 4) . "\\2", $f)
2912
		. '.css';
2913
2914 View Code Duplication
	if ((@filemtime($f) > @filemtime($css)) and (_VAR_MODE != 'recalcul')) {
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...
2915
		return $f;
2916
	}
2917
2918
	if ($url_absolue_css == $css) {
2919
		if (strncmp($GLOBALS['meta']['adresse_site'], $css, $l = strlen($GLOBALS['meta']['adresse_site'])) != 0
2920
			or !lire_fichier(_DIR_RACINE . substr($css, $l), $contenu)
2921
		) {
2922
			include_spip('inc/distant');
2923
			if (!$contenu = recuperer_page($css)) {
0 ignored issues
show
Deprecated Code introduced by
The function recuperer_page() has been deprecated.

This function has been deprecated.

Loading history...
2924
				return $css;
2925
			}
2926
		}
2927
	} elseif (!lire_fichier($css, $contenu)) {
2928
		return $css;
2929
	}
2930
2931
	// passer les url relatives a la css d'origine en url absolues
2932
	$contenu = urls_absolues_css($contenu, $css);
0 ignored issues
show
Bug introduced by
It seems like $contenu can also be of type boolean; however, urls_absolues_css() 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...
2933
2934
	// ecrire la css
2935
	if (!ecrire_fichier($f, $contenu)) {
2936
		return $css;
2937
	}
2938
2939
	return $f;
2940
}
2941
2942
2943
/**
2944
 * Récupère la valeur d'une clé donnée
2945
 * dans un tableau (ou un objet).
2946
 *
2947
 * @filtre
2948
 * @link https://www.spip.net/4572
2949
 * @example
2950
 *     ```
2951
 *     [(#VALEUR|table_valeur{cle/sous/element})]
2952
 *     ```
2953
 *
2954
 * @param mixed $table
2955
 *     Tableau ou objet PHP
2956
 *     (ou chaîne serialisée de tableau, ce qui permet d'enchaîner le filtre)
2957
 * @param string $cle
2958
 *     Clé du tableau (ou paramètre public de l'objet)
2959
 *     Cette clé peut contenir des caractères / pour sélectionner
2960
 *     des sous éléments dans le tableau, tel que `sous/element/ici`
2961
 *     pour obtenir la valeur de `$tableau['sous']['element']['ici']`
2962
 * @param mixed $defaut
2963
 *     Valeur par defaut retournée si la clé demandée n'existe pas
2964
 * @param bool  $conserver_null
2965
 *     Permet de forcer la fonction à renvoyer la valeur null d'un index
2966
 *     et non pas $defaut comme cela est fait naturellement par la fonction
2967
 *     isset. On utilise alors array_key_exists() à la place de isset().
2968
 * 
2969
 * @return mixed
2970
 *     Valeur trouvée ou valeur par défaut.
2971
 **/
2972
function table_valeur($table, $cle, $defaut = '', $conserver_null = false) {
2973
	foreach (explode('/', $cle) as $k) {
2974
2975
		$table = is_string($table) ? @unserialize($table) : $table;
2976
2977
		if (is_object($table)) {
2978
			$table = (($k !== "") and isset($table->$k)) ? $table->$k : $defaut;
2979
		} elseif (is_array($table)) {
2980
			if ($conserver_null) {
2981
				$table = array_key_exists($k, $table) ? $table[$k] : $defaut;
2982
			} else {
2983
				$table = isset($table[$k]) ? $table[$k] : $defaut;
2984
			}
2985
		} else {
2986
			$table = $defaut;
2987
		}
2988
	}
2989
2990
	return $table;
2991
}
2992
2993
/**
2994
 * Retrouve un motif dans un texte à partir d'une expression régulière
2995
 *
2996
 * S'appuie sur la fonction `preg_match()` en PHP
2997
 *
2998
 * @example
2999
 *    - `[(#TITRE|match{toto})]`
3000
 *    - `[(#TEXTE|match{^ceci$,Uims})]`
3001
 *    - `[(#TEXTE|match{truc(...)$, UimsS, 1})]` Capture de la parenthèse indiquée
3002
 *    - `[(#TEXTE|match{truc(...)$, 1})]` Équivalent, sans indiquer les modificateurs
3003
 *
3004
 * @filtre
3005
 * @link https://www.spip.net/4299
3006
 * @link http://php.net/manual/fr/function.preg-match.php Pour des infos sur `preg_match()`
3007
 *
3008
 * @param string $texte
3009
 *     Texte dans lequel chercher
3010
 * @param string|int $expression
3011
 *     Expression régulière de recherche, sans le délimiteur
3012
 * @param string $modif
3013
 *     - string : Modificateurs de l'expression régulière
3014
 *     - int : Numéro de parenthèse capturante
3015
 * @param int $capte
3016
 *     Numéro de parenthèse capturante
3017
 * @return bool|string
3018
 *     - false : l'expression n'a pas été trouvée
3019
 *     - true : expression trouvée, mais pas la parenthèse capturante
3020
 *     - string : expression trouvée.
3021
 **/
3022
function filtre_match_dist($texte, $expression, $modif = "UimsS", $capte = 0) {
3023
	if (intval($modif) and $capte == 0) {
3024
		$capte = $modif;
3025
		$modif = "UimsS";
3026
	}
3027
	$expression = str_replace("\/", "/", $expression);
3028
	$expression = str_replace("/", "\/", $expression);
3029
3030
	if (preg_match('/' . $expression . '/' . $modif, $texte, $r)) {
3031
		if (isset($r[$capte])) {
3032
			return $r[$capte];
3033
		} else {
3034
			return true;
3035
		}
3036
	}
3037
3038
	return false;
3039
}
3040
3041
3042
/**
3043
 * Remplacement de texte à base d'expression régulière
3044
 *
3045
 * @filtre
3046
 * @link https://www.spip.net/4309
3047
 * @see match()
3048
 * @example
3049
 *     ```
3050
 *     [(#TEXTE|replace{^ceci$,cela,UimsS})]
3051
 *     ```
3052
 *
3053
 * @param string $texte
3054
 *     Texte
3055
 * @param string $expression
3056
 *     Expression régulière
3057
 * @param string $replace
3058
 *     Texte de substitution des éléments trouvés
3059
 * @param string $modif
3060
 *     Modificateurs pour l'expression régulière.
3061
 * @return string
3062
 *     Texte
3063
 **/
3064
function replace($texte, $expression, $replace = '', $modif = "UimsS") {
3065
	$expression = str_replace("\/", "/", $expression);
3066
	$expression = str_replace("/", "\/", $expression);
3067
3068
	return preg_replace('/' . $expression . '/' . $modif, $replace, $texte);
3069
}
3070
3071
3072
/**
3073
 * Cherche les documents numerotés dans un texte traite par `propre()`
3074
 *
3075
 * Affecte la liste des doublons['documents']
3076
 *
3077
 * @param array $doublons
3078
 *     Liste des doublons
3079
 * @param string $letexte
3080
 *     Le texte
3081
 * @return string
3082
 *     Le texte
3083
 **/
3084
function traiter_doublons_documents(&$doublons, $letexte) {
3085
3086
	// Verifier dans le texte & les notes (pas beau, helas)
3087
	$t = $letexte . $GLOBALS['les_notes'];
3088
3089
	if (strstr($t, 'spip_document_') // evite le preg_match_all si inutile
3090
		and preg_match_all(
3091
			',<[^>]+\sclass=["\']spip_document_([0-9]+)[\s"\'],imsS',
3092
			$t, $matches, PREG_PATTERN_ORDER)
3093
	) {
3094
		if (!isset($doublons['documents'])) {
3095
			$doublons['documents'] = "";
3096
		}
3097
		$doublons['documents'] .= "," . join(',', $matches[1]);
3098
	}
3099
3100
	return $letexte;
3101
}
3102
3103
/**
3104
 * Filtre vide qui ne renvoie rien
3105
 *
3106
 * @example
3107
 *     `[(#CALCUL|vide)]` n'affichera pas le résultat du calcul
3108
 * @filtre
3109
 *
3110
 * @param mixed $texte
3111
 * @return string Chaîne vide
3112
 **/
3113
function vide($texte) {
0 ignored issues
show
Unused Code introduced by
The parameter $texte is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
3114
	return "";
3115
}
3116
3117
//
3118
// Filtres pour le modele/emb (embed document)
3119
//
3120
3121
/**
3122
 * Écrit des balises HTML `<param...>` à partir d'un tableau de données tel que `#ENV`
3123
 *
3124
 * Permet d'écrire les balises `<param>` à indiquer dans un `<object>`
3125
 * en prenant toutes les valeurs du tableau transmis.
3126
 *
3127
 * Certaines clés spécifiques à SPIP et aux modèles embed sont omises :
3128
 * id, lang, id_document, date, date_redac, align, fond, recurs, emb, dir_racine
3129
 *
3130
 * @example `[(#ENV*|env_to_params)]`
3131
 *
3132
 * @filtre
3133
 * @link https://www.spip.net/4005
3134
 *
3135
 * @param array|string $env
3136
 *      Tableau cle => valeur des paramètres à écrire, ou chaine sérialisée de ce tableau
3137
 * @param array $ignore_params
3138
 *      Permet de compléter les clés ignorées du tableau.
3139
 * @return string
3140
 *      Code HTML résultant
3141
 **/
3142 View Code Duplication
function env_to_params($env, $ignore_params = array()) {
0 ignored issues
show
Duplication introduced by
This function seems to be duplicated in your project.

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

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

Loading history...
3143
	$ignore_params = array_merge(
3144
		array('id', 'lang', 'id_document', 'date', 'date_redac', 'align', 'fond', '', 'recurs', 'emb', 'dir_racine'),
3145
		$ignore_params
3146
	);
3147
	if (!is_array($env)) {
3148
		$env = unserialize($env);
3149
	}
3150
	$texte = "";
3151
	if ($env) {
3152
		foreach ($env as $i => $j) {
3153
			if (is_string($j) and !in_array($i, $ignore_params)) {
3154
				$texte .= "<param name='" . attribut_html($i) . "'\n\tvalue='" . attribut_html($j) . "' />";
3155
			}
3156
		}
3157
	}
3158
3159
	return $texte;
3160
}
3161
3162
/**
3163
 * Écrit des attributs HTML à partir d'un tableau de données tel que `#ENV`
3164
 *
3165
 * Permet d'écrire des attributs d'une balise HTML en utilisant les données du tableau transmis.
3166
 * Chaque clé deviendra le nom de l'attribut (et la valeur, sa valeur)
3167
 *
3168
 * Certaines clés spécifiques à SPIP et aux modèles embed sont omises :
3169
 * id, lang, id_document, date, date_redac, align, fond, recurs, emb, dir_racine
3170
 *
3171
 * @example `<embed src='#URL_DOCUMENT' [(#ENV*|env_to_attributs)] width='#GET{largeur}' height='#GET{hauteur}'></embed>`
3172
 * @filtre
3173
 *
3174
 * @param array|string $env
3175
 *      Tableau cle => valeur des attributs à écrire, ou chaine sérialisée de ce tableau
3176
 * @param array $ignore_params
3177
 *      Permet de compléter les clés ignorées du tableau.
3178
 * @return string
3179
 *      Code HTML résultant
3180
 **/
3181 View Code Duplication
function env_to_attributs($env, $ignore_params = array()) {
0 ignored issues
show
Duplication introduced by
This function seems to be duplicated in your project.

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

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

Loading history...
3182
	$ignore_params = array_merge(
3183
		array('id', 'lang', 'id_document', 'date', 'date_redac', 'align', 'fond', '', 'recurs', 'emb', 'dir_racine'),
3184
		$ignore_params
3185
	);
3186
	if (!is_array($env)) {
3187
		$env = unserialize($env);
3188
	}
3189
	$texte = "";
3190
	if ($env) {
3191
		foreach ($env as $i => $j) {
3192
			if (is_string($j) and !in_array($i, $ignore_params)) {
3193
				$texte .= attribut_html($i) . "='" . attribut_html($j) . "' ";
3194
			}
3195
		}
3196
	}
3197
3198
	return $texte;
3199
}
3200
3201
3202
/**
3203
 * Concatène des chaînes
3204
 *
3205
 * @filtre
3206
 * @link https://www.spip.net/4150
3207
 * @example
3208
 *     ```
3209
 *     #TEXTE|concat{texte1,texte2,...}
3210
 *     ```
3211
 *
3212
 * @return string Chaînes concaténés
3213
 **/
3214
function concat() {
3215
	$args = func_get_args();
3216
3217
	return join('', $args);
3218
}
3219
3220
3221
/**
3222
 * Retourne le contenu d'un ou plusieurs fichiers
3223
 *
3224
 * Les chemins sont cherchés dans le path de SPIP
3225
 *
3226
 * @see balise_INCLURE_dist() La balise `#INCLURE` peut appeler cette fonction
3227
 *
3228
 * @param array|string $files
3229
 *     - array : Liste de fichiers
3230
 *     - string : fichier ou fichiers séparés par `|`
3231
 * @param bool $script
3232
 *     - si true, considère que c'est un fichier js à chercher `javascript/`
3233
 * @return string
3234
 *     Contenu du ou des fichiers, concaténé
3235
 **/
3236
function charge_scripts($files, $script = true) {
3237
	$flux = "";
3238
	foreach (is_array($files) ? $files : explode("|", $files) as $file) {
3239
		if (!is_string($file)) {
3240
			continue;
3241
		}
3242
		if ($script) {
3243
			$file = preg_match(",^\w+$,", $file) ? "javascript/$file.js" : '';
3244
		}
3245
		if ($file) {
3246
			$path = find_in_path($file);
3247
			if ($path) {
3248
				$flux .= spip_file_get_contents($path);
0 ignored issues
show
Bug introduced by
It seems like $path defined by find_in_path($file) on line 3246 can also be of type boolean; however, spip_file_get_contents() 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...
3249
			}
3250
		}
3251
	}
3252
3253
	return $flux;
3254
}
3255
3256
3257
/**
3258
 * Produit une balise img avec un champ alt d'office si vide
3259
 *
3260
 * Attention le htmlentities et la traduction doivent être appliqués avant.
3261
 *
3262
 * @param string $img
3263
 * @param string $alt
3264
 * @param string $atts
3265
 * @param string $title
3266
 * @param array $options
3267
 *   chemin_image : utiliser chemin_image sur $img fourni, ou non (oui par dafaut)
3268
 *   utiliser_suffixe_size : utiliser ou non le suffixe de taille dans le nom de fichier de l'image
3269
 *   sous forme -xx.png (pour les icones essentiellement) (oui par defaut)
3270
 *   variante_svg_si_possible: utiliser l'image -xx.svg au lieu de -32.png par exemple (si la variante svg est disponible)
3271
 * @return string
3272
 */
3273
function http_img_pack($img, $alt, $atts = '', $title = '', $options = array()) {
3274
3275
	$img_file = $img;
3276
	if ($p = strpos($img_file, '?')) {
3277
		$img_file = substr($img_file,0, $p);
3278
	}
3279
	if (!isset($options['chemin_image']) or $options['chemin_image'] == true) {
3280
		$img_file = chemin_image($img);
3281
	}
3282
	else {
3283
		if (!isset($options['variante_svg_si_possible']) or $options['variante_svg_si_possible'] == true){
3284
			// on peut fournir une icone generique -xx.svg qui fera le job dans toutes les tailles, et qui est prioritaire sur le png
3285
			// si il y a un .svg a la bonne taille (-16.svg) a cote, on l'utilise en remplacement du -16.png
3286
			if (preg_match(',-(\d+)[.](png|gif|svg)$,', $img_file, $m)
3287
			  and $variante_svg_generique = substr($img_file, 0, -strlen($m[0])) . "-xx.svg"
3288
			  and file_exists($variante_svg_generique)) {
3289
				if ($variante_svg_size = substr($variante_svg_generique,0,-6) . $m[1] . ".svg" and file_exists($variante_svg_size)) {
3290
					$img_file = $variante_svg_size;
3291
				}
3292
				else {
3293
					$img_file = $variante_svg_generique;
3294
				}
3295
			}
3296
		}
3297
	}
3298
	if (stripos($atts, 'width') === false) {
3299
		// utiliser directement l'info de taille presente dans le nom
3300
		if ((!isset($options['utiliser_suffixe_size'])
3301
				or $options['utiliser_suffixe_size'] == true
3302
			  or strpos($img_file, '-xx.svg') !== false)
3303
			and (preg_match(',-([0-9]+)[.](png|gif|svg)$,', $img, $regs)
3304
					 or preg_match(',\?([0-9]+)px$,', $img, $regs))
3305
		) {
3306
			$largeur = $hauteur = intval($regs[1]);
3307
		} else {
3308
			$taille = taille_image($img_file);
3309
			list($hauteur, $largeur) = $taille;
3310
			if (!$hauteur or !$largeur) {
3311
				return "";
3312
			}
3313
		}
3314
		$atts .= " width='" . $largeur . "' height='" . $hauteur . "'";
3315
	}
3316
3317
	if (file_exists($img_file)) {
3318
		$img_file = timestamp($img_file);
3319
	}
3320
	if ($alt === false) {
3321
		$alt = '';
3322
	}
3323
	elseif($alt or $alt==='') {
3324
		$alt = " alt='".attribut_html($alt)."'";
3325
	}
3326
	else {
3327
		$alt = " alt='".attribut_html($title)."'";
3328
	}
3329
	return "<img src='$img_file'$alt"
3330
	. ($title ? ' title="' . attribut_html($title) . '"' : '')
3331
	. " " . ltrim($atts)
3332
	. " />";
3333
}
3334
3335
/**
3336
 * Générer une directive `style='background:url()'` à partir d'un fichier image
3337
 *
3338
 * @param string $img
3339
 * @param string $att
3340
 * @param string $size
0 ignored issues
show
Documentation introduced by
Should the type for parameter $size 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...
3341
 * @return string
3342
 */
3343
function http_style_background($img, $att = '', $size=null) {
3344
	if ($size and is_numeric($size)){
3345
		$size = trim($size) . "px";
3346
	}
3347
	return " style='background" .
3348
		($att ? "" : "-image") . ": url(\"" . chemin_image($img) . "\")" . ($att ? (' ' . $att) : '') . ";"
3349
		. ($size ? "background-size:{$size};" : '')
3350
		. "'";
3351
}
3352
3353
/**
3354
 * Générer une balise HTML `img` à partir d'un nom de fichier
3355
 *
3356
 * @uses http_img_pack()
3357
 *
3358
 * @param string $img
3359
 * @param string $alt
3360
 * @param string $class
3361
 * @param string $width
0 ignored issues
show
Documentation introduced by
Should the type for parameter $width 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...
3362
 * @return string
3363
 *     Code HTML de la balise IMG
3364
 */
3365
function filtre_balise_img_dist($img, $alt = "", $class = "", $width=null) {
3366
	$atts = $class ? " class='" . attribut_html($class) . "'" : '';
3367
	// ecriture courte : on donne le width en 2e arg
3368
	if (empty($width) and is_numeric($alt)) {
3369
		$width = $alt;
3370
		$alt = '';
3371
	}
3372
	if ($width) {
3373
		$atts .= " width='{$width}'";
3374
	}
3375
	return http_img_pack($img, $alt, $atts, '',
3376
		array('chemin_image' => false, 'utiliser_suffixe_size' => false));
3377
}
3378
3379
3380
/**
3381
 * Inserer un svg inline
3382
 * http://www.accede-web.com/notices/html-css-javascript/6-images-icones/6-2-svg-images-vectorielles/
3383
 *
3384
 * pour l'inserer avec une balise <img>, utiliser le filtre |balise_img
3385
 *
3386
 * @param string $img
3387
 * @param string $alt
3388
 * @param string $class
3389
 * @return string
3390
 */
3391
function filtre_balise_svg_dist($img, $alt = "", $class = "") {
3392
	$img_file = $img;
3393
	if ($p = strpos($img_file, '?')) {
3394
		$img_file = substr($img_file,0, $p);
3395
	}
3396
3397
	if (!$img_file or !$svg = file_get_contents($img_file)) {
3398
		return '';
3399
	}
3400
3401
	if (!preg_match(",<svg\b[^>]*>,UimsS", $svg, $match)) {
3402
		return '';
3403
	}
3404
	$balise_svg = $match[0];
3405
	$balise_svg_source = $balise_svg;
3406
3407
	// entete XML à supprimer
3408
	$svg = preg_replace(',^\s*<\?xml[^>]*\?' . '>,', '', $svg);
3409
3410
	// IE est toujours mon ami
3411
	$balise_svg = inserer_attribut($balise_svg, 'focusable', 'false');
3412
	if ($class) {
3413
		$balise_svg = inserer_attribut($balise_svg, 'class', $class);
3414
	}
3415
	if ($alt){
3416
		$balise_svg = inserer_attribut($balise_svg, 'role', 'img');
3417
		$id = "img-svg-title-" . substr(md5("$img_file:$svg:$alt"),0,4);
3418
		$balise_svg = inserer_attribut($balise_svg, 'aria-labelledby', $id);
3419
		$title = "<title id=\"$id\">" . entites_html($alt)."</title>\n";
3420
		$balise_svg .= $title;
3421
	}
3422
	else {
3423
		$balise_svg = inserer_attribut($balise_svg, 'aria-hidden', 'true');
3424
	}
3425
	$svg = str_replace($balise_svg_source, $balise_svg, $svg);
3426
3427
	return $svg;
3428
}
3429
3430
3431
3432
/**
3433
 * Affiche chaque valeur d'un tableau associatif en utilisant un modèle
3434
 *
3435
 * @example
3436
 *     - `[(#ENV*|unserialize|foreach)]`
3437
 *     - `[(#ARRAY{a,un,b,deux}|foreach)]`
3438
 *
3439
 * @filtre
3440
 * @link https://www.spip.net/4248
3441
 *
3442
 * @param array $tableau
3443
 *     Tableau de données à afficher
3444
 * @param string $modele
3445
 *     Nom du modèle à utiliser
3446
 * @return string
3447
 *     Code HTML résultant
3448
 **/
3449
function filtre_foreach_dist($tableau, $modele = 'foreach') {
3450
	$texte = '';
3451
	if (is_array($tableau)) {
3452
		foreach ($tableau as $k => $v) {
3453
			$res = recuperer_fond('modeles/' . $modele,
3454
				array_merge(array('cle' => $k), (is_array($v) ? $v : array('valeur' => $v)))
3455
			);
3456
			$texte .= $res;
3457
		}
3458
	}
3459
3460
	return $texte;
3461
}
3462
3463
3464
/**
3465
 * Obtient des informations sur les plugins actifs
3466
 *
3467
 * @filtre
3468
 * @uses liste_plugin_actifs() pour connaître les informations affichables
3469
 *
3470
 * @param string $plugin
3471
 *     Préfixe du plugin ou chaîne vide
3472
 * @param string $type_info
3473
 *     Type d'info demandée
3474
 * @param bool $reload
3475
 *     true (à éviter) pour forcer le recalcul du cache des informations des plugins.
3476
 * @return array|string|bool
3477
 *
3478
 *     - Liste sérialisée des préfixes de plugins actifs (si $plugin = '')
3479
 *     - Suivant $type_info, avec $plugin un préfixe
3480
 *         - est_actif : renvoie true s'il est actif, false sinon
3481
 *         - x : retourne l'information x du plugin si présente (et plugin actif)
3482
 *         - tout : retourne toutes les informations du plugin actif
3483
 **/
3484
function filtre_info_plugin_dist($plugin, $type_info, $reload = false) {
3485
	include_spip('inc/plugin');
3486
	$plugin = strtoupper($plugin);
3487
	$plugins_actifs = liste_plugin_actifs();
3488
3489
	if (!$plugin) {
3490
		return serialize(array_keys($plugins_actifs));
3491
	} elseif (empty($plugins_actifs[$plugin]) and !$reload) {
3492
		return '';
3493
	} elseif (($type_info == 'est_actif') and !$reload) {
3494
		return $plugins_actifs[$plugin] ? 1 : 0;
3495
	} elseif (isset($plugins_actifs[$plugin][$type_info]) and !$reload) {
3496
		return $plugins_actifs[$plugin][$type_info];
3497
	} else {
3498
		$get_infos = charger_fonction('get_infos', 'plugins');
3499
		// On prend en compte les extensions
3500
		if (!is_dir($plugins_actifs[$plugin]['dir_type'])) {
3501
			$dir_plugins = constant($plugins_actifs[$plugin]['dir_type']);
3502
		} else {
3503
			$dir_plugins = $plugins_actifs[$plugin]['dir_type'];
3504
		}
3505
		if (!$infos = $get_infos($plugins_actifs[$plugin]['dir'], $reload, $dir_plugins)) {
3506
			return '';
3507
		}
3508
		if ($type_info == 'tout') {
3509
			return $infos;
3510
		} elseif ($type_info == 'est_actif') {
3511
			return $infos ? 1 : 0;
3512
		} else {
3513
			return strval($infos[$type_info]);
3514
		}
3515
	}
3516
}
3517
3518
3519
/**
3520
 * Affiche la puce statut d'un objet, avec un menu rapide pour changer
3521
 * de statut si possibilité de l'avoir
3522
 *
3523
 * @see inc_puce_statut_dist()
3524
 *
3525
 * @filtre
3526
 *
3527
 * @param int $id_objet
3528
 *     Identifiant de l'objet
3529
 * @param string $statut
3530
 *     Statut actuel de l'objet
3531
 * @param int $id_rubrique
3532
 *     Identifiant du parent
3533
 * @param string $type
3534
 *     Type d'objet
3535
 * @param bool $ajax
3536
 *     Indique s'il ne faut renvoyer que le coeur du menu car on est
3537
 *     dans une requete ajax suite à un post de changement rapide
3538
 * @return string
3539
 *     Code HTML de l'image de puce de statut à insérer (et du menu de changement si présent)
3540
 */
3541
function puce_changement_statut($id_objet, $statut, $id_rubrique, $type, $ajax = false) {
3542
	$puce_statut = charger_fonction('puce_statut', 'inc');
3543
3544
	return $puce_statut($id_objet, $statut, $id_rubrique, $type, $ajax);
3545
}
3546
3547
3548
/**
3549
 * Affiche la puce statut d'un objet, avec un menu rapide pour changer
3550
 * de statut si possibilité de l'avoir
3551
 *
3552
 * Utilisable sur tout objet qui a declaré ses statuts
3553
 *
3554
 * @example
3555
 *     [(#STATUT|puce_statut{article})] affiche une puce passive
3556
 *     [(#STATUT|puce_statut{article,#ID_ARTICLE,#ID_RUBRIQUE})] affiche une puce avec changement rapide
3557
 *
3558
 * @see inc_puce_statut_dist()
3559
 *
3560
 * @filtre
3561
 *
3562
 * @param string $statut
3563
 *     Statut actuel de l'objet
3564
 * @param string $objet
3565
 *     Type d'objet
3566
 * @param int $id_objet
3567
 *     Identifiant de l'objet
3568
 * @param int $id_parent
3569
 *     Identifiant du parent
3570
 * @return string
3571
 *     Code HTML de l'image de puce de statut à insérer (et du menu de changement si présent)
3572
 */
3573
function filtre_puce_statut_dist($statut, $objet, $id_objet = 0, $id_parent = 0) {
3574
	static $puce_statut = null;
3575
	if (!$puce_statut) {
3576
		$puce_statut = charger_fonction('puce_statut', 'inc');
3577
	}
3578
3579
	return $puce_statut($id_objet, $statut, $id_parent, $objet, false,
3580
		objet_info($objet, 'editable') ? _ACTIVER_PUCE_RAPIDE : false);
3581
}
3582
3583
3584
/**
3585
 * Encoder un contexte pour l'ajax
3586
 *
3587
 * Encoder le contexte, le signer avec une clé, le crypter
3588
 * avec le secret du site, le gziper si possible.
3589
 *
3590
 * L'entrée peut-être sérialisée (le `#ENV**` des fonds ajax et ajax_stat)
3591
 *
3592
 * @see  decoder_contexte_ajax()
3593
 * @uses calculer_cle_action()
3594
 *
3595
 * @param string|array $c
3596
 *   contexte, peut etre un tableau serialize
3597
 * @param string $form
3598
 *   nom du formulaire eventuel
3599
 * @param string $emboite
0 ignored issues
show
Documentation introduced by
Should the type for parameter $emboite 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...
3600
 *   contenu a emboiter dans le conteneur ajax
3601
 * @param string $ajaxid
3602
 *   ajaxid pour cibler le bloc et forcer sa mise a jour
3603
 * @return string
3604
 *   hash du contexte
3605
 */
3606
function encoder_contexte_ajax($c, $form = '', $emboite = null, $ajaxid = '') {
3607
	if (is_string($c)
3608
		and @unserialize($c) !== false
3609
	) {
3610
		$c = unserialize($c);
3611
	}
3612
3613
	// supprimer les parametres debut_x
3614
	// pour que la pagination ajax ne soit pas plantee
3615
	// si on charge la page &debut_x=1 : car alors en cliquant sur l'item 0,
3616
	// le debut_x=0 n'existe pas, et on resterait sur 1
3617
	if (is_array($c)) {
3618
		foreach ($c as $k => $v) {
3619
			if (strpos($k, 'debut_') === 0) {
3620
				unset($c[$k]);
3621
			}
3622
		}
3623
	}
3624
3625
	if (!function_exists('calculer_cle_action')) {
3626
		include_spip("inc/securiser_action");
3627
	}
3628
3629
	$c = serialize($c);
3630
	$cle = calculer_cle_action($form . $c);
3631
	$c = "$cle:$c";
3632
3633
	// on ne stocke pas les contextes dans des fichiers en cache
3634
	// par defaut, sauf si cette configuration a été forcée
3635
	// OU que la longueur de l’argument géneré est plus long
3636
	// que ce qui est toléré.
3637
	$cache_contextes_ajax = (defined('_CACHE_CONTEXTES_AJAX') and _CACHE_CONTEXTES_AJAX);
3638
	if (!$cache_contextes_ajax) {
3639
		$env = $c;
3640
		if (function_exists('gzdeflate') && function_exists('gzinflate')) {
3641
			$env = gzdeflate($env);
3642
			// https://core.spip.net/issues/2667 | https://bugs.php.net/bug.php?id=61287
3643
			if ((PHP_VERSION_ID == 50400) and !@gzinflate($env)) {
3644
				$cache_contextes_ajax = true;
3645
				spip_log("Contextes AJAX forces en fichiers ! Erreur PHP 5.4.0", _LOG_AVERTISSEMENT);
3646
			}
3647
		}
3648
		$env = _xor($env);
3649
		$env = base64_encode($env);
3650
		$len = strlen($env);
3651
		// Si l’url est trop longue pour le navigateur
3652
		$max_len = _CACHE_CONTEXTES_AJAX_SUR_LONGUEUR;
3653
		if ($len > $max_len) {
3654
			$cache_contextes_ajax = true;
3655
			spip_log("Contextes AJAX forces en fichiers !"
3656
				. " Cela arrive lorsque la valeur du contexte" 
3657
				. " depasse la longueur maximale autorisee ($max_len). Ici : $len."
3658
				, _LOG_AVERTISSEMENT);
3659
		}
3660
		// Sinon si Suhosin est actif et a une la valeur maximale des variables en GET...
3661
		elseif (
3662
			$max_len = @ini_get('suhosin.get.max_value_length')
3663
			and $max_len < $len
3664
		) {
3665
			$cache_contextes_ajax = true;
3666
			spip_log("Contextes AJAX forces en fichiers !"
3667
				. " Cela arrive lorsque la valeur du contexte"
3668
				. " depasse la longueur maximale autorisee par Suhosin"
3669
				. " ($max_len) dans 'suhosin.get.max_value_length'. Ici : $len."
3670
				. " Vous devriez modifier les parametres de Suhosin"
3671
				. " pour accepter au moins 1024 caracteres.", _LOG_AVERTISSEMENT);
3672
		} 
3673
3674
	}
3675
3676
	if ($cache_contextes_ajax) {
3677
		$dir = sous_repertoire(_DIR_CACHE, 'contextes');
3678
		// stocker les contextes sur disque et ne passer qu'un hash dans l'url
3679
		$md5 = md5($c);
3680
		ecrire_fichier("$dir/c$md5", $c);
3681
		$env = $md5;
3682
	}
3683
3684
	if ($emboite === null) {
3685
		return $env;
0 ignored issues
show
Bug introduced by
The variable $env does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
3686
	}
3687
	if (!trim($emboite)) {
3688
		return "";
3689
	}
3690
	// toujours encoder l'url source dans le bloc ajax
3691
	$r = self();
3692
	$r = ' data-origin="' . $r . '"';
3693
	$class = 'ajaxbloc';
3694
	if ($ajaxid and is_string($ajaxid)) {
3695
		// ajaxid est normalement conforme a un nom de classe css
3696
		// on ne verifie pas la conformite, mais on passe entites_html par dessus par precaution
3697
		$class .= ' ajax-id-' . entites_html($ajaxid);
3698
	}
3699
3700
	return "<div class='$class' " . "data-ajax-env='$env'$r>\n$emboite</div><!--ajaxbloc-->\n";
3701
}
3702
3703
/**
3704
 * Décoder un hash de contexte pour l'ajax
3705
 *
3706
 * Précude inverse de `encoder_contexte_ajax()`
3707
 *
3708
 * @see  encoder_contexte_ajax()
3709
 * @uses calculer_cle_action()
3710
 *
3711
 * @param string $c
3712
 *   hash du contexte
3713
 * @param string $form
3714
 *   nom du formulaire eventuel
3715
 * @return array|string|bool
3716
 *   - array|string : contexte d'environnement, possiblement sérialisé
3717
 *   - false : erreur de décodage
3718
 */
3719
function decoder_contexte_ajax($c, $form = '') {
3720
	if (!function_exists('calculer_cle_action')) {
3721
		include_spip("inc/securiser_action");
3722
	}
3723
	if (((defined('_CACHE_CONTEXTES_AJAX') and _CACHE_CONTEXTES_AJAX) or strlen($c) == 32)
3724
		and $dir = sous_repertoire(_DIR_CACHE, 'contextes')
3725
		and lire_fichier("$dir/c$c", $contexte)
3726
	) {
3727
		$c = $contexte;
3728
	} else {
3729
		$c = @base64_decode($c);
3730
		$c = _xor($c);
3731
		if (function_exists('gzdeflate') && function_exists('gzinflate')) {
3732
			$c = @gzinflate($c);
3733
		}
3734
	}
3735
3736
	// extraire la signature en debut de contexte
3737
	// et la verifier avant de deserializer
3738
	// format : signature:donneesserializees
3739 View Code Duplication
	if ($p = strpos($c,":")){
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...
3740
		$cle = substr($c,0,$p);
3741
		$c = substr($c,$p+1);
3742
3743
		if ($cle == calculer_cle_action($form . $c)) {
3744
			$env = @unserialize($c);
3745
			return $env;
3746
		}
3747
	}
3748
3749
	return false;
3750
}
3751
3752
3753
/**
3754
 * Encrypte ou décrypte un message
3755
 *
3756
 * @link http://www.php.net/manual/fr/language.operators.bitwise.php#81358
3757
 *
3758
 * @param string $message
3759
 *    Message à encrypter ou décrypter
3760
 * @param null|string $key
3761
 *    Clé de cryptage / décryptage.
3762
 *    Une clé sera calculée si non transmise
3763
 * @return string
3764
 *    Message décrypté ou encrypté
3765
 **/
3766
function _xor($message, $key = null) {
3767
	if (is_null($key)) {
3768
		if (!function_exists('calculer_cle_action')) {
3769
			include_spip("inc/securiser_action");
3770
		}
3771
		$key = pack("H*", calculer_cle_action('_xor'));
3772
	}
3773
3774
	$keylen = strlen($key);
3775
	$messagelen = strlen($message);
3776
	for ($i = 0; $i < $messagelen; $i++) {
3777
		$message[$i] = ~($message[$i] ^ $key[$i % $keylen]);
3778
	}
3779
3780
	return $message;
3781
}
3782
3783
/**
3784
 * Retourne une URL de réponse de forum (aucune action ici)
3785
 *
3786
 * @see filtre_url_reponse_forum() du plugin forum (prioritaire)
3787
 * @note
3788
 *   La vraie fonction est dans le plugin forum,
3789
 *   mais on évite ici une erreur du compilateur en absence du plugin
3790
 * @param string $texte
3791
 * @return string
3792
 */
3793
function url_reponse_forum($texte) { return $texte; }
3794
3795
/**
3796
 * retourne une URL de suivi rss d'un forum (aucune action ici)
3797
 *
3798
 * @see filtre_url_rss_forum() du plugin forum (prioritaire)
3799
 * @note
3800
 *   La vraie fonction est dans le plugin forum,
3801
 *   mais on évite ici une erreur du compilateur en absence du plugin
3802
 * @param string $texte
3803
 * @return string
3804
 */
3805
function url_rss_forum($texte) { return $texte; }
3806
3807
3808
/**
3809
 * Génère des menus avec liens ou `<strong class='on'>` non clicable lorsque
3810
 * l'item est sélectionné
3811
 *
3812
 * @filtre
3813
 * @link https://www.spip.net/4004
3814
 * @example
3815
 *   ```
3816
 *   [(#URL_RUBRIQUE|lien_ou_expose{#TITRE, #ENV{test}|=={en_cours}})]
3817
 *   ```
3818
 *
3819
 * @param string $url
3820
 *   URL du lien
3821
 * @param string $libelle
0 ignored issues
show
Documentation introduced by
Should the type for parameter $libelle 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...
3822
 *   Texte du lien
3823
 * @param bool $on
3824
 *   État exposé (génère un strong) ou non (génère un lien)
3825
 * @param string $class
3826
 *   Classes CSS ajoutées au lien
3827
 * @param string $title
3828
 *   Title ajouté au lien
3829
 * @param string $rel
3830
 *   Attribut `rel` ajouté au lien
3831
 * @param string $evt
3832
 *   Complement à la balise `a` pour gérer un événement javascript,
3833
 *   de la forme ` onclick='...'`
3834
 * @return string
3835
 *   Code HTML
3836
 */
3837
function lien_ou_expose($url, $libelle = null, $on = false, $class = "", $title = "", $rel = "", $evt = '') {
3838
	if ($on) {
3839
		$bal = "strong";
3840
		$att = "class='on'";
3841
	} else {
3842
		$bal = 'a';
3843
		$att = "href='$url'"
3844
			. ($title ? " title='" . attribut_html($title) . "'" : '')
3845
			. ($class ? " class='" . attribut_html($class) . "'" : '')
3846
			. ($rel ? " rel='" . attribut_html($rel) . "'" : '')
3847
			. $evt;
3848
	}
3849
	if ($libelle === null) {
3850
		$libelle = $url;
3851
	}
3852
3853
	return "<$bal $att>$libelle</$bal>";
3854
}
3855
3856
3857
/**
3858
 * Afficher un message "un truc"/"N trucs"
3859
 * Les items sont à indiquer comme pour la fonction _T() sous la forme :
3860
 * "module:chaine"
3861
 *
3862
 * @param int $nb : le nombre
3863
 * @param string $chaine_un : l'item de langue si $nb vaut un
3864
 * @param string $chaine_plusieurs : l'item de lanque si $nb >= 2
3865
 * @param string $var : La variable à remplacer par $nb dans l'item de langue (facultatif, défaut "nb")
3866
 * @param array $vars : Les autres variables nécessaires aux chaines de langues (facultatif)
3867
 * @return string : la chaine de langue finale en utilisant la fonction _T()
3868
 */
3869
function singulier_ou_pluriel($nb, $chaine_un, $chaine_plusieurs, $var = 'nb', $vars = array()) {
3870
	static $local_singulier_ou_pluriel = array();
3871
3872
	// si nb=0 ou pas de $vars valide on retourne une chaine vide, a traiter par un |sinon
3873
	if (!is_numeric($nb) or $nb == 0) {
3874
		return "";
3875
	}
3876
	if (!is_array($vars)) {
3877
		return "";
3878
	}
3879
3880
	$langue = $GLOBALS['spip_lang'];
3881
	if (!isset($local_singulier_ou_pluriel[$langue])) {
3882
		$local_singulier_ou_pluriel[$langue] = false;
3883
		if ($f = charger_fonction("singulier_ou_pluriel_${langue}", 'inc', true)
3884
		  or $f = charger_fonction("singulier_ou_pluriel", 'inc', true)) {
3885
			$local_singulier_ou_pluriel[$langue] = $f;
3886
		}
3887
	}
3888
3889
	// si on a une surcharge on l'utilise
3890
	if ($local_singulier_ou_pluriel[$langue]) {
3891
		return ($local_singulier_ou_pluriel[$langue])($nb, $chaine_un, $chaine_plusieurs, $var, $vars);
3892
	}
3893
3894
	// sinon traitement par defaut
3895
	$vars[$var] = $nb;
3896
	if ($nb >= 2) {
3897
		return _T($chaine_plusieurs, $vars);
3898
	} else {
3899
		return _T($chaine_un, $vars);
3900
	}
3901
}
3902
3903
3904
/**
3905
 * Fonction de base pour une icone dans un squelette
3906
 * structure html : `<span><a><img><b>texte</b></span>`
3907
 *
3908
 * @param string $type
3909
 *  'lien' ou 'bouton'
3910
 * @param string $lien
3911
 *  url
3912
 * @param string $texte
3913
 *  texte du lien / alt de l'image
3914
 * @param string $fond
3915
 *  objet avec ou sans son extension et sa taille (article, article-24, article-24.png)
3916
 * @param string $fonction
3917
 *  new/del/edit
3918
 * @param string $class
3919
 *  classe supplementaire (horizontale, verticale, ajax ...)
3920
 * @param string $javascript
3921
 *  "onclick='...'" par exemple
3922
 * @return string
3923
 */
3924
function prepare_icone_base($type, $lien, $texte, $fond, $fonction = "", $class = "", $javascript = "") {
3925
	if (in_array($fonction, array("del", "supprimer.gif"))) {
3926
		$class .= ' danger';
3927
	} elseif ($fonction == "rien.gif") {
3928
		$fonction = "";
3929
	} elseif ($fonction == "delsafe") {
3930
		$fonction = "del";
3931
	}
3932
3933
	$fond_origine = $fond;
3934
	// remappage des icone : article-24.png+new => article-new-24.png
3935 View Code Duplication
	if ($icone_renommer = charger_fonction('icone_renommer', 'inc', true)) {
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...
3936
		list($fond, $fonction) = $icone_renommer($fond, $fonction);
3937
	}
3938
3939
	// ajouter le type d'objet dans la class de l'icone
3940
	$class .= " " . substr(basename($fond), 0, -4);
3941
3942
	$alt = attribut_html($texte);
3943
	$title = " title=\"$alt\""; // est-ce pertinent de doubler le alt par un title ?
3944
3945
	$ajax = "";
3946
	if (strpos($class, "ajax") !== false) {
3947
		$ajax = "ajax";
3948
		if (strpos($class, "preload") !== false) {
3949
			$ajax .= " preload";
3950
		}
3951
		if (strpos($class, "nocache") !== false) {
3952
			$ajax .= " nocache";
3953
		}
3954
		$ajax = " class='$ajax'";
3955
	}
3956
3957
	$size = 24;
3958
	if (preg_match("/-([0-9]{1,3})[.](gif|png|svg)$/i", $fond, $match)
3959
	  or preg_match("/-([0-9]{1,3})([.](gif|png|svg))?$/i", $fond_origine, $match)) {
3960
		$size = $match[1];
3961
	}
3962
3963
	$icone = http_img_pack($fond, $alt, "width='$size' height='$size'");
3964
	$icone = "<span class=\"icone-image".($fonction ? " icone-fonction icone-fonction-$fonction" : "") . "\">$icone</span>";
3965
3966
	if ($type == 'lien') {
3967
		return "<span class='icone s$size $class'>"
3968
		. "<a href='$lien'$title$ajax$javascript>"
3969
		. $icone
3970
		. "<b>$texte</b>"
3971
		. "</a></span>\n";
3972
	} else {
3973
		return bouton_action("$icone<b>$texte</b>", $lien, "icone s$size $class", $javascript, $alt);
3974
	}
3975
}
3976
3977
/**
3978
 * Crée un lien ayant une icone
3979
 *
3980
 * @uses prepare_icone_base()
3981
 *
3982
 * @param string $lien
3983
 *     URL du lien
3984
 * @param string $texte
3985
 *     Texte du lien
3986
 * @param string $fond
3987
 *     Objet avec ou sans son extension et sa taille (article, article-24, article-24.png)
3988
 * @param string $fonction
3989
 *     Fonction du lien (`edit`, `new`, `del`)
3990
 * @param string $class
3991
 *     Classe CSS, tel que `left`, `right` pour définir un alignement
3992
 * @param string $javascript
3993
 *     Javascript ajouté sur le lien
3994
 * @return string
3995
 *     Code HTML du lien
3996
 **/
3997
function icone_base($lien, $texte, $fond, $fonction = "", $class = "", $javascript = "") {
3998
	return prepare_icone_base('lien', $lien, $texte, $fond, $fonction, $class, $javascript);
3999
}
4000
4001
/**
4002
 * Crée un lien précédé d'une icone au dessus du texte
4003
 *
4004
 * @uses icone_base()
4005
 * @see  icone_verticale() Pour un usage dans un code PHP.
4006
 *
4007
 * @filtre
4008
 * @example
4009
 *     ```
4010
 *     [(#AUTORISER{voir,groupemots,#ID_GROUPE})
4011
 *         [(#URL_ECRIRE{groupe_mots,id_groupe=#ID_GROUPE}
4012
 *            |icone_verticale{<:mots:icone_voir_groupe_mots:>,groupe_mots-24.png,'',left})]
4013
 *    ]
4014
 *     ```
4015
 *
4016
 * @param string $lien
4017
 *     URL du lien
4018
 * @param string $texte
4019
 *     Texte du lien
4020
 * @param string $fond
4021
 *     Objet avec ou sans son extension et sa taille (article, article-24, article-24.png)
4022
 * @param string $fonction
4023
 *     Fonction du lien (`edit`, `new`, `del`)
4024
 * @param string $class
4025
 *     Classe CSS à ajouter, tel que `left`, `right`, `center` pour définir un alignement.
4026
 *     Il peut y en avoir plusieurs : `left ajax`
4027
 * @param string $javascript
4028
 *     Javascript ajouté sur le lien
4029
 * @return string
4030
 *     Code HTML du lien
4031
 **/
4032
function filtre_icone_verticale_dist($lien, $texte, $fond, $fonction = "", $class = "", $javascript = "") {
4033
	return icone_base($lien, $texte, $fond, $fonction, "verticale $class", $javascript);
4034
}
4035
4036
/**
4037
 * Crée un lien précédé d'une icone horizontale
4038
 *
4039
 * @uses icone_base()
4040
 * @see  icone_horizontale() Pour un usage dans un code PHP.
4041
 *
4042
 * @filtre
4043
 * @example
4044
 *     En tant que filtre dans un squelettes :
4045
 *     ```
4046
 *     [(#URL_ECRIRE{sites}|icone_horizontale{<:sites:icone_voir_sites_references:>,site-24.png})]
4047
 *
4048
 *     [(#AUTORISER{supprimer,groupemots,#ID_GROUPE}|oui)
4049
 *         [(#URL_ACTION_AUTEUR{supprimer_groupe_mots,#ID_GROUPE,#URL_ECRIRE{mots}}
4050
 *             |icone_horizontale{<:mots:icone_supprimer_groupe_mots:>,groupe_mots,del})]
4051
 *     ]
4052
 *     ```
4053
 *
4054
 *     En tant que filtre dans un code php :
4055
 *     ```
4056
 *     $icone_horizontale=chercher_filtre('icone_horizontale');
4057
 *     $icone = $icone_horizontale(generer_url_ecrire("stats_visites","id_article=$id_article"),
4058
 *         _T('statistiques:icone_evolution_visites', array('visites' => $visites)),
4059
 *         "statistique-24.png");
4060
 *     ```
4061
 *
4062
 * @param string $lien
4063
 *     URL du lien
4064
 * @param string $texte
4065
 *     Texte du lien
4066
 * @param string $fond
4067
 *     Objet avec ou sans son extension et sa taille (article, article-24, article-24.png)
4068
 * @param string $fonction
4069
 *     Fonction du lien (`edit`, `new`, `del`)
4070
 * @param string $class
4071
 *     Classe CSS à ajouter
4072
 * @param string $javascript
4073
 *     Javascript ajouté sur le lien
4074
 * @return string
4075
 *     Code HTML du lien
4076
 **/
4077
function filtre_icone_horizontale_dist($lien, $texte, $fond, $fonction = "", $class = "", $javascript = "") {
4078
	return icone_base($lien, $texte, $fond, $fonction, "horizontale $class", $javascript);
4079
}
4080
4081
/**
4082
 * Crée un bouton d'action intégrant une icone horizontale
4083
 *
4084
 * @uses prepare_icone_base()
4085
 *
4086
 * @filtre
4087
 * @example
4088
 *     ```
4089
 *     [(#URL_ACTION_AUTEUR{supprimer_mot, #ID_MOT, #URL_ECRIRE{groupe_mots,id_groupe=#ID_GROUPE}}
4090
 *         |bouton_action_horizontal{<:mots:info_supprimer_mot:>,mot-24.png,del})]
4091
 *     ```
4092
 *
4093
 * @param string $lien
4094
 *     URL de l'action
4095
 * @param string $texte
4096
 *     Texte du bouton
4097
 * @param string $fond
4098
 *     Objet avec ou sans son extension et sa taille (article, article-24, article-24.png)
4099
 * @param string $fonction
4100
 *     Fonction du bouton (`edit`, `new`, `del`)
4101
 * @param string $class
4102
 *     Classe CSS à ajouter
4103
 * @param string $confirm
4104
 *     Message de confirmation à ajouter en javascript sur le bouton
4105
 * @return string
4106
 *     Code HTML du lien
4107
 **/
4108
function filtre_bouton_action_horizontal_dist($lien, $texte, $fond, $fonction = "", $class = "", $confirm = "") {
4109
	return prepare_icone_base('bouton', $lien, $texte, $fond, $fonction, "horizontale $class", $confirm);
4110
}
4111
4112
/**
4113
 * Filtre `icone` pour compatibilité mappé sur `icone_base`
4114
 *
4115
 * @uses icone_base()
4116
 * @see  filtre_icone_verticale_dist()
4117
 *
4118
 * @filtre
4119
 * @deprecated Utiliser le filtre `icone_verticale`
4120
 *
4121
 * @param string $lien
4122
 *     URL du lien
4123
 * @param string $texte
4124
 *     Texte du lien
4125
 * @param string $fond
4126
 *     Nom de l'image utilisée
4127
 * @param string $align
4128
 *     Classe CSS d'alignement (`left`, `right`, `center`)
4129
 * @param string $fonction
4130
 *     Fonction du lien (`edit`, `new`, `del`)
4131
 * @param string $class
4132
 *     Classe CSS à ajouter
4133
 * @param string $javascript
4134
 *     Javascript ajouté sur le lien
4135
 * @return string
4136
 *     Code HTML du lien
4137
 */
4138
function filtre_icone_dist($lien, $texte, $fond, $align = "", $fonction = "", $class = "", $javascript = "") {
4139
	return icone_base($lien, $texte, $fond, $fonction, "verticale $align $class", $javascript);
4140
}
4141
4142
4143
/**
4144
 * Explose un texte en tableau suivant un séparateur
4145
 *
4146
 * @note
4147
 *     Inverse l'écriture de la fonction PHP de même nom
4148
 *     pour que le filtre soit plus pratique dans les squelettes
4149
 *
4150
 * @filtre
4151
 * @example
4152
 *     ```
4153
 *     [(#GET{truc}|explode{-})]
4154
 *     ```
4155
 *
4156
 * @param string $a Texte
4157
 * @param string $b Séparateur
4158
 * @return array Liste des éléments
4159
 */
4160
function filtre_explode_dist($a, $b) { return explode($b, $a); }
4161
4162
/**
4163
 * Implose un tableau en chaine en liant avec un séparateur
4164
 *
4165
 * @note
4166
 *     Inverse l'écriture de la fonction PHP de même nom
4167
 *     pour que le filtre soit plus pratique dans les squelettes
4168
 *
4169
 * @filtre
4170
 * @example
4171
 *     ```
4172
 *     [(#GET{truc}|implode{-})]
4173
 *     ```
4174
 *
4175
 * @param array $a Tableau
4176
 * @param string $b Séparateur
4177
 * @return string Texte
4178
 */
4179
function filtre_implode_dist($a, $b) { return is_array($a) ? implode($b, $a) : $a; }
4180
4181
/**
4182
 * Produire les styles privés qui associent item de menu avec icone en background
4183
 *
4184
 * @return string Code CSS
4185
 */
4186
function bando_images_background() {
4187
	include_spip('inc/bandeau');
4188
	// recuperer tous les boutons et leurs images
4189
	$boutons = definir_barre_boutons(definir_barre_contexte(), true, false);
4190
4191
	$res = "";
4192
	foreach ($boutons as $page => $detail) {
4193
		if ($detail->icone and strlen(trim($detail->icone))) {
4194
			$res .= "\n.navigation_avec_icones #bando1_$page {background-image:url(" . $detail->icone . ");}";
4195
		}
4196
		$selecteur = (in_array($page, array('outils_rapides', 'outils_collaboratifs')) ? "" : ".navigation_avec_icones ");
4197
		if (is_array($detail->sousmenu)) {
4198
			foreach ($detail->sousmenu as $souspage => $sousdetail) {
4199
				if ($sousdetail->icone and strlen(trim($sousdetail->icone))) {
4200
					$res .= "\n$selecteur.bando2_$souspage {background-image:url(" . $sousdetail->icone . ");}";
4201
				}
4202
			}
4203
		}
4204
	}
4205
4206
	return $res;
4207
}
4208
4209
/**
4210
 * Generer un bouton_action
4211
 * utilise par #BOUTON_ACTION
4212
 *
4213
 * @param string $libelle
4214
 * @param string $url
4215
 * @param string $class
4216
 * @param string $confirm
4217
 *   message de confirmation oui/non avant l'action
4218
 * @param string $title
4219
 * @param string $callback
4220
 *   callback js a appeler lors de l'evenement action (apres confirmation eventuelle si $confirm est non vide)
4221
 *   et avant execution de l'action. Si la callback renvoie false, elle annule le declenchement de l'action
4222
 * @return string
4223
 */
4224
function bouton_action($libelle, $url, $class = "", $confirm = "", $title = "", $callback = "") {
4225
	if ($confirm) {
4226
		$confirm = "confirm(\"" . attribut_html($confirm) . "\")";
4227
		if ($callback) {
4228
			$callback = "$confirm?($callback):false";
4229
		} else {
4230
			$callback = $confirm;
4231
		}
4232
	}
4233
	$onclick = $callback ? " onclick='return " . addcslashes($callback, "'") . "'" : "";
4234
	$title = $title ? " title='$title'" : "";
4235
4236
	return "<form class='bouton_action_post $class' method='post' action='$url'><div>" . form_hidden($url)
4237
	. "<button type='submit' class='submit'$title$onclick>$libelle</button></div></form>";
4238
}
4239
4240
/**
4241
 * Donner n'importe quelle information sur un objet de maniere generique.
4242
 *
4243
 * La fonction va gerer en interne deux cas particuliers les plus utilises :
4244
 * l'URL et le titre (qui n'est pas forcemment le champ SQL "titre").
4245
 *
4246
 * On peut ensuite personnaliser les autres infos en creant une fonction
4247
 * generer_<nom_info>_entite($id_objet, $type_objet, $ligne).
4248
 * $ligne correspond a la ligne SQL de tous les champs de l'objet, les fonctions
4249
 * de personnalisation n'ont donc pas a refaire de requete.
4250
 *
4251
 * @param int $id_objet
4252
 * @param string $type_objet
4253
 * @param string $info
4254
 * @param string $etoile
4255
 * @return string
4256
 */
4257
function generer_info_entite($id_objet, $type_objet, $info, $etoile = "") {
4258
	static $trouver_table = null;
4259
	static $objets;
4260
4261
	// On verifie qu'on a tout ce qu'il faut
4262
	$id_objet = intval($id_objet);
4263
	if (!($id_objet and $type_objet and $info)) {
4264
		return '';
4265
	}
4266
4267
	// si on a deja note que l'objet n'existe pas, ne pas aller plus loin
4268
	if (isset($objets[$type_objet]) and $objets[$type_objet] === false) {
4269
		return '';
4270
	}
4271
4272
	// Si on demande l'url, on retourne direct la fonction
4273
	if ($info == 'url') {
4274
		return generer_url_entite($id_objet, $type_objet);
4275
	}
4276
4277
	// Sinon on va tout chercher dans la table et on garde en memoire
4278
	$demande_titre = ($info == 'titre');
4279
4280
	// On ne fait la requete que si on a pas deja l'objet ou si on demande le titre mais qu'on ne l'a pas encore
4281
	if (!isset($objets[$type_objet][$id_objet])
4282
		or
4283
		($demande_titre and !isset($objets[$type_objet][$id_objet]['titre']))
4284
	) {
4285
		if (!$trouver_table) {
4286
			$trouver_table = charger_fonction('trouver_table', 'base');
4287
		}
4288
		$desc = $trouver_table(table_objet_sql($type_objet));
4289
		if (!$desc) {
4290
			return $objets[$type_objet] = false;
4291
		}
4292
4293
		// Si on demande le titre, on le gere en interne
4294
		$champ_titre = "";
4295
		if ($demande_titre) {
4296
			// si pas de titre declare mais champ titre, il sera peuple par le select *
4297
			$champ_titre = (!empty($desc['titre'])) ? ', ' . $desc['titre'] : '';
4298
		}
4299
		include_spip('base/abstract_sql');
4300
		include_spip('base/connect_sql');
4301
		$objets[$type_objet][$id_objet] = sql_fetsel(
4302
			'*' . $champ_titre,
4303
			$desc['table_sql'],
4304
			id_table_objet($type_objet) . ' = ' . intval($id_objet)
4305
		);
4306
	}
4307
4308
	// Si la fonction generer_TRUC_TYPE existe, on l'utilise pour formater $info_generee
4309
	if ($generer = charger_fonction("generer_${info}_${type_objet}", '', true)) {
4310
		$info_generee = $generer($id_objet, $objets[$type_objet][$id_objet]);
4311
	} // Si la fonction generer_TRUC_entite existe, on l'utilise pour formater $info_generee
4312
	else {
4313
		if ($generer = charger_fonction("generer_${info}_entite", '', true)) {
4314
			$info_generee = $generer($id_objet, $type_objet, $objets[$type_objet][$id_objet]);
4315
		} // Sinon on prend directement le champ SQL tel quel
4316
		else {
4317
			$info_generee = (isset($objets[$type_objet][$id_objet][$info]) ? $objets[$type_objet][$id_objet][$info] : '');
4318
		}
4319
	}
4320
4321
	// On va ensuite appliquer les traitements automatiques si besoin
4322
	if (!$etoile) {
4323
		// FIXME: on fournit un ENV minimum avec id et type et connect=''
4324
		// mais ce fonctionnement est a ameliorer !
4325
		$info_generee = appliquer_traitement_champ($info_generee, $info, table_objet($type_objet),
4326
			array('id_objet' => $id_objet, 'objet' => $type_objet, ''));
4327
	}
4328
4329
	return $info_generee;
4330
}
4331
4332
/**
4333
 * Appliquer a un champ SQL le traitement qui est configure pour la balise homonyme dans les squelettes
4334
 *
4335
 * @param string $texte
4336
 * @param string $champ
4337
 * @param string $table_objet
4338
 * @param array $env
4339
 * @param string $connect
4340
 * @return string
4341
 */
4342
function appliquer_traitement_champ($texte, $champ, $table_objet = '', $env = array(), $connect = '') {
0 ignored issues
show
Unused Code introduced by
The parameter $connect is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
4343
	if (!$champ) {
4344
		return $texte;
4345
	}
4346
	
4347
	// On charge toujours les filtres de texte car la majorité des traitements les utilisent
4348
	// et il ne faut pas partir du principe que c'est déjà chargé (form ajax, etc)
4349
	include_spip('inc/texte');
4350
	
4351
	$champ = strtoupper($champ);
4352
	$traitements = isset($GLOBALS['table_des_traitements'][$champ]) ? $GLOBALS['table_des_traitements'][$champ] : false;
4353
	if (!$traitements or !is_array($traitements)) {
4354
		return $texte;
4355
	}
4356
4357
	$traitement = '';
4358
	if ($table_objet and (!isset($traitements[0]) or count($traitements) > 1)) {
4359
		// necessaire pour prendre en charge les vieux appels avec un table_objet_sql en 3e arg
4360
		$table_objet = table_objet($table_objet);
4361
		if (isset($traitements[$table_objet])) {
4362
			$traitement = $traitements[$table_objet];
4363
		}
4364
	}
4365
	if (!$traitement and isset($traitements[0])) {
4366
		$traitement = $traitements[0];
4367
	}
4368
	// (sinon prendre le premier de la liste par defaut ?)
4369
4370
	if (!$traitement) {
4371
		return $texte;
4372
	}
4373
4374
	$traitement = str_replace('%s', "'" . texte_script($texte) . "'", $traitement);
4375
4376
	// Fournir $connect et $Pile[0] au traitement si besoin
4377
	$Pile = array(0 => $env);
0 ignored issues
show
Unused Code introduced by
$Pile is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
4378
	eval("\$texte = $traitement;");
4379
4380
	return $texte;
4381
}
4382
4383
4384
/**
4385
 * Generer un lien (titre clicable vers url) vers un objet
4386
 *
4387
 * @param int $id_objet
4388
 * @param $objet
4389
 * @param int $longueur
4390
 * @param null|string $connect
4391
 * @return string
4392
 */
4393
function generer_lien_entite($id_objet, $objet, $longueur = 80, $connect = null) {
4394
	include_spip('inc/liens');
4395
	$titre = traiter_raccourci_titre($id_objet, $objet, $connect);
4396
	// lorsque l'objet n'est plus declare (plugin desactive par exemple)
4397
	// le raccourcis n'est plus valide
4398
	$titre = isset($titre['titre']) ? typo($titre['titre']) : '';
4399
	// on essaye avec generer_info_entite ?
4400
	if (!strlen($titre) and !$connect) {
4401
		$titre = generer_info_entite($id_objet, $objet, 'titre');
4402
	}
4403
	if (!strlen($titre)) {
4404
		$titre = _T('info_sans_titre');
4405
	}
4406
	$url = generer_url_entite($id_objet, $objet, '', '', $connect);
4407
4408
	return "<a href='$url' class='$objet'>" . couper($titre, $longueur) . "</a>";
4409
}
4410
4411
4412
/**
4413
 * Englobe (Wrap) un texte avec des balises
4414
 *
4415
 * @example `wrap('mot','<b>')` donne `<b>mot</b>'`
4416
 *
4417
 * @filtre
4418
 * @uses extraire_balises()
4419
 *
4420
 * @param string $texte
4421
 * @param string $wrap
4422
 * @return string
4423
 */
4424
function wrap($texte, $wrap) {
4425
	$balises = extraire_balises($wrap);
0 ignored issues
show
Unused Code introduced by
$balises is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
4426
	if (preg_match_all(",<([a-z]\w*)\b[^>]*>,UimsS", $wrap, $regs, PREG_PATTERN_ORDER)) {
4427
		$texte = $wrap . $texte;
4428
		$regs = array_reverse($regs[1]);
4429
		$wrap = "</" . implode("></", $regs) . ">";
4430
		$texte = $texte . $wrap;
4431
	}
4432
4433
	return $texte;
4434
}
4435
4436
4437
/**
4438
 * afficher proprement n'importe quoi
4439
 * On affiche in fine un pseudo-yaml qui premet de lire humainement les tableaux et de s'y reperer
4440
 *
4441
 * Les textes sont retournes avec simplement mise en forme typo
4442
 *
4443
 * le $join sert a separer les items d'un tableau, c'est en general un \n ou <br /> selon si on fait du html ou du texte
4444
 * les tableaux-listes (qui n'ont que des cles numeriques), sont affiches sous forme de liste separee par des virgules :
4445
 * c'est VOULU !
4446
 *
4447
 * @param $u
4448
 * @param string $join
4449
 * @param int $indent
4450
 * @return array|mixed|string
4451
 */
4452
function filtre_print_dist($u, $join = "<br />", $indent = 0) {
4453
	if (is_string($u)) {
4454
		$u = typo($u);
4455
4456
		return $u;
4457
	}
4458
4459
	// caster $u en array si besoin
4460
	if (is_object($u)) {
4461
		$u = (array)$u;
4462
	}
4463
4464
	if (is_array($u)) {
4465
		$out = "";
4466
		// toutes les cles sont numeriques ?
4467
		// et aucun enfant n'est un tableau
4468
		// liste simple separee par des virgules
4469
		$numeric_keys = array_map('is_numeric', array_keys($u));
4470
		$array_values = array_map('is_array', $u);
4471
		$object_values = array_map('is_object', $u);
4472
		if (array_sum($numeric_keys) == count($numeric_keys)
4473
			and !array_sum($array_values)
4474
			and !array_sum($object_values)
4475
		) {
4476
			return join(", ", array_map('filtre_print_dist', $u));
4477
		}
4478
4479
		// sinon on passe a la ligne et on indente
4480
		$i_str = str_pad("", $indent, " ");
4481
		foreach ($u as $k => $v) {
4482
			$out .= $join . $i_str . "$k: " . filtre_print_dist($v, $join, $indent + 2);
4483
		}
4484
4485
		return $out;
4486
	}
4487
4488
	// on sait pas quoi faire...
4489
	return $u;
4490
}
4491
4492
4493
/**
4494
 * Renvoyer l'info d'un objet
4495
 * telles que definies dans declarer_tables_objets_sql
4496
 *
4497
 * @param string $objet
4498
 * @param string $info
4499
 * @return string|array
4500
 */
4501
function objet_info($objet, $info) {
4502
	$table = table_objet_sql($objet);
4503
	$infos = lister_tables_objets_sql($table);
4504
4505
	return (isset($infos[$info]) ? $infos[$info] : '');
4506
}
4507
4508
/**
4509
 * Filtre pour afficher 'Aucun truc' ou '1 truc' ou 'N trucs'
4510
 * avec la bonne chaîne de langue en fonction de l'objet utilisé
4511
 *
4512
 * @param int $nb
4513
 *     Nombre d'éléments
4514
 * @param string $objet
4515
 *     Objet
4516
 * @return mixed|string
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use string.

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

Loading history...
4517
 *     Texte traduit du comptage, tel que '3 articles'
4518
 */
4519
function objet_afficher_nb($nb, $objet) {
4520
	if (!$nb) {
4521
		return _T(objet_info($objet, 'info_aucun_objet'));
0 ignored issues
show
Bug introduced by
It seems like objet_info($objet, 'info_aucun_objet') targeting objet_info() can also be of type array; however, _T() does only seem to accept string, maybe add an additional type check?

This check looks at variables that 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...
4522
	} else {
4523
		return _T(objet_info($objet, $nb == 1 ? 'info_1_objet' : 'info_nb_objets'), array('nb' => $nb));
0 ignored issues
show
Bug introduced by
It seems like objet_info($objet, $nb =...et' : 'info_nb_objets') targeting objet_info() can also be of type array; however, _T() does only seem to accept string, maybe add an additional type check?

This check looks at variables that 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...
4524
	}
4525
}
4526
4527
/**
4528
 * Filtre pour afficher l'img icone d'un objet
4529
 *
4530
 * @param string $objet
4531
 * @param int $taille
4532
 * @param string $class
4533
 * @return string
4534
 */
4535
function objet_icone($objet, $taille = 24, $class='') {
4536
	$icone = objet_info($objet, 'icone_objet') . "-" . $taille . ".png";
4537
	$icone = chemin_image($icone);
4538
	$balise_img = charger_filtre('balise_img');
4539
4540
	return $icone ? $balise_img($icone, _T(objet_info($objet, 'texte_objet')), $class, $taille) : '';
0 ignored issues
show
Bug introduced by
It seems like objet_info($objet, 'texte_objet') targeting objet_info() can also be of type array; however, _T() does only seem to accept string, maybe add an additional type check?

This check looks at variables that 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...
4541
}
4542
4543
/**
4544
 * Renvoyer une traduction d'une chaine de langue contextuelle à un objet si elle existe,
4545
 * la traduction de la chaine generique
4546
 *
4547
 * Ex : [(#ENV{objet}|objet_label{trad_reference})]
4548
 * va chercher si une chaine objet:trad_reference existe et renvoyer sa trad le cas echeant
4549
 * sinon renvoie la trad de la chaine trad_reference
4550
 * Si la chaine fournie contient un prefixe il est remplacé par celui de l'objet pour chercher la chaine contextuelle
4551
 *
4552
 * Les arguments $args et $options sont ceux de la fonction _T
4553
 *
4554
 * @param string $objet
4555
 * @param string $chaine
4556
 * @param array $args
4557
 * @param array $options
4558
 * @return string
4559
 */
4560
function objet_T($objet, $chaine, $args = array(), $options = array()){
4561
	$chaine = explode(':',$chaine);
4562
	if ($t = _T($objet . ':' . end($chaine), $args, array_merge($options, array('force'=>false)))) {
4563
		return $t;
4564
	}
4565
	$chaine = implode(':',$chaine);
4566
	return _T($chaine, $args, $options);
4567
}
4568
4569
/**
4570
 * Fonction de secours pour inserer le head_css de facon conditionnelle
4571
 *
4572
 * Appelée en filtre sur le squelette qui contient #INSERT_HEAD,
4573
 * elle vérifie l'absence éventuelle de #INSERT_HEAD_CSS et y suplée si besoin
4574
 * pour assurer la compat avec les squelettes qui n'utilisent pas.
4575
 *
4576
 * @param string $flux Code HTML
4577
 * @return string      Code HTML
4578
 */
4579
function insert_head_css_conditionnel($flux) {
4580
	if (strpos($flux, '<!-- insert_head_css -->') === false
4581
		and $p = strpos($flux, '<!-- insert_head -->')
4582
	) {
4583
		// plutot avant le premier js externe (jquery) pour etre non bloquant
4584
		if ($p1 = stripos($flux, '<script src=') and $p1 < $p) {
4585
			$p = $p1;
4586
		}
4587
		$flux = substr_replace($flux, pipeline('insert_head_css', '<!-- insert_head_css -->'), $p, 0);
4588
	}
4589
4590
	return $flux;
4591
}
4592
4593
/**
4594
 * Produire un fichier statique à partir d'un squelette dynamique
4595
 *
4596
 * Permet ensuite à Apache de le servir en statique sans repasser
4597
 * par spip.php à chaque hit sur le fichier.
4598
 *
4599
 * Si le format (css ou js) est passe dans `contexte['format']`, on l'utilise
4600
 * sinon on regarde si le fond finit par .css ou .js, sinon on utilie "html"
4601
 *
4602
 * @uses urls_absolues_css()
4603
 *
4604
 * @param string $fond
4605
 * @param array $contexte
4606
 * @param array $options
4607
 * @param string $connect
4608
 * @return string
4609
 */
4610
function produire_fond_statique($fond, $contexte = array(), $options = array(), $connect = '') {
4611
	if (isset($contexte['format'])) {
4612
		$extension = $contexte['format'];
4613
		unset($contexte['format']);
4614
	} else {
4615
		$extension = "html";
4616
		if (preg_match(',[.](css|js|json)$,', $fond, $m)) {
4617
			$extension = $m[1];
4618
		}
4619
	}
4620
	// recuperer le contenu produit par le squelette
4621
	$options['raw'] = true;
4622
	$cache = recuperer_fond($fond, $contexte, $options, $connect);
4623
4624
	// calculer le nom de la css
4625
	$dir_var = sous_repertoire(_DIR_VAR, 'cache-' . $extension);
4626
	$nom_safe = preg_replace(",\W,", '_', str_replace('.', '_', $fond));
4627
	$contexte_implicite = calculer_contexte_implicite();
4628
4629
	// par defaut on hash selon les contextes qui sont a priori moins variables
4630
	// mais on peut hasher selon le contenu a la demande, si plusieurs contextes produisent un meme contenu
4631
	// reduit la variabilite du nom et donc le nombre de css concatenees possibles in fine
4632
	if (isset($options['hash_on_content']) and $options['hash_on_content']) {
4633
		$hash = md5($contexte_implicite['host'] . '::'. $cache);
4634
	}
4635
	else {
4636
		unset($contexte_implicite['notes']); // pas pertinent pour signaler un changeemnt de contenu pour des css/js
4637
		ksort($contexte);
4638
		$hash = md5($fond . json_encode($contexte_implicite) . json_encode($contexte) . $connect);
4639
	}
4640
	$filename = $dir_var . $extension . "dyn-$nom_safe-" . substr($hash, 0, 8) . ".$extension";
4641
4642
	// mettre a jour le fichier si il n'existe pas
4643
	// ou trop ancien
4644
	// le dernier fichier produit est toujours suffixe par .last
4645
	// et recopie sur le fichier cible uniquement si il change
4646
	if (!file_exists($filename)
4647
		or !file_exists($filename . ".last")
4648
		or (isset($cache['lastmodified']) and $cache['lastmodified'] and filemtime($filename . ".last") < $cache['lastmodified'])
4649
		or (defined('_VAR_MODE') and _VAR_MODE == 'recalcul')
4650
	) {
4651
		$contenu = $cache['texte'];
4652
		// passer les urls en absolu si c'est une css
4653
		if ($extension == "css") {
4654
			$contenu = urls_absolues_css($contenu,
4655
				test_espace_prive() ? generer_url_ecrire('accueil') : generer_url_public($fond));
4656
		}
4657
4658
		$comment = '';
4659
		// ne pas insérer de commentaire si c'est du json
4660
		if ($extension != "json") {
4661
			$comment = "/* #PRODUIRE{fond=$fond";
4662
			foreach ($contexte as $k => $v) {
4663
				$comment .= ",$k=$v";
4664
			}
4665
			// pas de date dans le commentaire car sinon ca invalide le md5 et force la maj
4666
			// mais on peut mettre un md5 du contenu, ce qui donne un aperu rapide si la feuille a change ou non
4667
			$comment .= "}\n   md5:" . md5($contenu) . " */\n";
4668
		}
4669
		// et ecrire le fichier si il change
4670
		ecrire_fichier_calcule_si_modifie($filename, $comment . $contenu, false, true);
4671
	}
4672
4673
	return timestamp($filename);
4674
}
4675
4676
/**
4677
 * Ajouter un timestamp a une url de fichier
4678
 * [(#CHEMIN{monfichier}|timestamp)]
4679
 *
4680
 * @param string $fichier
4681
 *    Le chemin du fichier sur lequel on souhaite ajouter le timestamp
4682
 * @return string
4683
 *    $fichier auquel on a ajouté le timestamp
4684
 */
4685
function timestamp($fichier) {
4686
	if (!$fichier
4687
		or !file_exists($fichier)
4688
		or !$m = filemtime($fichier)
4689
	) {
4690
		return $fichier;
4691
	}
4692
4693
	return "$fichier?$m";
4694
}
4695
4696
/**
4697
 * Supprimer le timestamp d'une url
4698
 *
4699
 * @param string $url
4700
 * @return string
4701
 */
4702
function supprimer_timestamp($url) {
4703
	if (strpos($url, "?") === false) {
4704
		return $url;
4705
	}
4706
4707
	return preg_replace(",\?[[:digit:]]+$,", "", $url);
4708
}
4709
4710
/**
4711
 * Nettoyer le titre d'un email
4712
 *
4713
 * Éviter une erreur lorsqu'on utilise `|nettoyer_titre_email` dans un squelette de mail
4714
 *
4715
 * @filtre
4716
 * @uses nettoyer_titre_email()
4717
 *
4718
 * @param string $titre
4719
 * @return string
4720
 */
4721
function filtre_nettoyer_titre_email_dist($titre) {
4722
	include_spip('inc/envoyer_mail');
4723
4724
	return nettoyer_titre_email($titre);
4725
}
4726
4727
/**
4728
 * Afficher le sélecteur de rubrique
4729
 *
4730
 * Il permet de placer un objet dans la hiérarchie des rubriques de SPIP
4731
 *
4732
 * @uses chercher_rubrique()
4733
 *
4734
 * @param string $titre
4735
 * @param int $id_objet
4736
 * @param int $id_parent
4737
 * @param string $objet
4738
 * @param int $id_secteur
4739
 * @param bool $restreint
4740
 * @param bool $actionable
4741
 *   true : fournit le selecteur dans un form directement postable
4742
 * @param bool $retour_sans_cadre
4743
 * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be string|array? 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...
4744
 */
4745
function filtre_chercher_rubrique_dist(
4746
	$titre,
4747
	$id_objet,
4748
	$id_parent,
4749
	$objet,
4750
	$id_secteur,
4751
	$restreint,
4752
	$actionable = false,
4753
	$retour_sans_cadre = false
4754
) {
4755
	include_spip('inc/filtres_ecrire');
4756
4757
	return chercher_rubrique($titre, $id_objet, $id_parent, $objet, $id_secteur, $restreint, $actionable,
4758
		$retour_sans_cadre);
4759
}
4760
4761
/**
4762
 * Rediriger une page suivant une autorisation,
4763
 * et ce, n'importe où dans un squelette, même dans les inclusions.
4764
 *
4765
 * En l'absence de redirection indiquée, la fonction redirige par défaut
4766
 * sur une 403 dans l'espace privé et 404 dans l'espace public.
4767
 *
4768
 * @example
4769
 *     ```
4770
 *     [(#AUTORISER{non}|sinon_interdire_acces)]
4771
 *     [(#AUTORISER{non}|sinon_interdire_acces{#URL_PAGE{login}, 401})]
4772
 *     ```
4773
 *
4774
 * @filtre
4775
 * @param bool $ok
4776
 *     Indique si l'on doit rediriger ou pas
4777
 * @param string $url
4778
 *     Adresse eventuelle vers laquelle rediriger
4779
 * @param int $statut
4780
 *     Statut HTML avec lequel on redirigera
4781
 * @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...
4782
 *     message d'erreur
4783
 * @return string|void
4784
 *     Chaîne vide si l'accès est autorisé
4785
 */
4786
function sinon_interdire_acces($ok = false, $url = '', $statut = 0, $message = null) {
4787
	if ($ok) {
4788
		return '';
4789
	}
4790
4791
	// Vider tous les tampons
4792
	$level = @ob_get_level();
4793
	while ($level--) {
4794
		@ob_end_clean();
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...
4795
	}
4796
4797
	include_spip('inc/headers');
4798
4799
	// S'il y a une URL, on redirige (si pas de statut, la fonction mettra 302 par défaut)
4800
	if ($url) {
4801
		redirige_par_entete($url, '', $statut);
4802
	}
4803
4804
	// ecriture simplifiee avec message en 3eme argument (= statut 403)
4805
	if (!is_numeric($statut) and is_null($message)) {
4806
		$message = $statut;
4807
		$statut = 0;
4808
	}
4809
	if (!$message) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $message of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
4810
		$message = '';
4811
	}
4812
	$statut = intval($statut);
4813
4814
	// Si on est dans l'espace privé, on génère du 403 Forbidden par defaut ou du 404
4815
	if (test_espace_prive()) {
4816
		if (!$statut or !in_array($statut, array(404, 403))) {
4817
			$statut = 403;
0 ignored issues
show
Unused Code introduced by
$statut is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
4818
		}
4819
		http_status(403);
4820
		$echec = charger_fonction('403', 'exec');
4821
		$echec($message);
4822
	} else {
4823
		// Sinon dans l'espace public on redirige vers une 404 par défaut, car elle toujours présente normalement
4824
		if (!$statut) {
4825
			$statut = 404;
4826
		}
4827
		// Dans tous les cas on modifie l'entité avec ce qui est demandé
4828
		http_status($statut);
4829
		// Si le statut est une erreur et qu'il n'y a pas de redirection on va chercher le squelette du même nom
4830
		if ($statut >= 400) {
4831
			echo recuperer_fond("$statut", array('erreur' => $message));
4832
		}
4833
	}
4834
4835
4836
	exit;
4837
}
4838
4839
/**
4840
 * Assurer le fonctionnement de |compacte meme sans l'extension compresseur
4841
 *
4842
 * @param string $source
4843
 * @param null|string $format
4844
 * @return string
4845
 */
4846
function filtre_compacte_dist($source, $format = null) {
4847
	if (function_exists('compacte')) {
4848
		return compacte($source, $format);
4849
	}
4850
4851
	return $source;
4852
}
4853
4854
4855
/**
4856
 * Afficher partiellement un mot de passe que l'on ne veut pas rendre lisible par un champ hidden
4857
 * @param string $passe
4858
 * @param bool $afficher_partiellement
4859
 * @param int|null $portion_pourcent
4860
 * @return string
4861
 */
4862
function spip_affiche_mot_de_passe_masque($passe, $afficher_partiellement = false, $portion_pourcent = null) {
4863
	$l = strlen($passe);
4864
4865
	if ($l<=8 or !$afficher_partiellement){
4866
		if (!$l) {
4867
			return ''; // montrer qu'il y a pas de mot de passe si il y en a pas
4868
		}
4869
		return str_pad('',$afficher_partiellement ? $l : 16,'*');
4870
	}
4871
4872
	if (is_null($portion_pourcent)) {
4873
		if (!defined('_SPIP_AFFICHE_MOT_DE_PASSE_MASQUE_PERCENT')) {
4874
			define('_SPIP_AFFICHE_MOT_DE_PASSE_MASQUE_PERCENT', 20); // 20%
4875
		}
4876
		$portion_pourcent = _SPIP_AFFICHE_MOT_DE_PASSE_MASQUE_PERCENT;
4877
	}
4878
	if ($portion_pourcent >= 100) {
4879
		return $passe;
4880
	}
4881
	$e = intval(ceil($l * $portion_pourcent / 100 / 2));
4882
	$e = max($e, 0);
4883
	$mid = str_pad('',$l-2*$e,'*');
4884
	if ($e>0 and strlen($mid)>8){
4885
		$mid = '***...***';
4886
	}
4887
	return substr($passe,0,$e) . $mid . ($e > 0 ? substr($passe,-$e) : '');
4888
}