Completed
Push — master ( 58c96b...0b76de )
by cam
04:07
created

filtres.php ➔ uniformiser_label()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
nc 4
nop 2
dl 0
loc 10
rs 9.9332
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/fonctions'); // 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_fichiers_fonctions(); // 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 s'il existe, sinon retourne une chaîne vide
114
 *
115
 * Fonction générique qui prend en argument l’objet (texte, etc) à modifier
116
 * et le nom du filtre.
117
 *
118
 * - À la différence de la fonction `filtrer()`, celle-ci ne lève
119
 *   pas d'erreur de squelettes si le filtre n'est pas trouvé.
120
 * - À la différence de la fonction `appliquer_si_filtre()` le contenu
121
 *   d'origine n'est pas retourné si le filtre est absent.
122
 *
123
 * Les arguments supplémentaires transmis à cette fonction sont utilisés
124
 * comme arguments pour le filtre appelé.
125
 *
126
 * @example
127
 *      ```
128
 *      [(#BALISE|appliquer_filtre{nom_du_filtre})]
129
 *      [(#BALISE|appliquer_filtre{nom_du_filtre, arg1, arg2, ...})]
130
 *
131
 *      // Applique le filtre minifier si on le trouve :
132
 *      // - Ne retourne rien si le filtre 'minifier' n'est pas trouvé
133
 *      [(#INCLURE{fichier.js}|appliquer_filtre{minifier, js})]
134
 *
135
 *      // - Retourne le contenu du fichier.js si le filtre n'est pas trouvé.
136
 *      [(#INCLURE{fichier.js}|appliquer_si_filtre{minifier, js})]
137
 *      ```
138
 *
139
 * @filtre
140
 * @see filtrer() Génère une erreur si le filtre est absent
141
 * @see appliquer_si_filtre() Proche : retourne le texte d'origine si le filtre est absent
142
 * @uses appliquer_filtre_sinon()
143
 *
144
 * @param mixed $arg
145
 *     Texte (le plus souvent) sur lequel appliquer le filtre
146
 * @param string $filtre
147
 *     Nom du filtre à appliquer
148
 * @return string
149
 *     Texte traité par le filtre si le filtre existe,
150
 *     Chaîne vide sinon.
151
 **/
152
function appliquer_filtre($arg, $filtre) {
153
	$args = func_get_args();
154
	return appliquer_filtre_sinon($arg, $filtre, $args, '');
155
}
156
157
/**
158
 * Applique un filtre s'il existe, sinon retourne le contenu d'origine sans modification
159
 *
160
 * Se référer à `appliquer_filtre()` pour les détails.
161
 *
162
 * @example
163
 *      ```
164
 *      [(#INCLURE{fichier.js}|appliquer_si_filtre{minifier, js})]
165
 *      ```
166
 * @filtre
167
 * @see appliquer_filtre() Proche : retourne vide si le filtre est absent
168
 * @uses appliquer_filtre_sinon()
169
 *
170
 * @param mixed $arg
171
 *     Texte (le plus souvent) sur lequel appliquer le filtre
172
 * @param string $filtre
173
 *     Nom du filtre à appliquer
174
 * @return string
175
 *     Texte traité par le filtre si le filtre existe,
176
 *     Texte d'origine sinon
177
 **/
178
function appliquer_si_filtre($arg, $filtre) {
179
	$args = func_get_args();
180
	return appliquer_filtre_sinon($arg, $filtre, $args, $arg);
181
}
182
183
/**
184
 * Retourne la version de SPIP
185
 *
186
 * Si l'on retrouve un numéro de révision GIT ou SVN, il est ajouté entre crochets.
187
 * Si effectivement le SPIP est installé par Git ou Svn, 'GIT' ou 'SVN' est ajouté avant sa révision.
188
 *
189
 * @global spip_version_affichee Contient la version de SPIP
190
 * @uses version_vcs_courante() Pour trouver le numéro de révision
191
 *
192
 * @return string
193
 *     Version de SPIP
194
 **/
195
function spip_version() {
196
	$version = $GLOBALS['spip_version_affichee'];
197
	if ($vcs_version = version_vcs_courante(_DIR_RACINE)) {
198
		$version .= " $vcs_version";
199
	}
200
201
	return $version;
202
}
203
204
/**
205
 * Retourne une courte description d’une révision VCS d’un répertoire
206
 *
207
 * @param string $dir Le répertoire à tester
208
 * @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...
209
 * @retun string|array|null
210
 *    - array|null si $raw = true,
211
 *    - string|null si $raw = false
212
 */
213
function version_vcs_courante($dir, $raw = false) {
214
	$desc = decrire_version_git($dir);
215
	if ($desc === null) {
216
		$desc = decrire_version_svn($dir);
217
	}
218
	if ($desc === null or $raw) {
219
		return $desc;
220
	}
221
	// affichage "GIT [master: abcdef]"
222
	$commit = isset($desc['commit_short']) ? $desc['commit_short'] : $desc['commit'];
223
	if ($desc['branch']) {
224
		$commit = $desc['branch'] . ': ' . $commit;
225
	}
226
	return "{$desc['vcs']} [$commit]";
227
}
228
229
/**
230
 * Retrouve un numéro de révision Git d'un répertoire
231
 *
232
 * @param string $dir Chemin du répertoire
233
 * @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...
234
 *      null si aucune info trouvée
235
 *      array ['branch' => xx, 'commit' => yy] sinon.
236
 **/
237
function decrire_version_git($dir) {
238
	if (!$dir) {
239
		$dir = '.';
240
	}
241
242
	// version installee par GIT
243
	if (lire_fichier($dir . '/.git/HEAD', $c)) {
244
		$currentHead = trim(substr($c, 4));
245
		if (lire_fichier($dir . '/.git/' . $currentHead, $hash)) {
246
			return [
247
				'vcs' => 'GIT',
248
				'branch' => basename($currentHead),
249
				'commit' => trim($hash),
250
				'commit_short' => substr(trim($hash), 0, 8),
251
			];
252
		}
253
	}
254
255
	return null;
256
}
257
258
259
/**
260
 * Retrouve un numéro de révision Svn d'un répertoire
261
 *
262
 * @param string $dir Chemin du répertoire
263
 * @return array|null
264
 *      null si aucune info trouvée
265
 *      array ['commit' => yy, 'date' => xx, 'author' => xx] sinon.
266
 **/
267
function decrire_version_svn($dir) {
268
	if (!$dir) {
269
		$dir = '.';
270
	}
271
	// version installee par SVN
272
	if (file_exists($dir . '/.svn/wc.db') && class_exists('SQLite3')) {
273
		$db = new SQLite3($dir . '/.svn/wc.db');
274
		$result = $db->query('SELECT changed_revision FROM nodes WHERE local_relpath = "" LIMIT 1');
275
		if ($result) {
276
			$row = $result->fetchArray();
277
			if ($row['changed_revision'] != "") {
278
				return [
279
					'vcs' => 'SVN',
280
					'branch' => '',
281
					'commit' => $row['changed_revision'],
282
				];
283
			}
284
		}
285
	}
286
	return null;
287
}
288
289
/**
290
 * Retrouve un numéro de révision SVN d'un répertoire
291
 *
292
 * Mention de la révision SVN courante d'un répertoire
293
 * /!\ Retourne un nombre négatif si on est sur .svn
294
 *
295
 * @deprecated Utiliser version_vcs_courante()
296
 * @param string $dir Chemin du répertoire
297
 * @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...
298
 *
299
 *     - 0 si aucune info trouvée
300
 *     - -NN (entier) si info trouvée par .svn/wc.db
301
 *
302
 **/
303
function version_svn_courante($dir) {
304
	if ($desc = decrire_version_svn($dir)) {
305
		return -$desc['commit'];
306
	}
307
	return 0;
308
}
309
310
// La matrice est necessaire pour ne filtrer _que_ des fonctions definies dans filtres_images
311
// et laisser passer les fonctions personnelles baptisees image_...
312
$GLOBALS['spip_matrice']['image_graver'] = true;//'inc/filtres_images_mini.php';
313
$GLOBALS['spip_matrice']['image_select'] = true;//'inc/filtres_images_mini.php';
314
$GLOBALS['spip_matrice']['image_reduire'] = true;//'inc/filtres_images_mini.php';
315
$GLOBALS['spip_matrice']['image_reduire_par'] = true;//'inc/filtres_images_mini.php';
316
$GLOBALS['spip_matrice']['image_passe_partout'] = true;//'inc/filtres_images_mini.php';
317
318
$GLOBALS['spip_matrice']['couleur_html_to_hex'] = 'inc/filtres_images_mini.php';
319
$GLOBALS['spip_matrice']['couleur_hex_to_hsl'] = 'inc/filtres_images_mini.php';
320
$GLOBALS['spip_matrice']['couleur_hex_to_rgb'] = 'inc/filtres_images_mini.php';
321
$GLOBALS['spip_matrice']['couleur_foncer'] = 'inc/filtres_images_mini.php';
322
$GLOBALS['spip_matrice']['couleur_eclaircir'] = 'inc/filtres_images_mini.php';
323
324
// ou pour inclure un script au moment ou l'on cherche le filtre
325
$GLOBALS['spip_matrice']['filtre_image_dist'] = 'inc/filtres_mime.php';
326
$GLOBALS['spip_matrice']['filtre_audio_dist'] = 'inc/filtres_mime.php';
327
$GLOBALS['spip_matrice']['filtre_video_dist'] = 'inc/filtres_mime.php';
328
$GLOBALS['spip_matrice']['filtre_application_dist'] = 'inc/filtres_mime.php';
329
$GLOBALS['spip_matrice']['filtre_message_dist'] = 'inc/filtres_mime.php';
330
$GLOBALS['spip_matrice']['filtre_multipart_dist'] = 'inc/filtres_mime.php';
331
$GLOBALS['spip_matrice']['filtre_text_dist'] = 'inc/filtres_mime.php';
332
$GLOBALS['spip_matrice']['filtre_text_csv_dist'] = 'inc/filtres_mime.php';
333
$GLOBALS['spip_matrice']['filtre_text_html_dist'] = 'inc/filtres_mime.php';
334
$GLOBALS['spip_matrice']['filtre_audio_x_pn_realaudio'] = 'inc/filtres_mime.php';
335
336
337
/**
338
 * Charge et exécute un filtre (graphique ou non)
339
 *
340
 * Recherche la fonction prévue pour un filtre (qui peut être un filtre graphique `image_*`)
341
 * et l'exécute avec les arguments transmis à la fonction, obtenus avec `func_get_args()`
342
 *
343
 * @api
344
 * @uses image_filtrer() Pour un filtre image
345
 * @uses chercher_filtre() Pour un autre filtre
346
 *
347
 * @param string $filtre
348
 *     Nom du filtre à appliquer
349
 * @return string
350
 *     Code HTML retourné par le filtre
351
 **/
352
function filtrer($filtre) {
353
	$tous = func_get_args();
354
	if (trouver_filtre_matrice($filtre) and substr($filtre, 0, 6) == 'image_') {
355
		return image_filtrer($tous);
356
	} elseif ($f = chercher_filtre($filtre)) {
357
		array_shift($tous);
358
		return call_user_func_array($f, $tous);
359
	} else {
360
		// le filtre n'existe pas, on provoque une erreur
361
		$msg = array('zbug_erreur_filtre', array('filtre' => texte_script($filtre)));
362
		erreur_squelette($msg);
363
		return '';
364
	}
365
}
366
367
/**
368
 * Cherche un filtre spécial indiqué dans la globale `spip_matrice`
369
 * et charge le fichier éventuellement associé contenant le filtre.
370
 *
371
 * Les filtres d'images par exemple sont déclarés de la sorte, tel que :
372
 * ```
373
 * $GLOBALS['spip_matrice']['image_reduire'] = true;
374
 * $GLOBALS['spip_matrice']['image_monochrome'] = 'filtres/images_complements.php';
375
 * ```
376
 *
377
 * @param string $filtre
378
 * @return bool true si on trouve le filtre dans la matrice, false sinon.
379
 */
380
function trouver_filtre_matrice($filtre) {
381
	if (isset($GLOBALS['spip_matrice'][$filtre]) and is_string($f = $GLOBALS['spip_matrice'][$filtre])) {
382
		find_in_path($f, '', true);
383
		$GLOBALS['spip_matrice'][$filtre] = true;
384
	}
385
	return !empty($GLOBALS['spip_matrice'][$filtre]);
386
}
387
388
389
/**
390
 * Filtre `set` qui sauve la valeur en entrée dans une variable
391
 *
392
 * La valeur pourra être retrouvée avec `#GET{variable}`.
393
 *
394
 * @example
395
 *     `[(#CALCUL|set{toto})]` enregistre le résultat de `#CALCUL`
396
 *     dans la variable `toto` et renvoie vide.
397
 *     C'est équivalent à `[(#SET{toto, #CALCUL})]` dans ce cas.
398
 *     `#GET{toto}` retourne la valeur sauvegardée.
399
 *
400
 * @example
401
 *     `[(#CALCUL|set{toto,1})]` enregistre le résultat de `#CALCUL`
402
 *      dans la variable toto et renvoie la valeur. Cela permet d'utiliser
403
 *      d'autres filtres ensuite. `#GET{toto}` retourne la valeur.
404
 *
405
 * @filtre
406
 * @param array $Pile Pile de données
407
 * @param mixed $val Valeur à sauver
408
 * @param string $key Clé d'enregistrement
409
 * @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...
410
 * @return mixed
411
 */
412
function filtre_set(&$Pile, $val, $key, $continue = null) {
413
	$Pile['vars'][$key] = $val;
414
	return $continue ? $val : '';
415
}
416
417
/**
418
 * Filtre `setenv` qui enregistre une valeur dans l'environnement du squelette
419
 *
420
 * La valeur pourra être retrouvée avec `#ENV{variable}`.
421
 * 
422
 * @example
423
 *     `[(#CALCUL|setenv{toto})]` enregistre le résultat de `#CALCUL`
424
 *      dans l'environnement toto et renvoie vide.
425
 *      `#ENV{toto}` retourne la valeur.
426
 *
427
 *      `[(#CALCUL|setenv{toto,1})]` enregistre le résultat de `#CALCUL`
428
 *      dans l'environnement toto et renvoie la valeur.
429
 *      `#ENV{toto}` retourne la valeur.
430
 *
431
 * @filtre
432
 *
433
 * @param array $Pile
434
 * @param mixed $val Valeur à enregistrer
435
 * @param mixed $key Nom de la variable
436
 * @param null|mixed $continue Si présent, retourne la valeur en sortie
437
 * @return string|mixed Retourne `$val` si `$continue` présent, sinon ''.
438
 */
439
function filtre_setenv(&$Pile, $val, $key, $continue = null) {
440
	$Pile[0][$key] = $val;
441
	return $continue ? $val : '';
442
}
443
444
/**
445
 * @param array $Pile
446
 * @param array|string $keys
447
 * @return string
448
 */
449
function filtre_sanitize_env(&$Pile, $keys) {
450
	$Pile[0] = spip_sanitize_from_request($Pile[0], $keys);
451
	return '';
452
}
453
454
455
/**
456
 * Filtre `debug` qui affiche un debug de la valeur en entrée
457
 *
458
 * Log la valeur dans `debug.log` et l'affiche si on est webmestre.
459
 *
460
 * @example
461
 *     `[(#TRUC|debug)]` affiche et log la valeur de `#TRUC`
462
 * @example
463
 *     `[(#TRUC|debug{avant}|calcul|debug{apres}|etc)]`
464
 *     affiche la valeur de `#TRUC` avant et après le calcul,
465
 *     en précisant "avant" et "apres".
466
 *
467
 * @filtre
468
 * @link https://www.spip.net/5695
469
 * @param mixed $val La valeur à debugguer
470
 * @param mixed|null $key Clé pour s'y retrouver
471
 * @return mixed Retourne la valeur (sans la modifier).
472
 */
473
function filtre_debug($val, $key = null) {
474
	$debug = (
475
		is_null($key) ? '' : (var_export($key, true) . " = ")
476
		) . var_export($val, true);
477
478
	include_spip('inc/autoriser');
479
	if (autoriser('webmestre')) {
480
		echo "<div class='spip_debug'>\n", $debug, "</div>\n";
481
	}
482
483
	spip_log($debug, 'debug');
484
485
	return $val;
486
}
487
488
489
/**
490
 * Exécute un filtre image
491
 *
492
 * Fonction générique d'entrée des filtres images.
493
 * Accepte en entrée :
494
 *
495
 * - un texte complet,
496
 * - un img-log (produit par #LOGO_XX),
497
 * - un tag `<img ...>` complet,
498
 * - un nom de fichier *local* (passer le filtre `|copie_locale` si on veut
499
 *   l'appliquer à un document distant).
500
 *
501
 * Applique le filtre demande à chacune des occurrences
502
 *
503
 * @param array $args
504
 *     Liste des arguments :
505
 *
506
 *     - le premier est le nom du filtre image à appliquer
507
 *     - le second est le texte sur lequel on applique le filtre
508
 *     - les suivants sont les arguments du filtre image souhaité.
509
 * @return string
510
 *     Texte qui a reçu les filtres
511
 **/
512
function image_filtrer($args) {
513
	$filtre = array_shift($args); # enlever $filtre
514
	$texte = array_shift($args);
515
	if (!strlen($texte)) {
516
		return;
517
	}
518
	find_in_path('filtres_images_mini.php', 'inc/', true);
519
	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...
520
	// Cas du nom de fichier local
521
	$is_file = trim($texte);
522
	if (strpos(substr($is_file, strlen(_DIR_RACINE)), '..') !== false
523
		  or strpbrk($is_file, "<>\n\r\t") !== false
524
		  or strpos($is_file, '/') === 0
525
	) {
526
		$is_file = false;
527
	}
528
	if ($is_file) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $is_file of type false|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false 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...
529
		$is_local_file = function($path) {
530
			if (strpos($path, "?") !== false) {
531
				$path = supprimer_timestamp($path);
532
				// remove ?24px added by find_in_theme on .svg files
533
				$path = preg_replace(",\?[[:digit:]]+(px)$,", "", $path);
534
			}
535
			return file_exists($path);
536
		};
537
		if ($is_local_file($is_file) or tester_url_absolue($is_file)) {
538
			array_unshift($args, "<img src='$is_file' />");
539
			$res = call_user_func_array($filtre, $args);
540
			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...
541
			return $res;
542
		}
543
	}
544
545
	// Cas general : trier toutes les images, avec eventuellement leur <span>
546
	if (preg_match_all(
547
		',(<([a-z]+) [^<>]*spip_documents[^<>]*>)?\s*(<img\s.*>),UimsS',
548
		$texte, $tags, PREG_SET_ORDER)) {
549
		foreach ($tags as $tag) {
550
			$class = extraire_attribut($tag[3], 'class');
551
			if (!$class or
552
				(strpos($class, 'filtre_inactif') === false
553
					// compat historique a virer en 3.2
554
					and strpos($class, 'no_image_filtrer') === false)
555
			) {
556
				array_unshift($args, $tag[3]);
557
				if ($reduit = call_user_func_array($filtre, $args)) {
558
					// En cas de span spip_documents, modifier le style=...width:
559
					if ($tag[1]) {
560
						$w = extraire_attribut($reduit, 'width');
561
						if (!$w and preg_match(",width:\s*(\d+)px,S", extraire_attribut($reduit, 'style'), $regs)) {
562
							$w = $regs[1];
563
						}
564
						if ($w and ($style = extraire_attribut($tag[1], 'style'))) {
565
							$style = preg_replace(",width:\s*\d+px,S", "width:${w}px", $style);
566
							$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 565 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...
567
							$texte = str_replace($tag[1], $replace, $texte);
568
						}
569
					}
570
					// traiter aussi un eventuel mouseover
571
					if ($mouseover = extraire_attribut($reduit, 'onmouseover')) {
572
						if (preg_match(",this[.]src=['\"]([^'\"]+)['\"],ims", $mouseover, $match)) {
573
							$srcover = $match[1];
574
							array_shift($args);
575
							array_unshift($args, "<img src='" . $match[1] . "' />");
576
							$srcover_filter = call_user_func_array($filtre, $args);
577
							$srcover_filter = extraire_attribut($srcover_filter, 'src');
578
							$reduit = str_replace($srcover, $srcover_filter, $reduit);
579
						}
580
					}
581
					$texte = str_replace($tag[3], $reduit, $texte);
582
				}
583
				array_shift($args);
584
			}
585
		}
586
	}
587
	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...
588
	return $texte;
589
}
590
591
/**
592
 * Retourne les tailles d'une image
593
 *
594
 * Pour les filtres `largeur` et `hauteur`
595
 *
596
 * @param string $img
597
 *     Balise HTML `<img ... />` ou chemin de l'image (qui peut être une URL distante).
598
 * @return array
599
 *     Liste (hauteur, largeur) en pixels
600
 **/
601
function taille_image($img, $force_refresh = false) {
602
603
	static $largeur_img = array(), $hauteur_img = array();
604
	$srcWidth = 0;
605
	$srcHeight = 0;
606
607
	$src = extraire_attribut($img, 'src');
608
609
	if (!$src) {
610
		$src = $img;
611
	} else {
612
		$srcWidth = extraire_attribut($img, 'width');
613
		$srcHeight = extraire_attribut($img, 'height');
614
	}
615
616
	// ne jamais operer directement sur une image distante pour des raisons de perfo
617
	// la copie locale a toutes les chances d'etre la ou de resservir
618
	if (tester_url_absolue($src)) {
0 ignored issues
show
Bug introduced by
It seems like $src defined by extraire_attribut($img, 'src') on line 607 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...
619
		include_spip('inc/distant');
620
		$fichier = copie_locale($src);
0 ignored issues
show
Bug introduced by
It seems like $src defined by extraire_attribut($img, 'src') on line 607 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...
621
		$src = $fichier ? _DIR_RACINE . $fichier : $src;
622
	}
623 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...
624
		$src = substr($src, 0, $p);
625
	}
626
627
	$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...
628
	if (isset($largeur_img[$src]) and !$force_refresh) {
629
		$srcWidth = $largeur_img[$src];
630
	}
631
	if (isset($hauteur_img[$src]) and !$force_refresh) {
632
		$srcHeight = $hauteur_img[$src];
633
	}
634
	if (!$srcWidth or !$srcHeight) {
635
636
		if (file_exists($src)
637
			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...
638
		) {
639
			if (!$srcWidth) {
640
				$largeur_img[$src] = $srcWidth = $srcsize[0];
641
			}
642
			if (!$srcHeight) {
643
				$hauteur_img[$src] = $srcHeight = $srcsize[1];
644
			}
645
		}
646
		elseif(strpos($src, "<svg") !== false) {
647
			include_spip('inc/svg');
648
			if ($attrs = svg_lire_attributs($src)){
0 ignored issues
show
Bug introduced by
It seems like $src can also be of type array; however, svg_lire_attributs() 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...
649
				list($width, $height, $viewbox) = svg_getimagesize_from_attr($attrs);
0 ignored issues
show
Unused Code introduced by
The assignment to $viewbox is unused. Consider omitting it like so list($first,,$third).

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

Consider the following code example.

<?php

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

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

print $a . " - " . $c;

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

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
650
				if (!$srcWidth){
651
					$largeur_img[$src] = $srcWidth = $width;
652
				}
653
				if (!$srcHeight){
654
					$hauteur_img[$src] = $srcHeight = $height;
655
				}
656
			}
657
		}
658
		// $src peut etre une reference a une image temporaire dont a n'a que le log .src
659
		// on s'y refere, l'image sera reconstruite en temps utile si necessaire
660
		elseif (@file_exists($f = "$src.src")
661
			and lire_fichier($f, $valeurs)
662
			and $valeurs = unserialize($valeurs)
663
		) {
664
			if (!$srcWidth) {
665
				$largeur_img[$src] = $srcWidth = $valeurs["largeur_dest"];
666
			}
667
			if (!$srcHeight) {
668
				$hauteur_img[$src] = $srcHeight = $valeurs["hauteur_dest"];
669
			}
670
		}
671
	}
672
673
	return array($srcHeight, $srcWidth);
674
}
675
676
677
/**
678
 * Retourne la largeur d'une image
679
 *
680
 * @filtre
681
 * @link https://www.spip.net/4296
682
 * @uses taille_image()
683
 * @see  hauteur()
684
 *
685
 * @param string $img
686
 *     Balise HTML `<img ... />` ou chemin de l'image (qui peut être une URL distante).
687
 * @return int|null
688
 *     Largeur en pixels, NULL ou 0 si aucune image.
689
 **/
690
function largeur($img) {
691
	if (!$img) {
692
		return;
693
	}
694
	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...
695
696
	return $l;
697
}
698
699
/**
700
 * Retourne la hauteur d'une image
701
 *
702
 * @filtre
703
 * @link https://www.spip.net/4291
704
 * @uses taille_image()
705
 * @see  largeur()
706
 *
707
 * @param string $img
708
 *     Balise HTML `<img ... />` ou chemin de l'image (qui peut être une URL distante).
709
 * @return int|null
710
 *     Hauteur en pixels, NULL ou 0 si aucune image.
711
 **/
712
function hauteur($img) {
713
	if (!$img) {
714
		return;
715
	}
716
	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...
717
718
	return $h;
719
}
720
721
722
/**
723
 * Échappement des entités HTML avec correction des entités « brutes »
724
 *
725
 * Ces entités peuvent être générées par les butineurs lorsqu'on rentre des
726
 * caractères n'appartenant pas au charset de la page [iso-8859-1 par défaut]
727
 *
728
 * Attention on limite cette correction aux caracteres « hauts » (en fait > 99
729
 * pour aller plus vite que le > 127 qui serait logique), de manière à
730
 * préserver des eéhappements de caractères « bas » (par exemple `[` ou `"`)
731
 * et au cas particulier de `&amp;` qui devient `&amp;amp;` dans les URL
732
 *
733
 * @see corriger_toutes_entites_html()
734
 * @param string $texte
735
 * @return string
736
 **/
737
function corriger_entites_html($texte) {
738
	if (strpos($texte, '&amp;') === false) {
739
		return $texte;
740
	}
741
742
	return preg_replace(',&amp;(#[0-9][0-9][0-9]+;|amp;),iS', '&\1', $texte);
743
}
744
745
/**
746
 * Échappement des entités HTML avec correction des entités « brutes » ainsi
747
 * que les `&amp;eacute;` en `&eacute;`
748
 *
749
 * Identique à `corriger_entites_html()` en corrigeant aussi les
750
 * `&amp;eacute;` en `&eacute;`
751
 *
752
 * @see corriger_entites_html()
753
 * @param string $texte
754
 * @return string
755
 **/
756
function corriger_toutes_entites_html($texte) {
757
	if (strpos($texte, '&amp;') === false) {
758
		return $texte;
759
	}
760
761
	return preg_replace(',&amp;(#?[a-z0-9]+;),iS', '&\1', $texte);
762
}
763
764
/**
765
 * Échappe les `&` en `&amp;`
766
 *
767
 * @param string $texte
768
 * @return string
769
 **/
770
function proteger_amp($texte) {
771
	return str_replace('&', '&amp;', $texte);
772
}
773
774
775
/**
776
 * Échappe en entités HTML certains caractères d'un texte
777
 *
778
 * Traduira un code HTML en transformant en entités HTML les caractères
779
 * en dehors du charset de la page ainsi que les `"`, `<` et `>`.
780
 *
781
 * Ceci permet d’insérer le texte d’une balise dans un `<textarea> </textarea>`
782
 * sans dommages.
783
 *
784
 * @filtre
785
 * @link https://www.spip.net/4280
786
 *
787
 * @uses echappe_html()
788
 * @uses echappe_retour()
789
 * @uses proteger_amp()
790
 * @uses corriger_entites_html()
791
 * @uses corriger_toutes_entites_html()
792
 *
793
 * @param string $texte
794
 *   chaine a echapper
795
 * @param bool $tout
796
 *   corriger toutes les `&amp;xx;` en `&xx;`
797
 * @param bool $quote
798
 *   Échapper aussi les simples quotes en `&#039;`
799
 * @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...
800
 */
801
function entites_html($texte, $tout = false, $quote = true) {
802 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...
803
		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...
804
	) {
805
		return $texte;
806
	}
807
	include_spip('inc/texte');
808
	$flags = ($quote ? ENT_QUOTES : ENT_NOQUOTES);
809
	$flags |= ENT_HTML401;
810
	$texte = spip_htmlspecialchars(echappe_retour(echappe_html($texte, '', true), '', 'proteger_amp'), $flags);
811
	if ($tout) {
812
		return corriger_toutes_entites_html($texte);
813
	} else {
814
		return corriger_entites_html($texte);
815
	}
816
}
817
818
/**
819
 * Convertit les caractères spéciaux HTML dans le charset du site.
820
 *
821
 * @exemple
822
 *     Si le charset de votre site est `utf-8`, `&eacute;` ou `&#233;`
823
 *     sera transformé en `é`
824
 *
825
 * @filtre
826
 * @link https://www.spip.net/5513
827
 *
828
 * @param string $texte
829
 *     Texte à convertir
830
 * @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...
831
 *     Texte converti
832
 **/
833
function filtrer_entites($texte) {
834
	if (strpos($texte, '&') === false) {
835
		return $texte;
836
	}
837
	// filtrer
838
	$texte = html2unicode($texte);
839
	// remettre le tout dans le charset cible
840
	$texte = unicode2charset($texte);
841
	// cas particulier des " et ' qu'il faut filtrer aussi
842
	// (on le faisait deja avec un &quot;)
843 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...
844
		$texte = str_replace(array("&#039;", "&#39;", "&#034;", "&#34;"), array("'", "'", '"', '"'), $texte);
845
	}
846
847
	return $texte;
848
}
849
850
851
if (!function_exists('filtre_filtrer_entites_dist')) {
852
	/**
853
	 * Version sécurisée de filtrer_entites
854
	 * 
855
	 * @uses interdire_scripts()
856
	 * @uses filtrer_entites()
857
	 * 
858
	 * @param string $t
859
	 * @return string
860
	 */
861
	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 (L659-662) 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...
862
		include_spip('inc/texte');
863
		return interdire_scripts(filtrer_entites($t));
864
	}
865
}
866
867
868
/**
869
 * Supprime des caractères illégaux
870
 *
871
 * Remplace les caractères de controle par le caractère `-`
872
 *
873
 * @link http://www.w3.org/TR/REC-xml/#charsets
874
 *
875
 * @param string|array $texte
876
 * @return string|array
877
 **/
878
function supprimer_caracteres_illegaux($texte) {
879
	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";
880
	static $to = null;
881
882
	if (is_array($texte)) {
883
		return array_map('supprimer_caracteres_illegaux', $texte);
884
	}
885
886
	if (!$to) {
887
		$to = str_repeat('-', strlen($from));
888
	}
889
890
	return strtr($texte, $from, $to);
891
}
892
893
/**
894
 * Correction de caractères
895
 *
896
 * Supprimer les caracteres windows non conformes et les caracteres de controle illégaux
897
 *
898
 * @param string|array $texte
899
 * @return string|array
900
 **/
901
function corriger_caracteres($texte) {
902
	$texte = corriger_caracteres_windows($texte);
903
	$texte = supprimer_caracteres_illegaux($texte);
904
905
	return $texte;
906
}
907
908
/**
909
 * Encode du HTML pour transmission XML notamment dans les flux RSS
910
 *
911
 * Ce filtre transforme les liens en liens absolus, importe les entitées html et échappe les tags html.
912
 *
913
 * @filtre
914
 * @link https://www.spip.net/4287
915
 *
916
 * @param string $texte
917
 *     Texte à transformer
918
 * @return string
919
 *     Texte encodé pour XML
920
 */
921
function texte_backend($texte) {
922
923
	static $apostrophe = array("&#8217;", "'"); # n'allouer qu'une fois
924
925
	// si on a des liens ou des images, les passer en absolu
926
	$texte = liens_absolus($texte);
927
928
	// echapper les tags &gt; &lt;
929
	$texte = preg_replace(',&(gt|lt);,S', '&amp;\1;', $texte);
930
931
	// importer les &eacute;
932
	$texte = filtrer_entites($texte);
933
934
	// " -> &quot; et tout ce genre de choses
935
	$u = $GLOBALS['meta']['pcre_u'];
936
	$texte = str_replace("&nbsp;", " ", $texte);
937
	$texte = preg_replace('/\s{2,}/S' . $u, " ", $texte);
938
	// ne pas echapper les sinqle quotes car certains outils de syndication gerent mal
939
	$texte = entites_html($texte, false, false);
940
	// mais bien echapper les double quotes !
941
	$texte = str_replace('"', '&#034;', $texte);
942
943
	// verifier le charset
944
	$texte = charset2unicode($texte);
945
946
	// Caracteres problematiques en iso-latin 1
947
	if (isset($GLOBALS['meta']['charset']) and $GLOBALS['meta']['charset'] == 'iso-8859-1') {
948
		$texte = str_replace(chr(156), '&#156;', $texte);
949
		$texte = str_replace(chr(140), '&#140;', $texte);
950
		$texte = str_replace(chr(159), '&#159;', $texte);
951
	}
952
953
	// l'apostrophe curly pose probleme a certains lecteure de RSS
954
	// et le caractere apostrophe alourdit les squelettes avec PHP
955
	// ==> on les remplace par l'entite HTML
956
	return str_replace($apostrophe, "'", $texte);
957
}
958
959
/**
960
 * Encode et quote du HTML pour transmission XML notamment dans les flux RSS
961
 *
962
 * Comme texte_backend(), mais avec addslashes final pour squelettes avec PHP (rss)
963
 *
964
 * @uses texte_backend()
965
 * @filtre
966
 *
967
 * @param string $texte
968
 *     Texte à transformer
969
 * @return string
970
 *     Texte encodé et quote pour XML
971
 */
972
function texte_backendq($texte) {
973
	return addslashes(texte_backend($texte));
974
}
975
976
977
/**
978
 * Enlève un numéro préfixant un texte
979
 *
980
 * Supprime `10. ` dans la chaine `10. Titre`
981
 *
982
 * @filtre
983
 * @link https://www.spip.net/4314
984
 * @see recuperer_numero() Pour obtenir le numéro
985
 * @example
986
 *     ```
987
 *     [<h1>(#TITRE|supprimer_numero)</h1>]
988
 *     ```
989
 *
990
 * @param string $texte
991
 *     Texte
992
 * @return int|string
993
 *     Numéro de titre, sinon chaîne vide
994
 **/
995
function supprimer_numero($texte) {
996
	return preg_replace(
997
		",^[[:space:]]*([0-9]+)([.)]|" . chr(194) . '?' . chr(176) . ")[[:space:]]+,S",
998
		"", $texte);
999
}
1000
1001
/**
1002
 * Récupère un numéro préfixant un texte
1003
 *
1004
 * Récupère le numéro `10` dans la chaine `10. Titre`
1005
 *
1006
 * @filtre
1007
 * @link https://www.spip.net/5514
1008
 * @see supprimer_numero() Pour supprimer le numéro
1009
 * @see balise_RANG_dist() Pour obtenir un numéro de titre
1010
 * @example
1011
 *     ```
1012
 *     [(#TITRE|recuperer_numero)]
1013
 *     ```
1014
 *
1015
 * @param string $texte
1016
 *     Texte
1017
 * @return int|string
1018
 *     Numéro de titre, sinon chaîne vide
1019
 **/
1020
function recuperer_numero($texte) {
1021
	if (preg_match(
1022
		",^[[:space:]]*([0-9]+)([.)]|" . chr(194) . '?' . chr(176) . ")[[:space:]]+,S",
1023
		$texte, $regs)) {
1024
		return strval($regs[1]);
1025
	} else {
1026
		return '';
1027
	}
1028
}
1029
1030
/**
1031
 * Suppression basique et brutale de tous les tags
1032
 *
1033
 * Supprime tous les tags `<...>`.
1034
 * Utilisé fréquemment pour écrire des RSS.
1035
 *
1036
 * @filtre
1037
 * @link https://www.spip.net/4315
1038
 * @example
1039
 *     ```
1040
 *     <title>[(#TITRE|supprimer_tags|texte_backend)]</title>
1041
 *     ```
1042
 *
1043
 * @note
1044
 *     Ce filtre supprime aussi les signes inférieurs `<` rencontrés.
1045
 *
1046
 * @param string $texte
1047
 *     Texte à échapper
1048
 * @param string $rempl
1049
 *     Inutilisé.
1050
 * @return string
1051
 *     Texte converti
1052
 **/
1053
function supprimer_tags($texte, $rempl = "") {
1054
	$texte = preg_replace(",<(!--|\w|/|!\[endif|!\[if)[^>]*>,US", $rempl, $texte);
1055
	// ne pas oublier un < final non ferme car coupe
1056
	$texte = preg_replace(",<(!--|\w|/).*$,US", $rempl, $texte);
1057
	// mais qui peut aussi etre un simple signe plus petit que
1058
	$texte = str_replace('<', '&lt;', $texte);
1059
1060
	return $texte;
1061
}
1062
1063
/**
1064
 * Convertit les chevrons de tag en version lisible en HTML
1065
 *
1066
 * Transforme les chevrons de tag `<...>` en entité HTML.
1067
 *
1068
 * @filtre
1069
 * @link https://www.spip.net/5515
1070
 * @example
1071
 *     ```
1072
 *     <pre>[(#TEXTE|echapper_tags)]</pre>
1073
 *     ```
1074
 *
1075
 * @param string $texte
1076
 *     Texte à échapper
1077
 * @param string $rempl
1078
 *     Inutilisé.
1079
 * @return string
1080
 *     Texte converti
1081
 **/
1082
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...
1083
	$texte = preg_replace("/<([^>]*)>/", "&lt;\\1&gt;", $texte);
1084
1085
	return $texte;
1086
}
1087
1088
/**
1089
 * Convertit un texte HTML en texte brut
1090
 *
1091
 * Enlève les tags d'un code HTML, élimine les doubles espaces.
1092
 *
1093
 * @filtre
1094
 * @link https://www.spip.net/4317
1095
 * @example
1096
 *     ```
1097
 *     <title>[(#TITRE|textebrut) - ][(#NOM_SITE_SPIP|textebrut)]</title>
1098
 *     ```
1099
 *
1100
 * @param string $texte
1101
 *     Texte à convertir
1102
 * @return string
1103
 *     Texte converti
1104
 **/
1105
function textebrut($texte) {
1106
	$u = $GLOBALS['meta']['pcre_u'];
1107
	$texte = preg_replace('/\s+/S' . $u, " ", $texte);
1108
	$texte = preg_replace("/<(p|br)( [^>]*)?" . ">/iS", "\n\n", $texte);
1109
	$texte = preg_replace("/^\n+/", "", $texte);
1110
	$texte = preg_replace("/\n+$/", "", $texte);
1111
	$texte = preg_replace("/\n +/", "\n", $texte);
1112
	$texte = supprimer_tags($texte);
1113
	$texte = preg_replace("/(&nbsp;| )+/S", " ", $texte);
1114
	// nettoyer l'apostrophe curly qui pose probleme a certains rss-readers, lecteurs de mail...
1115
	$texte = str_replace("&#8217;", "'", $texte);
1116
1117
	return $texte;
1118
}
1119
1120
1121
/**
1122
 * Remplace les liens SPIP en liens ouvrant dans une nouvelle fenetre (target=blank)
1123
 *
1124
 * @filtre
1125
 * @link https://www.spip.net/4297
1126
 *
1127
 * @param string $texte
1128
 *     Texte avec des liens
1129
 * @return string
1130
 *     Texte avec liens ouvrants
1131
 **/
1132
function liens_ouvrants($texte) {
1133
	if (preg_match_all(",(<a\s+[^>]*https?://[^>]*class=[\"']spip_(out|url)\b[^>]+>),imsS",
1134
		$texte, $liens, PREG_PATTERN_ORDER)) {
1135
		foreach ($liens[0] as $a) {
1136
			$rel = 'noopener noreferrer ' . extraire_attribut($a, 'rel');
1137
			$ablank = inserer_attribut($a, 'rel', $rel);
1138
			$ablank = inserer_attribut($ablank, 'target', '_blank');
1139
			$texte = str_replace($a, $ablank, $texte);
1140
		}
1141
	}
1142
1143
	return $texte;
1144
}
1145
1146
/**
1147
 * Ajouter un attribut rel="nofollow" sur tous les liens d'un texte
1148
 *
1149
 * @param string $texte
1150
 * @return string
1151
 */
1152
function liens_nofollow($texte) {
1153
	if (stripos($texte, "<a") === false) {
1154
		return $texte;
1155
	}
1156
1157
	if (preg_match_all(",<a\b[^>]*>,UimsS", $texte, $regs, PREG_PATTERN_ORDER)) {
1158
		foreach ($regs[0] as $a) {
1159
			$rel = extraire_attribut($a, "rel");
1160
			if (strpos($rel, "nofollow") === false) {
1161
				$rel = "nofollow" . ($rel ? " $rel" : "");
1162
				$anofollow = inserer_attribut($a, "rel", $rel);
1163
				$texte = str_replace($a, $anofollow, $texte);
1164
			}
1165
		}
1166
	}
1167
1168
	return $texte;
1169
}
1170
1171
/**
1172
 * Transforme les sauts de paragraphe HTML `p` en simples passages à la ligne `br`
1173
 *
1174
 * @filtre
1175
 * @link https://www.spip.net/4308
1176
 * @example
1177
 *     ```
1178
 *     [<div>(#DESCRIPTIF|PtoBR)[(#NOTES|PtoBR)]</div>]
1179
 *     ```
1180
 *
1181
 * @param string $texte
1182
 *     Texte à transformer
1183
 * @return string
1184
 *     Texte sans paraghaphes
1185
 **/
1186
function PtoBR($texte) {
1187
	$u = $GLOBALS['meta']['pcre_u'];
1188
	$texte = preg_replace("@</p>@iS", "\n", $texte);
1189
	$texte = preg_replace("@<p\b.*>@UiS", "<br />", $texte);
1190
	$texte = preg_replace("@^\s*<br />@S" . $u, "", $texte);
1191
1192
	return $texte;
1193
}
1194
1195
1196
/**
1197
 * Assure qu'un texte ne vas pas déborder d'un bloc
1198
 * par la faute d'un mot trop long (souvent des URLs)
1199
 *
1200
 * Ne devrait plus être utilisé et fait directement en CSS par un style
1201
 * `word-wrap:break-word;`
1202
 *
1203
 * @note
1204
 *   Pour assurer la compatibilité du filtre, on encapsule le contenu par
1205
 *   un `div` ou `span` portant ce style CSS inline.
1206
 *
1207
 * @filtre
1208
 * @link https://www.spip.net/4298
1209
 * @link http://www.alsacreations.com/tuto/lire/1038-gerer-debordement-contenu-css.html
1210
 * @deprecated Utiliser le style CSS `word-wrap:break-word;`
1211
 *
1212
 * @param string $texte Texte
1213
 * @return string Texte encadré du style CSS
1214
 */
1215
function lignes_longues($texte) {
1216
	if (!strlen(trim($texte))) {
1217
		return $texte;
1218
	}
1219
	include_spip('inc/texte');
1220
	$tag = preg_match(',</?(' . _BALISES_BLOCS . ')[>[:space:]],iS', $texte) ?
1221
		'div' : 'span';
1222
1223
	return "<$tag style='word-wrap:break-word;'>$texte</$tag>";
1224
}
1225
1226
/**
1227
 * Passe un texte en majuscules, y compris les accents, en HTML
1228
 *
1229
 * Encadre le texte du style CSS `text-transform: uppercase;`.
1230
 * Le cas spécifique du i turc est géré.
1231
 *
1232
 * @filtre
1233
 * @example
1234
 *     ```
1235
 *     [(#EXTENSION|majuscules)]
1236
 *     ```
1237
 *
1238
 * @param string $texte Texte
1239
 * @return string Texte en majuscule
1240
 */
1241
function majuscules($texte) {
1242
	if (!strlen($texte)) {
1243
		return '';
1244
	}
1245
1246
	// Cas du turc
1247
	if ($GLOBALS['spip_lang'] == 'tr') {
1248
		# remplacer hors des tags et des entites
1249 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...
1250
			foreach ($regs as $n => $match) {
1251
				$texte = str_replace($match[0], "@@SPIP_TURC$n@@", $texte);
1252
			}
1253
		}
1254
1255
		$texte = str_replace('i', '&#304;', $texte);
1256
1257 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...
1258
			foreach ($regs as $n => $match) {
1259
				$texte = str_replace("@@SPIP_TURC$n@@", $match[0], $texte);
1260
			}
1261
		}
1262
	}
1263
1264
	// Cas general
1265
	return "<span style='text-transform: uppercase;'>$texte</span>";
1266
}
1267
1268
/**
1269
 * Retourne une taille en octets humainement lisible
1270
 *
1271
 * Tel que "127.4 ko" ou "3.1 Mo"
1272
 *
1273
 * @example
1274
 *     - `[(#TAILLE|taille_en_octets)]`
1275
 *     - `[(#VAL{123456789}|taille_en_octets)]` affiche `117.7 Mo`
1276
 *
1277
 * @filtre
1278
 * @link https://www.spip.net/4316
1279
 * @param int $taille
1280
 * @return string
1281
 **/
1282
function taille_en_octets($taille) {
1283
	if (!defined('_KILOBYTE')) {
1284
		/**
1285
		 * Définit le nombre d'octets dans un Kilobyte
1286
		 *
1287
		 * @var int
1288
		 **/
1289
		define('_KILOBYTE', 1024);
1290
	}
1291
1292
	if ($taille < 1) {
1293
		return '';
1294
	}
1295
	if ($taille < _KILOBYTE) {
1296
		$taille = _T('taille_octets', array('taille' => $taille));
1297 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...
1298
		$taille = _T('taille_ko', array('taille' => round($taille / _KILOBYTE, 1)));
1299
	} elseif ($taille < _KILOBYTE * _KILOBYTE * _KILOBYTE) {
1300
		$taille = _T('taille_mo', array('taille' => round($taille / _KILOBYTE / _KILOBYTE, 1)));
1301 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...
1302
		$taille = _T('taille_go', array('taille' => round($taille / _KILOBYTE / _KILOBYTE / _KILOBYTE, 2)));
1303
	}
1304
1305
	return $taille;
1306
}
1307
1308
1309
/**
1310
 * Rend une chaine utilisable sans dommage comme attribut HTML
1311
 *
1312
 * @example `<a href="#URL_ARTICLE" title="[(#TITRE|attribut_html)]">#TITRE</a>`
1313
 *
1314
 * @filtre
1315
 * @link https://www.spip.net/4282
1316
 * @uses textebrut()
1317
 * @uses texte_backend()
1318
 *
1319
 * @param string $texte
1320
 *     Texte à mettre en attribut
1321
 * @param bool $textebrut
1322
 *     Passe le texte en texte brut (enlève les balises html) ?
1323
 * @return string
1324
 *     Texte prêt pour être utilisé en attribut HTML
1325
 **/
1326
function attribut_html($texte, $textebrut = true) {
1327
	$u = $GLOBALS['meta']['pcre_u'];
1328
	if ($textebrut) {
1329
		$texte = preg_replace(array(",\n,", ",\s(?=\s),msS" . $u), array(" ", ""), textebrut($texte));
1330
	}
1331
	$texte = texte_backend($texte);
1332
	$texte = str_replace(array("'", '"'), array('&#039;', '&#034;'), $texte);
1333
1334
	return preg_replace(array("/&(amp;|#38;)/", "/&(?![A-Za-z]{0,4}\w{2,3};|#[0-9]{2,5};)/"), array("&", "&#38;"),
1335
		$texte);
1336
}
1337
1338
1339
/**
1340
 * Vider les URL nulles
1341
 *
1342
 * - Vide les URL vides comme `http://` ou `mailto:` (sans rien d'autre)
1343
 * - échappe les entités et gère les `&amp;`
1344
 *
1345
 * @uses entites_html()
1346
 *
1347
 * @param string $url
1348
 *     URL à vérifier et échapper
1349
 * @param bool $entites
1350
 *     `true` pour échapper les entités HTML.
1351
 * @return string
1352
 *     URL ou chaîne vide
1353
 **/
1354
function vider_url($url, $entites = true) {
1355
	# un message pour abs_url
1356
	$GLOBALS['mode_abs_url'] = 'url';
1357
	$url = trim($url);
1358
	$r = ",^(?:" . _PROTOCOLES_STD . '):?/?/?$,iS';
1359
1360
	return preg_match($r, $url) ? '' : ($entites ? entites_html($url) : $url);
1361
}
1362
1363
1364
/**
1365
 * Maquiller une adresse e-mail
1366
 *
1367
 * Remplace `@` par 3 caractères aléatoires.
1368
 *
1369
 * @uses creer_pass_aleatoire()
1370
 *
1371
 * @param string $texte Adresse email
1372
 * @return string Adresse email maquillée
1373
 **/
1374
function antispam($texte) {
1375
	include_spip('inc/acces');
1376
	$masque = creer_pass_aleatoire(3);
1377
1378
	return preg_replace("/@/", " $masque ", $texte);
1379
}
1380
1381
/**
1382
 * Vérifie un accès à faible sécurité
1383
 *
1384
 * Vérifie qu'un visiteur peut accéder à la page demandée,
1385
 * qui est protégée par une clé, calculée à partir du low_sec de l'auteur,
1386
 * et des paramètres le composant l'appel (op, args)
1387
 *
1388
 * @example
1389
 *     `[(#ID_AUTEUR|securiser_acces{#ENV{cle}, rss, #ENV{op}, #ENV{args}}|sinon_interdire_acces)]`
1390
 *
1391
 * @see  bouton_spip_rss() pour générer un lien de faible sécurité pour les RSS privés
1392
 * @see  afficher_low_sec() pour calculer une clé valide
1393
 * @uses verifier_low_sec()
1394
 *
1395
 * @filtre
1396
 * @param int $id_auteur
1397
 *     L'auteur qui demande la page
1398
 * @param string $cle
1399
 *     La clé à tester
1400
 * @param string $dir
1401
 *     Un type d'accès (nom du répertoire dans lequel sont rangés les squelettes demandés, tel que 'rss')
1402
 * @param string $op
1403
 *     Nom de l'opération éventuelle
1404
 * @param string $args
1405
 *     Nom de l'argument calculé
1406
 * @return bool
1407
 *     True si on a le droit d'accès, false sinon.
1408
 **/
1409
function securiser_acces($id_auteur, $cle, $dir, $op = '', $args = '') {
1410
	include_spip('inc/acces');
1411
	if ($op) {
1412
		$dir .= " $op $args";
1413
	}
1414
1415
	return verifier_low_sec($id_auteur, $cle, $dir);
1416
}
1417
1418
/**
1419
 * Retourne le second paramètre lorsque
1420
 * le premier est considere vide, sinon retourne le premier paramètre.
1421
 *
1422
 * En php `sinon($a, 'rien')` retourne `$a`, ou `'rien'` si `$a` est vide.
1423
 * En filtre SPIP `|sinon{#TEXTE, rien}` : affiche `#TEXTE` ou `rien` si `#TEXTE` est vide,
1424
 *
1425
 * @filtre
1426
 * @see filtre_logique() pour la compilation du filtre dans un squelette
1427
 * @link https://www.spip.net/4313
1428
 * @note
1429
 *     L'utilisation de `|sinon` en tant que filtre de squelette
1430
 *     est directement compilé dans `public/references` par la fonction `filtre_logique()`
1431
 *
1432
 * @param mixed $texte
1433
 *     Contenu de reference a tester
1434
 * @param mixed $sinon
1435
 *     Contenu a retourner si le contenu de reference est vide
1436
 * @return mixed
1437
 *     Retourne $texte, sinon $sinon.
1438
 **/
1439
function sinon($texte, $sinon = '') {
1440
	if ($texte or (!is_array($texte) and strlen($texte))) {
1441
		return $texte;
1442
	} else {
1443
		return $sinon;
1444
	}
1445
}
1446
1447
/**
1448
 * Filtre `|choixsivide{vide, pas vide}` alias de `|?{si oui, si non}` avec les arguments inversés
1449
 *
1450
 * @example
1451
 *     `[(#TEXTE|choixsivide{vide, plein})]` affiche vide si le `#TEXTE`
1452
 *     est considéré vide par PHP (chaîne vide, false, 0, tableau vide, etc…).
1453
 *     C'est l'équivalent de `[(#TEXTE|?{plein, vide})]`
1454
 *
1455
 * @filtre
1456
 * @see choixsiegal()
1457
 * @link https://www.spip.net/4189
1458
 *
1459
 * @param mixed $a
1460
 *     La valeur à tester
1461
 * @param mixed $vide
1462
 *     Ce qui est retourné si `$a` est considéré vide
1463
 * @param mixed $pasvide
1464
 *     Ce qui est retourné sinon
1465
 * @return mixed
1466
 **/
1467
function choixsivide($a, $vide, $pasvide) {
1468
	return $a ? $pasvide : $vide;
1469
}
1470
1471
/**
1472
 * Filtre `|choixsiegal{valeur, sioui, sinon}`
1473
 *
1474
 * @example
1475
 *     `#LANG_DIR|choixsiegal{ltr,left,right}` retourne `left` si
1476
 *      `#LANG_DIR` vaut `ltr` et `right` sinon.
1477
 *
1478
 * @filtre
1479
 * @link https://www.spip.net/4148
1480
 *
1481
 * @param mixed $a1
1482
 *     La valeur à tester
1483
 * @param mixed $a2
1484
 *     La valeur de comparaison
1485
 * @param mixed $v
1486
 *     Ce qui est retourné si la comparaison est vraie
1487
 * @param mixed $f
1488
 *     Ce qui est retourné sinon
1489
 * @return mixed
1490
 **/
1491
function choixsiegal($a1, $a2, $v, $f) {
1492
	return ($a1 == $a2) ? $v : $f;
1493
}
1494
1495
//
1496
// Export iCal
1497
//
1498
1499
/**
1500
 * Adapte un texte pour être inséré dans une valeur d'un export ICAL
1501
 *
1502
 * Passe le texte en utf8, enlève les sauts de lignes et échappe les virgules.
1503
 *
1504
 * @example `SUMMARY:[(#TITRE|filtrer_ical)]`
1505
 * @filtre
1506
 *
1507
 * @param string $texte
1508
 * @return string
1509
 **/
1510
function filtrer_ical($texte) {
1511
	#include_spip('inc/charsets');
1512
	$texte = html2unicode($texte);
1513
	$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...
1514
	$texte = preg_replace("/\n/", " ", $texte);
1515
	$texte = preg_replace("/,/", "\,", $texte);
1516
1517
	return $texte;
1518
}
1519
1520
1521
/**
1522
 * Transforme les sauts de ligne simples en sauts forcés avec `_ `
1523
 *
1524
 * Ne modifie pas les sauts de paragraphe (2 sauts consécutifs au moins),
1525
 * ou les retours à l'intérieur de modèles ou de certaines balises html.
1526
 *
1527
 * @note
1528
 *     Cette fonction pouvait être utilisée pour forcer les alinéas,
1529
 *     (retours à la ligne sans saut de paragraphe), mais ce traitement
1530
 *     est maintenant automatique.
1531
 *     Cf. plugin Textwheel et la constante _AUTOBR
1532
 *
1533
 * @uses echappe_html()
1534
 * @uses echappe_retour()
1535
 *
1536
 * @param string $texte
1537
 * @param string $delim
1538
 *      Ce par quoi sont remplacés les sauts
1539
 * @return string
1540
 **/
1541
function post_autobr($texte, $delim = "\n_ ") {
1542
	if (!function_exists('echappe_html')) {
1543
		include_spip('inc/texte_mini');
1544
	}
1545
	$texte = str_replace("\r\n", "\r", $texte);
1546
	$texte = str_replace("\r", "\n", $texte);
1547
1548 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...
1549
		$texte = substr($texte, 0, -strlen($fin = $fin[0]));
1550
	} else {
1551
		$fin = '';
1552
	}
1553
1554
	$texte = echappe_html($texte, '', true);
1555
1556
	// echapper les modeles
1557
	if (strpos($texte, "<") !== false) {
1558
		include_spip('inc/lien');
1559
		if (defined('_PREG_MODELE')) {
1560
			$preg_modeles = "@" . _PREG_MODELE . "@imsS";
1561
			$texte = echappe_html($texte, '', true, $preg_modeles);
1562
		}
1563
	}
1564
1565
	$debut = '';
1566
	$suite = $texte;
1567
	while ($t = strpos('-' . $suite, "\n", 1)) {
1568
		$debut .= substr($suite, 0, $t - 1);
1569
		$suite = substr($suite, $t);
1570
		$car = substr($suite, 0, 1);
1571
		if (($car <> '-') and ($car <> '_') and ($car <> "\n") and ($car <> "|") and ($car <> "}")
1572
			and !preg_match(',^\s*(\n|</?(quote|div|dl|dt|dd)|$),S', ($suite))
1573
			and !preg_match(',</?(quote|div|dl|dt|dd)> *$,iS', $debut)
1574
		) {
1575
			$debut .= $delim;
1576
		} else {
1577
			$debut .= "\n";
1578
		}
1579 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...
1580
			$debut .= $regs[0];
1581
			$suite = substr($suite, strlen($regs[0]));
1582
		}
1583
	}
1584
	$texte = $debut . $suite;
1585
1586
	$texte = echappe_retour($texte);
1587
1588
	return $texte . $fin;
1589
}
1590
1591
1592
/**
1593
 * Expression régulière pour obtenir le contenu des extraits idiomes `<:module:cle:>`
1594
 *
1595
 * @var string
1596
 */
1597
define('_EXTRAIRE_IDIOME', '@<:(?:([a-z0-9_]+):)?([a-z0-9_]+):>@isS');
1598
1599
/**
1600
 * Extrait une langue des extraits idiomes (`<:module:cle_de_langue:>`)
1601
 *
1602
 * Retrouve les balises `<:cle_de_langue:>` d'un texte et remplace son contenu
1603
 * par l'extrait correspondant à la langue demandée (si possible), sinon dans la
1604
 * langue par défaut du site.
1605
 *
1606
 * Ne pas mettre de span@lang=fr si on est déjà en fr.
1607
 *
1608
 * @filtre
1609
 * @uses inc_traduire_dist()
1610
 * @uses code_echappement()
1611
 * @uses echappe_retour()
1612
 *
1613
 * @param string $letexte
1614
 * @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...
1615
 *     Langue à retrouver (si vide, utilise la langue en cours).
1616
 * @param array $options Options {
1617
 * @type bool $echappe_span
1618
 *         True pour échapper les balises span (false par défaut)
1619
 * @type string $lang_defaut
1620
 *         Code de langue : permet de définir la langue utilisée par défaut,
1621
 *         en cas d'absence de traduction dans la langue demandée.
1622
 *         Par défaut la langue du site.
1623
 *         Indiquer 'aucune' pour ne pas retourner de texte si la langue
1624
 *         exacte n'a pas été trouvée.
1625
 * }
1626
 * @return string
1627
 **/
1628
function extraire_idiome($letexte, $lang = null, $options = array()) {
1629
	static $traduire = false;
1630
	if ($letexte
1631
		and preg_match_all(_EXTRAIRE_IDIOME, $letexte, $regs, PREG_SET_ORDER)
1632
	) {
1633
		if (!$traduire) {
1634
			$traduire = charger_fonction('traduire', 'inc');
1635
			include_spip('inc/lang');
1636
		}
1637
		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...
1638
			$lang = $GLOBALS['spip_lang'];
1639
		}
1640
		// Compatibilité avec le prototype de fonction précédente qui utilisait un boolean
1641
		if (is_bool($options)) {
1642
			$options = array('echappe_span' => $options);
1643
		}
1644 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...
1645
			$options = array_merge($options, array('echappe_span' => false));
1646
		}
1647
1648
		foreach ($regs as $reg) {
1649
			$cle = ($reg[1] ? $reg[1] . ':' : '') . $reg[2];
1650
			$desc = $traduire($cle, $lang, true);
1651
			$l = $desc->langue;
1652
			// si pas de traduction, on laissera l'écriture de l'idiome entier dans le texte.
1653
			if (strlen($desc->texte)) {
1654
				$trad = code_echappement($desc->texte, 'idiome', false);
1655
				if ($l !== $lang) {
1656
					$trad = str_replace("'", '"', inserer_attribut($trad, 'lang', $l));
1657
				}
1658 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...
1659
					$trad = str_replace("'", '"', inserer_attribut($trad, 'dir', lang_dir($l)));
1660
				}
1661
				if (!$options['echappe_span']) {
1662
					$trad = echappe_retour($trad, 'idiome');
1663
				}
1664
				$letexte = str_replace($reg[0], $trad, $letexte);
1665
			}
1666
		}
1667
	}
1668
	return $letexte;
1669
}
1670
1671
/**
1672
 * Expression régulière pour obtenir le contenu des extraits polyglottes `<multi>`
1673
 *
1674
 * @var string
1675
 */
1676
define('_EXTRAIRE_MULTI', "@<multi>(.*?)</multi>@sS");
1677
1678
1679
/**
1680
 * Extrait une langue des extraits polyglottes (`<multi>`)
1681
 *
1682
 * Retrouve les balises `<multi>` d'un texte et remplace son contenu
1683
 * par l'extrait correspondant à la langue demandée.
1684
 *
1685
 * Si la langue demandée n'est pas trouvée dans le multi, ni une langue
1686
 * approchante (exemple `fr` si on demande `fr_TU`), on retourne l'extrait
1687
 * correspondant à la langue par défaut (option 'lang_defaut'), qui est
1688
 * par défaut la langue du site. Et si l'extrait n'existe toujours pas
1689
 * dans cette langue, ça utilisera la première langue utilisée
1690
 * dans la balise `<multi>`.
1691
 *
1692
 * Ne pas mettre de span@lang=fr si on est déjà en fr.
1693
 *
1694
 * @filtre
1695
 * @link https://www.spip.net/5332
1696
 *
1697
 * @uses extraire_trads()
1698
 * @uses approcher_langue()
1699
 * @uses lang_typo()
1700
 * @uses code_echappement()
1701
 * @uses echappe_retour()
1702
 *
1703
 * @param string $letexte
1704
 * @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...
1705
 *     Langue à retrouver (si vide, utilise la langue en cours).
1706
 * @param array $options Options {
1707
 * @type bool $echappe_span
1708
 *         True pour échapper les balises span (false par défaut)
1709
 * @type string $lang_defaut
1710
 *         Code de langue : permet de définir la langue utilisée par défaut,
1711
 *         en cas d'absence de traduction dans la langue demandée.
1712
 *         Par défaut la langue du site.
1713
 *         Indiquer 'aucune' pour ne pas retourner de texte si la langue
1714
 *         exacte n'a pas été trouvée.
1715
 * }
1716
 * @return string
1717
 **/
1718
function extraire_multi($letexte, $lang = null, $options = array()) {
1719
1720
	if ($letexte
1721
		and preg_match_all(_EXTRAIRE_MULTI, $letexte, $regs, PREG_SET_ORDER)
1722
	) {
1723
		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...
1724
			$lang = $GLOBALS['spip_lang'];
1725
		}
1726
1727
		// Compatibilité avec le prototype de fonction précédente qui utilisait un boolean
1728
		if (is_bool($options)) {
1729
			$options = array('echappe_span' => $options, 'lang_defaut' => _LANGUE_PAR_DEFAUT);
1730
		}
1731 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...
1732
			$options = array_merge($options, array('echappe_span' => false));
1733
		}
1734
		if (!isset($options['lang_defaut'])) {
1735
			$options = array_merge($options, array('lang_defaut' => _LANGUE_PAR_DEFAUT));
1736
		}
1737
1738
		include_spip('inc/lang');
1739
		foreach ($regs as $reg) {
1740
			// chercher la version de la langue courante
1741
			$trads = extraire_trads($reg[1]);
1742
			if ($l = approcher_langue($trads, $lang)) {
1743
				$trad = $trads[$l];
1744
			} else {
1745
				if ($options['lang_defaut'] == 'aucune') {
1746
					$trad = '';
1747
				} else {
1748
					// langue absente, prendre le fr ou une langue précisée (meme comportement que inc/traduire.php)
1749
					// ou la premiere dispo
1750
					// mais typographier le texte selon les regles de celle-ci
1751
					// Attention aux blocs multi sur plusieurs lignes
1752
					if (!$l = approcher_langue($trads, $options['lang_defaut'])) {
1753
						$l = key($trads);
1754
					}
1755
					$trad = $trads[$l];
1756
					$typographie = charger_fonction(lang_typo($l), 'typographie');
1757
					$trad = $typographie($trad);
1758
					// Tester si on echappe en span ou en div
1759
					// il ne faut pas echapper en div si propre produit un seul paragraphe
1760
					include_spip('inc/texte');
1761
					$trad_propre = preg_replace(",(^<p[^>]*>|</p>$),Uims", "", propre($trad));
1762
					$mode = preg_match(',</?(' . _BALISES_BLOCS . ')[>[:space:]],iS', $trad_propre) ? 'div' : 'span';
1763
					if ($mode === 'div') {
1764
						$trad = rtrim($trad) . "\n\n";
1765
					}
1766
					$trad = code_echappement($trad, 'multi', false, $mode);
1767
					$trad = str_replace("'", '"', inserer_attribut($trad, 'lang', $l));
1768 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...
1769
						$trad = str_replace("'", '"', inserer_attribut($trad, 'dir', lang_dir($l)));
1770
					}
1771
					if (!$options['echappe_span']) {
1772
						$trad = echappe_retour($trad, 'multi');
1773
					}
1774
				}
1775
			}
1776
			$letexte = str_replace($reg[0], $trad, $letexte);
1777
		}
1778
	}
1779
1780
	return $letexte;
1781
}
1782
1783
/**
1784
 * Convertit le contenu d'une balise `<multi>` en un tableau
1785
 *
1786
 * Exemple de blocs.
1787
 * - `texte par défaut [fr] en français [en] en anglais`
1788
 * - `[fr] en français [en] en anglais`
1789
 *
1790
 * @param string $bloc
1791
 *     Le contenu intérieur d'un bloc multi
1792
 * @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...
1793
 *     Peut retourner un code de langue vide, lorsqu'un texte par défaut est indiqué.
1794
 **/
1795
function extraire_trads($bloc) {
1796
	$lang = '';
1797
// ce reg fait planter l'analyse multi s'il y a de l'{italique} dans le champ
1798
//	while (preg_match("/^(.*?)[{\[]([a-z_]+)[}\]]/siS", $bloc, $regs)) {
1799
	while (preg_match("/^(.*?)[\[]([a-z_]+)[\]]/siS", $bloc, $regs)) {
1800
		$texte = trim($regs[1]);
1801
		if ($texte or $lang) {
1802
			$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...
1803
		}
1804
		$bloc = substr($bloc, strlen($regs[0]));
1805
		$lang = $regs[2];
1806
	}
1807
	$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...
1808
1809
	return $trads;
1810
}
1811
1812
1813
/**
1814
 * Calculer l'initiale d'un nom
1815
 *
1816
 * @param string $nom
1817
 * @return string L'initiale en majuscule
1818
 */
1819
function filtre_initiale($nom) {
1820
	return spip_substr(trim(strtoupper(extraire_multi($nom))), 0, 1);
1821
}
1822
1823
1824
/**
1825
 * Retourne la donnée si c'est la première fois qu'il la voit
1826
 *
1827
 * Il est possible de gérer différentes "familles" de données avec
1828
 * le second paramètre.
1829
 *
1830
 * @filtre
1831
 * @link https://www.spip.net/4320
1832
 * @example
1833
 *     ```
1834
 *     [(#ID_SECTEUR|unique)]
1835
 *     [(#ID_SECTEUR|unique{tete})] n'a pas d'incidence sur
1836
 *     [(#ID_SECTEUR|unique{pied})]
1837
 *     [(#ID_SECTEUR|unique{pied,1})] affiche le nombre d'éléments.
1838
 *     Préférer totefois #TOTAL_UNIQUE{pied}
1839
 *     ```
1840
 *
1841
 * @todo
1842
 *    Ameliorations possibles :
1843
 *
1844
 *    1) si la donnée est grosse, mettre son md5 comme clé
1845
 *    2) purger $mem quand on change de squelette (sinon bug inclusions)
1846
 *
1847
 * @param string $donnee
1848
 *      Donnée que l'on souhaite unique
1849
 * @param string $famille
1850
 *      Famille de stockage (1 unique donnée par famille)
1851
 *
1852
 *      - _spip_raz_ : (interne) Vide la pile de mémoire et la retourne
1853
 *      - _spip_set_ : (interne) Affecte la pile de mémoire avec la donnée
1854
 * @param bool $cpt
1855
 *      True pour obtenir le nombre d'éléments différents stockés
1856
 * @return string|int|array|null|void
1857
 *
1858
 *      - string : Donnée si c'est la première fois qu'elle est vue
1859
 *      - void : si la donnée a déjà été vue
1860
 *      - int : si l'on demande le nombre d'éléments
1861
 *      - array (interne) : si on dépile
1862
 *      - null (interne) : si on empile
1863
 **/
1864
function unique($donnee, $famille = '', $cpt = false) {
1865
	static $mem = array();
1866
	// permettre de vider la pile et de la restaurer
1867
	// pour le calcul de introduction...
1868
	if ($famille == '_spip_raz_') {
1869
		$tmp = $mem;
1870
		$mem = array();
1871
1872
		return $tmp;
1873
	} elseif ($famille == '_spip_set_') {
1874
		$mem = $donnee;
1875
1876
		return;
1877
	}
1878
	// eviter une notice
1879
	if (!isset($mem[$famille])) {
1880
		$mem[$famille] = array();
1881
	}
1882
	if ($cpt) {
1883
		return count($mem[$famille]);
1884
	}
1885
	// eviter une notice
1886
	if (!isset($mem[$famille][$donnee])) {
1887
		$mem[$famille][$donnee] = 0;
1888
	}
1889
	if (!($mem[$famille][$donnee]++)) {
1890
		return $donnee;
1891
	}
1892
}
1893
1894
1895
/**
1896
 * Filtre qui alterne des valeurs en fonction d'un compteur
1897
 *
1898
 * Affiche à tour de rôle et dans l'ordre, un des arguments transmis
1899
 * à chaque incrément du compteur.
1900
 *
1901
 * S'il n'y a qu'un seul argument, et que c'est un tableau,
1902
 * l'alternance se fait sur les valeurs du tableau.
1903
 *
1904
 * Souvent appliqué à l'intérieur d'une boucle, avec le compteur `#COMPTEUR_BOUCLE`
1905
 *
1906
 * @example
1907
 *     - `[(#COMPTEUR_BOUCLE|alterner{bleu,vert,rouge})]`
1908
 *     - `[(#COMPTEUR_BOUCLE|alterner{#LISTE{bleu,vert,rouge}})]`
1909
 *
1910
 * @filtre
1911
 * @link https://www.spip.net/4145
1912
 *
1913
 * @param int $i
1914
 *     Le compteur
1915
 * @return mixed
1916
 *     Une des valeurs en fonction du compteur.
1917
 **/
1918
function alterner($i) {
1919
	// recuperer les arguments (attention fonctions un peu space)
1920
	$num = func_num_args();
1921
	$args = func_get_args();
1922
1923
	if ($num == 2 && is_array($args[1])) {
1924
		$args = $args[1];
1925
		array_unshift($args, '');
1926
		$num = count($args);
1927
	}
1928
1929
	// renvoyer le i-ieme argument, modulo le nombre d'arguments
1930
	return $args[(intval($i) - 1) % ($num - 1) + 1];
1931
}
1932
1933
1934
/**
1935
 * Récupérer un attribut d'une balise HTML
1936
 *
1937
 * la regexp est mortelle : cf. `tests/unit/filtres/extraire_attribut.php`
1938
 * Si on a passé un tableau de balises, renvoyer un tableau de résultats
1939
 * (dans ce cas l'option `$complet` n'est pas disponible)
1940
 *
1941
 * @param string|array $balise
1942
 *     Texte ou liste de textes dont on veut extraire des balises
1943
 * @param string $attribut
1944
 *     Nom de l'attribut désiré
1945
 * @param bool $complet
1946
 *     True pour retourner un tableau avec
1947
 *     - le texte de la balise
1948
 *     - l'ensemble des résultats de la regexp ($r)
1949
 * @return string|array
1950
 *     - Texte de l'attribut retourné, ou tableau des texte d'attributs
1951
 *       (si 1er argument tableau)
1952
 *     - Tableau complet (si 2e argument)
1953
 **/
1954
function extraire_attribut($balise, $attribut, $complet = false) {
1955
	if (is_array($balise)) {
1956
		array_walk(
1957
			$balise,
1958
			function(&$a, $key, $t){
1959
				$a = extraire_attribut($a, $t);
1960
			},
1961
			$attribut
1962
		);
1963
1964
		return $balise;
1965
	}
1966
	if (preg_match(
1967
		',(^.*?<(?:(?>\s*)(?>[\w:.-]+)(?>(?:=(?:"[^"]*"|\'[^\']*\'|[^\'"]\S*))?))*?)(\s+'
1968
		. $attribut
1969
		. '(?:=\s*("[^"]*"|\'[^\']*\'|[^\'"]\S*))?)()((?:[\s/][^>]*)?>.*),isS',
1970
1971
		$balise, $r)) {
1972
		if (isset($r[3][0]) and ($r[3][0] == '"' || $r[3][0] == "'")) {
1973
			$r[4] = substr($r[3], 1, -1);
1974
			$r[3] = $r[3][0];
1975
		} elseif ($r[3] !== '') {
1976
			$r[4] = $r[3];
1977
			$r[3] = '';
1978
		} else {
1979
			$r[4] = trim($r[2]);
1980
		}
1981
		$att = $r[4];
1982 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...
1983
			$att = str_replace(array("&#039;", "&#39;", "&#034;", "&#34;"), array("'", "'", '"', '"'), $att);
1984
		}
1985
		$att = filtrer_entites($att);
1986
	} else {
1987
		$att = null;
1988
	}
1989
1990
	if ($complet) {
1991
		return array($att, $r);
1992
	} else {
1993
		return $att;
1994
	}
1995
}
1996
1997
/**
1998
 * Insérer (ou modifier) un attribut html dans une balise
1999
 *
2000
 * @example
2001
 *     - `[(#LOGO_ARTICLE|inserer_attribut{class, logo article})]`
2002
 *     - `[(#LOGO_ARTICLE|inserer_attribut{alt, #TTTRE|attribut_html|couper{60}})]`
2003
 *     - `[(#FICHIER|image_reduire{40}|inserer_attribut{data-description, #DESCRIPTIF})]`
2004
 *       Laissera les balises HTML de la valeur (ici `#DESCRIPTIF`) si on n'applique pas le
2005
 *       filtre `attribut_html` dessus.
2006
 *
2007
 * @filtre
2008
 * @link https://www.spip.net/4294
2009
 * @uses attribut_html()
2010
 * @uses extraire_attribut()
2011
 *
2012
 * @param string $balise
2013
 *     Code html de la balise (ou contenant une balise)
2014
 * @param string $attribut
2015
 *     Nom de l'attribut html à modifier
2016
 * @param string $val
2017
 *     Valeur de l'attribut à appliquer
2018
 * @param bool $proteger
2019
 *     Prépare la valeur en tant qu'attribut de balise (mais conserve les balises html).
2020
 * @param bool $vider
2021
 *     True pour vider l'attribut. Une chaîne vide pour `$val` fera pareil.
2022
 * @return string
2023
 *     Code html modifié
2024
 **/
2025
function inserer_attribut($balise, $attribut, $val, $proteger = true, $vider = false) {
2026
	// preparer l'attribut
2027
	// supprimer les &nbsp; etc mais pas les balises html
2028
	// qui ont un sens dans un attribut value d'un input
2029
	if ($proteger) {
2030
		$val = attribut_html($val, false);
2031
	}
2032
2033
	// echapper les ' pour eviter tout bug
2034
	$val = str_replace("'", "&#039;", $val);
2035
	if ($vider and strlen($val) == 0) {
2036
		$insert = '';
2037
	} else {
2038
		$insert = " $attribut='$val'";
2039
	}
2040
2041
	list($old, $r) = extraire_attribut($balise, $attribut, true);
2042
2043
	if ($old !== null) {
2044
		// Remplacer l'ancien attribut du meme nom
2045
		$balise = $r[1] . $insert . $r[5];
2046
	} else {
2047
		// preferer une balise " />" (comme <img />)
2048
		if (preg_match(',/>,', $balise)) {
2049
			$balise = preg_replace(",\s?/>,S", $insert . " />", $balise, 1);
2050
		} // sinon une balise <a ...> ... </a>
2051
		else {
2052
			$balise = preg_replace(",\s?>,S", $insert . ">", $balise, 1);
2053
		}
2054
	}
2055
2056
	return $balise;
2057
}
2058
2059
/**
2060
 * Supprime un attribut HTML
2061
 *
2062
 * @example `[(#LOGO_ARTICLE|vider_attribut{class})]`
2063
 *
2064
 * @filtre
2065
 * @link https://www.spip.net/4142
2066
 * @uses inserer_attribut()
2067
 * @see  extraire_attribut()
2068
 *
2069
 * @param string $balise Code HTML de l'élément
2070
 * @param string $attribut Nom de l'attribut à enlever
2071
 * @return string Code HTML sans l'attribut
2072
 **/
2073
function vider_attribut($balise, $attribut) {
2074
	return inserer_attribut($balise, $attribut, '', false, true);
2075
}
2076
2077
/**
2078
 * Fonction support pour les filtres |ajouter_class |supprimer_class |commuter_class
2079
 *
2080
 * @param string $balise
2081
 * @param string|array $class
2082
 * @param string $operation
2083
 * @return string
2084
 */
2085
function modifier_class($balise, $class, $operation='ajouter') {
2086
	if (is_string($class)) {
2087
		$class = explode(' ', trim($class));
2088
	}
2089
	$class = array_filter($class);
2090
2091
	// si la ou les classes ont des caracteres invalides on ne fait rien
2092
	if (preg_match(",[^\w-],", implode('', $class))) {
2093
		return $balise;
2094
	}
2095
2096
	if ($class) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $class of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
2097
		$class = array_unique($class);
2098
		$class_courante = extraire_attribut($balise, 'class');
2099
2100
		$class_new = $class_courante;
2101
		foreach ($class as $c) {
2102
			if ($c) {
2103
				$is_class_presente = false;
2104
				if (strpos($class_courante, $c) !== false
2105
					and preg_match("/(^|\s)".preg_quote($c)."($|\s)/", $class_courante)) {
2106
					$is_class_presente = true;
2107
				}
2108
				if (in_array($operation, ['ajouter', 'commuter'])
2109
					and !$is_class_presente) {
2110
					$class_new = rtrim($class_new) . " " . $c;
2111
				}
2112
				elseif (in_array($operation, ['supprimer', 'commuter'])
2113
					and $is_class_presente) {
2114
					$class_new = trim(preg_replace("/(^|\s)".preg_quote($c)."($|\s)/", "\\1", $class_new));
2115
				}
2116
			}
2117
		}
2118
2119
		if ($class_new !== $class_courante) {
2120
			if (strlen($class_new)) {
2121
				$balise = inserer_attribut($balise, 'class', $class_new);
0 ignored issues
show
Bug introduced by
It seems like $class_new defined by $class_courante on line 2100 can also be of type array; 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...
2122
			}
2123
			elseif ($class_courante) {
2124
				$balise = vider_attribut($balise, 'class');
2125
			}
2126
		}
2127
	}
2128
2129
	return $balise;
2130
}
2131
2132
/**
2133
 * Ajoute une ou plusieurs classes sur une balise (si pas deja presentes)
2134
 * @param string $balise
2135
 * @param string|array $class
2136
 * @return string
2137
 */
2138
function ajouter_class($balise, $class){
2139
	return modifier_class($balise, $class, 'ajouter');
2140
}
2141
2142
/**
2143
 * Supprime une ou plusieurs classes sur une balise (si presentes)
2144
 * @param string $balise
2145
 * @param string|array $class
2146
 * @return string
2147
 */
2148
function supprimer_class($balise, $class){
2149
	return modifier_class($balise, $class, 'supprimer');
2150
}
2151
2152
/**
2153
 * Bascule une ou plusieurs classes sur une balise : ajoutees si absentes, supprimees si presentes
2154
 *
2155
 * @param string $balise
2156
 * @param string|array $class
2157
 * @return string
2158
 */
2159
function commuter_class($balise, $class){
2160
	return modifier_class($balise, $class, 'commuter');
2161
}
2162
2163
/**
2164
 * Un filtre pour déterminer le nom du statut des inscrits
2165
 *
2166
 * @param void|int $id
2167
 * @param string $mode
2168
 * @return string
2169
 */
2170
function tester_config($id, $mode = '') {
2171
	include_spip('action/inscrire_auteur');
2172
2173
	return tester_statut_inscription($mode, $id);
2174
}
2175
2176
//
2177
// Quelques fonctions de calcul arithmetique
2178
//
2179
function floatstr($a) { return str_replace(',','.',(string)floatval($a)); }
2180
function strize($f, $a, $b) { return floatstr($f(floatstr($a),floatstr($b))); }
2181
2182
/**
2183
 * Additionne 2 nombres
2184
 *
2185
 * @filtre
2186
 * @link https://www.spip.net/4307
2187
 * @see moins()
2188
 * @example
2189
 *     ```
2190
 *     [(#VAL{28}|plus{14})]
2191
 *     ```
2192
 *
2193
 * @param int $a
2194
 * @param int $b
2195
 * @return int $a+$b
2196
 **/
2197
function plus($a, $b) {
2198
	return $a + $b;
2199
}
2200
function strplus($a, $b) {return strize('plus', $a, $b);}
2201
/**
2202
 * Soustrait 2 nombres
2203
 *
2204
 * @filtre
2205
 * @link https://www.spip.net/4302
2206
 * @see plus()
2207
 * @example
2208
 *     ```
2209
 *     [(#VAL{28}|moins{14})]
2210
 *     ```
2211
 *
2212
 * @param int $a
2213
 * @param int $b
2214
 * @return int $a-$b
2215
 **/
2216
function moins($a, $b) {
2217
	return $a - $b;
2218
}
2219
function strmoins($a, $b) {return strize('moins', $a, $b);}
2220
2221
/**
2222
 * Multiplie 2 nombres
2223
 *
2224
 * @filtre
2225
 * @link https://www.spip.net/4304
2226
 * @see div()
2227
 * @see modulo()
2228
 * @example
2229
 *     ```
2230
 *     [(#VAL{28}|mult{14})]
2231
 *     ```
2232
 *
2233
 * @param int $a
2234
 * @param int $b
2235
 * @return int $a*$b
2236
 **/
2237
function mult($a, $b) {
2238
	return $a * $b;
2239
}
2240
function strmult($a, $b) {return strize('mult', $a, $b);}
2241
2242
/**
2243
 * Divise 2 nombres
2244
 *
2245
 * @filtre
2246
 * @link https://www.spip.net/4279
2247
 * @see mult()
2248
 * @see modulo()
2249
 * @example
2250
 *     ```
2251
 *     [(#VAL{28}|div{14})]
2252
 *     ```
2253
 *
2254
 * @param int $a
2255
 * @param int $b
2256
 * @return int $a/$b (ou 0 si $b est nul)
2257
 **/
2258
function div($a, $b) {
2259
	return $b ? $a / $b : 0;
2260
}
2261
function strdiv($a, $b) {return strize('div', $a, $b);}
2262
2263
/**
2264
 * Retourne le modulo 2 nombres
2265
 *
2266
 * @filtre
2267
 * @link https://www.spip.net/4301
2268
 * @see mult()
2269
 * @see div()
2270
 * @example
2271
 *     ```
2272
 *     [(#VAL{28}|modulo{14})]
2273
 *     ```
2274
 *
2275
 * @param int $nb
2276
 * @param int $mod
2277
 * @param int $add
2278
 * @return int ($nb % $mod) + $add
2279
 **/
2280
function modulo($nb, $mod, $add = 0) {
2281
	return ($mod ? $nb % $mod : 0) + $add;
2282
}
2283
2284
2285
/**
2286
 * Vérifie qu'un nom (d'auteur) ne comporte pas d'autres tags que <multi>
2287
 * et ceux volontairement spécifiés dans la constante
2288
 *
2289
 * @param string $nom
2290
 *      Nom (signature) proposé
2291
 * @return bool
2292
 *      - false si pas conforme,
2293
 *      - true sinon
2294
 **/
2295
function nom_acceptable($nom) {
2296
	if (!is_string($nom)) {
2297
		return false;
2298
	}
2299
	if (!defined('_TAGS_NOM_AUTEUR')) {
2300
		define('_TAGS_NOM_AUTEUR', '');
2301
	}
2302
	$tags_acceptes = array_unique(explode(',', 'multi,' . _TAGS_NOM_AUTEUR));
2303
	foreach ($tags_acceptes as $tag) {
2304
		if (strlen($tag)) {
2305
			$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...
2306
			$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...
2307
			$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...
2308
			$remp2[] = '\x60/' . trim($tag) . '\x61';
2309
		}
2310
	}
2311
	$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...
2312
2313
	return str_replace('&lt;', '<', $v_nom) == $nom;
2314
}
2315
2316
2317
/**
2318
 * Vérifier la conformité d'une ou plusieurs adresses email (suivant RFC 822)
2319
 *
2320
 * @param string|array $adresses
2321
 *      Adresse ou liste d'adresse
2322
 *      si on fournit un tableau, il est filtre et la fonction renvoie avec uniquement les adresses email valides (donc possiblement vide)
2323
 * @return bool|string|array
2324
 *      - false si une des adresses n'est pas conforme,
2325
 *      - la normalisation de la dernière adresse donnée sinon
2326
 *      - renvoie un tableau si l'entree est un tableau
2327
 **/
2328
function email_valide($adresses) {
2329
	if (is_array($adresses)) {
2330
		$adresses = array_map('email_valide', $adresses);
2331
		$adresses = array_filter($adresses);
2332
		return $adresses;
2333
	}
2334
2335
	$email_valide = charger_fonction('email_valide', 'inc');
2336
	return $email_valide($adresses);
2337
}
2338
2339
/**
2340
 * Permet d'afficher un symbole à côté des liens pointant vers les
2341
 * documents attachés d'un article (liens ayant `rel=enclosure`).
2342
 *
2343
 * @filtre
2344
 * @link https://www.spip.net/4134
2345
 *
2346
 * @param string $tags Texte
2347
 * @return string Texte
2348
 **/
2349
function afficher_enclosures($tags) {
2350
	$s = array();
2351
	foreach (extraire_balises($tags, 'a') as $tag) {
2352
		if (extraire_attribut($tag, 'rel') == 'enclosure'
2353
			and $t = extraire_attribut($tag, 'href')
2354
		) {
2355
			$s[] = preg_replace(',>[^<]+</a>,S',
2356
				'>'
2357
				. 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 2353 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...
2358
					'title="' . attribut_html($t) . '"')
0 ignored issues
show
Bug introduced by
It seems like $t defined by extraire_attribut($tag, 'href') on line 2353 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...
2359
				. '</a>', $tag);
2360
		}
2361
	}
2362
2363
	return join('&nbsp;', $s);
2364
}
2365
2366
/**
2367
 * Filtre des liens HTML `<a>` selon la valeur de leur attribut `rel`
2368
 * et ne retourne que ceux là.
2369
 *
2370
 * @filtre
2371
 * @link https://www.spip.net/4187
2372
 *
2373
 * @param string $tags Texte
2374
 * @param string $rels Attribut `rel` à capturer (ou plusieurs séparés par des virgules)
2375
 * @return string Liens trouvés
2376
 **/
2377
function afficher_tags($tags, $rels = 'tag,directory') {
2378
	$s = array();
2379
	foreach (extraire_balises($tags, 'a') as $tag) {
2380
		$rel = extraire_attribut($tag, 'rel');
2381
		if (strstr(",$rels,", ",$rel,")) {
2382
			$s[] = $tag;
2383
		}
2384
	}
2385
2386
	return join(', ', $s);
2387
}
2388
2389
2390
/**
2391
 * Convertir les médias fournis par un flux RSS (podcasts)
2392
 * en liens conformes aux microformats
2393
 *
2394
 * Passe un `<enclosure url="fichier" length="5588242" type="audio/mpeg"/>`
2395
 * au format microformat `<a rel="enclosure" href="fichier" ...>fichier</a>`.
2396
 *
2397
 * Peut recevoir un `<link` ou un `<media:content` parfois.
2398
 *
2399
 * Attention : `length="zz"` devient `title="zz"`, pour rester conforme.
2400
 *
2401
 * @filtre
2402
 * @see microformat2enclosure() Pour l'inverse
2403
 *
2404
 * @param string $e Tag RSS `<enclosure>`
2405
 * @return string Tag HTML `<a>` avec microformat.
2406
 **/
2407
function enclosure2microformat($e) {
2408
	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...
2409
		$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...
2410
	}
2411
	$type = extraire_attribut($e, 'type');
2412
	if (!$length = extraire_attribut($e, 'length')) {
2413
		# <media:content : longeur dans fileSize. On tente.
2414
		$length = extraire_attribut($e, 'fileSize');
2415
	}
2416
	$fichier = basename($url);
2417
2418
	return '<a rel="enclosure"'
2419
	. ($url ? ' href="' . spip_htmlspecialchars($url) . '"' : '')
2420
	. ($type ? ' type="' . spip_htmlspecialchars($type) . '"' : '')
0 ignored issues
show
Bug introduced by
It seems like $type defined by extraire_attribut($e, 'type') on line 2411 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...
2421
	. ($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...
2422
	. '>' . $fichier . '</a>';
2423
}
2424
2425
/**
2426
 * Convertir les liens conformes aux microformats en médias pour flux RSS,
2427
 * par exemple pour les podcasts
2428
 *
2429
 * Passe un texte ayant des liens avec microformat
2430
 * `<a rel="enclosure" href="fichier" ...>fichier</a>`
2431
 * au format RSS `<enclosure url="fichier" ... />`.
2432
 *
2433
 * @filtre
2434
 * @see enclosure2microformat() Pour l'inverse
2435
 *
2436
 * @param string $tags Texte HTML ayant des tag `<a>` avec microformat
2437
 * @return string Tags RSS `<enclosure>`.
2438
 **/
2439
function microformat2enclosure($tags) {
2440
	$enclosures = array();
2441
	foreach (extraire_balises($tags, 'a') as $e) {
2442
		if (extraire_attribut($e, 'rel') == 'enclosure') {
2443
			$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...
2444
			$type = extraire_attribut($e, 'type');
2445
			if (!$length = intval(extraire_attribut($e, 'title'))) {
2446
				$length = intval(extraire_attribut($e, 'length'));
2447
			} # vieux data
2448
			$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...
2449
			$enclosures[] = '<enclosure'
2450
				. ($url ? ' url="' . spip_htmlspecialchars($url) . '"' : '')
2451
				. ($type ? ' type="' . spip_htmlspecialchars($type) . '"' : '')
0 ignored issues
show
Bug introduced by
It seems like $type defined by extraire_attribut($e, 'type') on line 2444 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...
2452
				. ($length ? ' length="' . $length . '"' : '')
2453
				. ' />';
2454
		}
2455
	}
2456
2457
	return join("\n", $enclosures);
2458
}
2459
2460
2461
/**
2462
 * Créer les éléments ATOM `<dc:subject>` à partir des tags
2463
 *
2464
 * Convertit les liens avec attribut `rel="tag"`
2465
 * en balise `<dc:subject></dc:subject>` pour les flux RSS au format Atom.
2466
 *
2467
 * @filtre
2468
 *
2469
 * @param string $tags Texte
2470
 * @return string Tags RSS Atom `<dc:subject>`.
2471
 **/
2472
function tags2dcsubject($tags) {
2473
	$subjects = '';
2474
	foreach (extraire_balises($tags, 'a') as $e) {
2475
		if (extraire_attribut($e, 'rel') == 'tag') {
2476
			$subjects .= '<dc:subject>'
2477
				. texte_backend(textebrut($e))
2478
				. '</dc:subject>' . "\n";
2479
		}
2480
	}
2481
2482
	return $subjects;
2483
}
2484
2485
/**
2486
 * Retourne la premiere balise html du type demandé
2487
 *
2488
 * Retourne le contenu d'une balise jusqu'à la première fermeture rencontrée
2489
 * du même type.
2490
 * Si on a passe un tableau de textes, retourne un tableau de resultats.
2491
 *
2492
 * @example `[(#DESCRIPTIF|extraire_balise{img})]`
2493
 *
2494
 * @filtre
2495
 * @link https://www.spip.net/4289
2496
 * @see extraire_balises()
2497
 * @note
2498
 *     Attention : les résultats peuvent être incohérents sur des balises imbricables,
2499
 *     tel que demander à extraire `div` dans le texte `<div> un <div> mot </div> absent </div>`,
2500
 *     ce qui retournerait `<div> un <div> mot </div>` donc.
2501
 *
2502
 * @param string|array $texte
2503
 *     Texte(s) dont on souhaite extraire une balise html
2504
 * @param string $tag
2505
 *     Nom de la balise html à extraire
2506
 * @return void|string|array
2507
 *     - Code html de la balise, sinon rien
2508
 *     - Tableau de résultats, si tableau en entrée.
2509
 **/
2510 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...
2511
	if (is_array($texte)) {
2512
		array_walk(
2513
			$texte,
2514
			function(&$a, $key, $t){
2515
				$a = extraire_balise($a, $t);
2516
			},
2517
			$tag
2518
		);
2519
2520
		return $texte;
2521
	}
2522
2523
	if (preg_match(
2524
		",<$tag\b[^>]*(/>|>.*</$tag\b[^>]*>|>),UimsS",
2525
		$texte, $regs)) {
2526
		return $regs[0];
2527
	}
2528
}
2529
2530
/**
2531
 * Extrait toutes les balises html du type demandé
2532
 *
2533
 * Retourne dans un tableau le contenu de chaque balise jusqu'à la première
2534
 * fermeture rencontrée du même type.
2535
 * Si on a passe un tableau de textes, retourne un tableau de resultats.
2536
 *
2537
 * @example `[(#TEXTE|extraire_balises{img}|implode{" - "})]`
2538
 *
2539
 * @filtre
2540
 * @link https://www.spip.net/5618
2541
 * @see extraire_balise()
2542
 * @note
2543
 *     Attention : les résultats peuvent être incohérents sur des balises imbricables,
2544
 *     tel que demander à extraire `div` dans un texte.
2545
 *
2546
 * @param string|array $texte
2547
 *     Texte(s) dont on souhaite extraire une balise html
2548
 * @param string $tag
2549
 *     Nom de la balise html à extraire
2550
 * @return array
2551
 *     - Liste des codes html des occurrences de la balise, sinon tableau vide
2552
 *     - Tableau de résultats, si tableau en entrée.
2553
 **/
2554 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...
2555
	if (is_array($texte)) {
2556
		array_walk(
2557
			$texte,
2558
			function(&$a, $key, $t){
2559
				$a = extraire_balises($a, $t);
2560
			},
2561
			$tag
2562
		);
2563
2564
		return $texte;
2565
	}
2566
2567
	if (preg_match_all(
2568
		",<${tag}\b[^>]*(/>|>.*</${tag}\b[^>]*>|>),UimsS",
2569
		$texte, $regs, PREG_PATTERN_ORDER)) {
2570
		return $regs[0];
2571
	} else {
2572
		return array();
2573
	}
2574
}
2575
2576
/**
2577
 * Indique si le premier argument est contenu dans le second
2578
 *
2579
 * Cette fonction est proche de `in_array()` en PHP avec comme principale
2580
 * différence qu'elle ne crée pas d'erreur si le second argument n'est pas
2581
 * un tableau (dans ce cas elle tentera de le désérialiser, et sinon retournera
2582
 * la valeur par défaut transmise).
2583
 *
2584
 * @example `[(#VAL{deux}|in_any{#LISTE{un,deux,trois}}|oui) ... ]`
2585
 *
2586
 * @filtre
2587
 * @see filtre_find() Assez proche, avec les arguments valeur et tableau inversés.
2588
 *
2589
 * @param string $val
2590
 *     Valeur à chercher dans le tableau
2591
 * @param array|string $vals
2592
 *     Tableau des valeurs. S'il ce n'est pas un tableau qui est transmis,
2593
 *     la fonction tente de la désérialiser.
2594
 * @param string $def
2595
 *     Valeur par défaut retournée si `$vals` n'est pas un tableau.
2596
 * @return string
2597
 *     - ' ' si la valeur cherchée est dans le tableau
2598
 *     - '' si la valeur n'est pas dans le tableau
2599
 *     - `$def` si on n'a pas transmis de tableau
2600
 **/
2601
function in_any($val, $vals, $def = '') {
2602
	if (!is_array($vals) and $v = unserialize($vals)) {
2603
		$vals = $v;
2604
	}
2605
2606
	return (!is_array($vals) ? $def : (in_array($val, $vals) ? ' ' : ''));
2607
}
2608
2609
2610
/**
2611
 * Retourne le résultat d'une expression mathématique simple
2612
 *
2613
 * N'accepte que les *, + et - (à ameliorer si on l'utilise vraiment).
2614
 *
2615
 * @filtre
2616
 * @example
2617
 *      ```
2618
 *      valeur_numerique("3*2") retourne 6
2619
 *      ```
2620
 *
2621
 * @param string $expr
2622
 *     Expression mathématique `nombre operateur nombre` comme `3*2`
2623
 * @return int
2624
 *     Résultat du calcul
2625
 **/
2626
function valeur_numerique($expr) {
2627
	$a = 0;
2628
	if (preg_match(',^[0-9]+(\s*[+*-]\s*[0-9]+)*$,S', trim($expr))) {
2629
		eval("\$a = $expr;");
2630
	}
2631
2632
	return intval($a);
2633
}
2634
2635
/**
2636
 * Retourne un calcul de règle de trois
2637
 *
2638
 * @filtre
2639
 * @example
2640
 *     ```
2641
 *     [(#VAL{6}|regledetrois{4,3})] retourne 8
2642
 *     ```
2643
 *
2644
 * @param int $a
2645
 * @param int $b
2646
 * @param int $c
2647
 * @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...
2648
 *      Retourne `$a*$b/$c`
2649
 **/
2650
function regledetrois($a, $b, $c) {
2651
	return round($a * $b / $c);
2652
}
2653
2654
2655
/**
2656
 * Crée des tags HTML input hidden pour chaque paramètre et valeur d'une URL
2657
 *
2658
 * Fournit la suite de Input-Hidden correspondant aux paramètres de
2659
 * l'URL donnée en argument, compatible avec les types_urls
2660
 *
2661
 * @filtre
2662
 * @link https://www.spip.net/4286
2663
 * @see balise_ACTION_FORMULAIRE()
2664
 *     Également pour transmettre les actions à un formulaire
2665
 * @example
2666
 *     ```
2667
 *     [(#ENV{action}|form_hidden)] dans un formulaire
2668
 *     ```
2669
 *
2670
 * @param string $action URL
2671
 * @return string Suite de champs input hidden
2672
 **/
2673
function form_hidden($action) {
2674
2675
	$contexte = array();
2676
	include_spip('inc/urls');
2677
	if ($p = urls_decoder_url($action, '')
2678
		and reset($p)
2679
	) {
2680
		$fond = array_shift($p);
2681
		if ($fond != '404') {
2682
			$contexte = array_shift($p);
2683
			$contexte['page'] = $fond;
2684
			$action = preg_replace('/([?]' . preg_quote($fond) . '[^&=]*[0-9]+)(&|$)/', '?&', $action);
2685
		}
2686
	}
2687
	// defaire ce qu'a injecte urls_decoder_url : a revoir en modifiant la signature de urls_decoder_url
2688
	if (defined('_DEFINIR_CONTEXTE_TYPE') and _DEFINIR_CONTEXTE_TYPE) {
2689
		unset($contexte['type']);
2690
	}
2691
	if (defined('_DEFINIR_CONTEXTE_TYPE_PAGE') and _DEFINIR_CONTEXTE_TYPE_PAGE) {
2692
		unset($contexte['type-page']);
2693
	}
2694
2695
	// on va remplir un tableau de valeurs en prenant bien soin de ne pas
2696
	// ecraser les elements de la forme mots[]=1&mots[]=2
2697
	$values = array();
2698
2699
	// d'abord avec celles de l'url
2700
	if (false !== ($p = strpos($action, '?'))) {
2701
		foreach (preg_split('/&(amp;)?/S', substr($action, $p + 1)) as $c) {
2702
			$c = explode('=', $c, 2);
2703
			$var = array_shift($c);
2704
			$val = array_shift($c);
2705
			if ($var) {
2706
				$val = rawurldecode($val);
2707
				$var = rawurldecode($var); // decoder les [] eventuels
2708 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...
2709
					$values[] = array($var, $val);
2710
				} else {
2711
					if (!isset($values[$var])) {
2712
						$values[$var] = array($var, $val);
2713
					}
2714
				}
2715
			}
2716
		}
2717
	}
2718
2719
	// ensuite avec celles du contexte, sans doublonner !
2720
	foreach ($contexte as $var => $val) {
2721 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...
2722
			$values[] = array($var, $val);
2723
		} else {
2724
			if (!isset($values[$var])) {
2725
				$values[$var] = array($var, $val);
2726
			}
2727
		}
2728
	}
2729
2730
	// puis on rassemble le tout
2731
	$hidden = array();
2732
	foreach ($values as $value) {
2733
		list($var, $val) = $value;
2734
		$hidden[] = '<input name="'
2735
			. entites_html($var)
2736
			. '"'
2737
			. (is_null($val)
2738
				? ''
2739
				: ' value="' . entites_html($val) . '"'
2740
			)
2741
			. ' type="hidden"' . "\n/>";
2742
	}
2743
2744
	return join("", $hidden);
2745
}
2746
2747
2748
/**
2749
 * Retourne la première valeur d'un tableau
2750
 *
2751
 * Plus précisément déplace le pointeur du tableau sur la première valeur et la retourne.
2752
 *
2753
 * @example `[(#LISTE{un,deux,trois}|reset)]` retourne 'un'
2754
 *
2755
 * @filtre
2756
 * @link http://php.net/manual/fr/function.reset.php
2757
 * @see filtre_end()
2758
 *
2759
 * @param array $array
2760
 * @return mixed|null|false
2761
 *    - null si $array n'est pas un tableau,
2762
 *    - false si le tableau est vide
2763
 *    - la première valeur du tableau sinon.
2764
 **/
2765
function filtre_reset($array) {
2766
	return !is_array($array) ? null : reset($array);
2767
}
2768
2769
/**
2770
 * Retourne la dernière valeur d'un tableau
2771
 *
2772
 * Plus précisément déplace le pointeur du tableau sur la dernière valeur et la retourne.
2773
 *
2774
 * @example `[(#LISTE{un,deux,trois}|end)]` retourne 'trois'
2775
 *
2776
 * @filtre
2777
 * @link http://php.net/manual/fr/function.end.php
2778
 * @see filtre_reset()
2779
 *
2780
 * @param array $array
2781
 * @return mixed|null|false
2782
 *    - null si $array n'est pas un tableau,
2783
 *    - false si le tableau est vide
2784
 *    - la dernière valeur du tableau sinon.
2785
 **/
2786
function filtre_end($array) {
2787
	return !is_array($array) ? null : end($array);
2788
}
2789
2790
/**
2791
 * Empile une valeur à la fin d'un tableau
2792
 *
2793
 * @example `[(#LISTE{un,deux,trois}|push{quatre}|print)]`
2794
 *
2795
 * @filtre
2796
 * @link https://www.spip.net/4571
2797
 * @link http://php.net/manual/fr/function.array-push.php
2798
 *
2799
 * @param array $array
2800
 * @param mixed $val
2801
 * @return array|string
2802
 *     - '' si $array n'est pas un tableau ou si echec.
2803
 *     - le tableau complété de la valeur sinon.
2804
 *
2805
 **/
2806
function filtre_push($array, $val) {
2807
	if (!is_array($array) or !array_push($array, $val)) {
2808
		return '';
2809
	}
2810
2811
	return $array;
2812
}
2813
2814
/**
2815
 * Indique si une valeur est contenue dans un tableau
2816
 *
2817
 * @example `[(#LISTE{un,deux,trois}|find{quatre}|oui) ... ]`
2818
 *
2819
 * @filtre
2820
 * @link https://www.spip.net/4575
2821
 * @see in_any() Assez proche, avec les paramètres tableau et valeur inversés.
2822
 *
2823
 * @param array $array
2824
 * @param mixed $val
2825
 * @return bool
2826
 *     - `false` si `$array` n'est pas un tableau
2827
 *     - `true` si la valeur existe dans le tableau, `false` sinon.
2828
 **/
2829
function filtre_find($array, $val) {
2830
	return (is_array($array) and in_array($val, $array));
2831
}
2832
2833
2834
/**
2835
 * Passer les url relatives à la css d'origine en url absolues
2836
 *
2837
 * @uses suivre_lien()
2838
 *
2839
 * @param string $contenu
2840
 *     Contenu du fichier CSS
2841
 * @param string $source
2842
 *     Chemin du fichier CSS
2843
 * @return string
2844
 *     Contenu avec urls en absolus
2845
 **/
2846
function urls_absolues_css($contenu, $source) {
2847
	$path = suivre_lien(url_absolue($source), './');
2848
2849
	return preg_replace_callback(
2850
		",url\s*\(\s*['\"]?([^'\"/#\s][^:]*)['\"]?\s*\),Uims",
2851
		function($x) use ($path) {
2852
			return "url('" . suivre_lien($path, $x[1]) . "')";
2853
		},
2854
		$contenu
2855
	);
2856
}
2857
2858
2859
/**
2860
 * Inverse le code CSS (left <--> right) d'une feuille de style CSS
2861
 *
2862
 * Récupère le chemin d'une CSS existante et :
2863
 *
2864
 * 1. regarde si une CSS inversée droite-gauche existe dans le meme répertoire
2865
 * 2. sinon la crée (ou la recrée) dans `_DIR_VAR/cache_css/`
2866
 *
2867
 * Si on lui donne à manger une feuille nommée `*_rtl.css` il va faire l'inverse.
2868
 *
2869
 * @filtre
2870
 * @example
2871
 *     ```
2872
 *     [<link rel="stylesheet" href="(#CHEMIN{css/perso.css}|direction_css)" type="text/css" />]
2873
 *     ```
2874
 * @param string $css
2875
 *     Chemin vers le fichier CSS
2876
 * @param string $voulue
2877
 *     Permet de forcer le sens voulu (en indiquant `ltr`, `rtl` ou un
2878
 *     code de langue). En absence, prend le sens de la langue en cours.
2879
 *
2880
 * @return string
2881
 *     Chemin du fichier CSS inversé
2882
 **/
2883
function direction_css($css, $voulue = '') {
2884
	if (!preg_match(',(_rtl)?\.css$,i', $css, $r)) {
2885
		return $css;
2886
	}
2887
2888
	// si on a precise le sens voulu en argument, le prendre en compte
2889
	if ($voulue = strtolower($voulue)) {
2890
		if ($voulue != 'rtl' and $voulue != 'ltr') {
2891
			$voulue = lang_dir($voulue);
2892
		}
2893
	} else {
2894
		$voulue = lang_dir();
2895
	}
2896
2897
	$r = count($r) > 1;
2898
	$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...
2899
	$dir = $r ? 'rtl' : 'ltr';
2900
	$ndir = $r ? 'ltr' : 'rtl';
2901
2902
	if ($voulue == $dir) {
2903
		return $css;
2904
	}
2905
2906
	if (
2907
		// url absolue
2908
		preg_match(",^https?:,i", $css)
2909
		// ou qui contient un ?
2910
		or (($p = strpos($css, '?')) !== false)
2911
	) {
2912
		$distant = true;
2913
		$cssf = parse_url($css);
2914
		$cssf = $cssf['path'] . ($cssf['query'] ? "?" . $cssf['query'] : "");
2915
		$cssf = preg_replace(',[?:&=],', "_", $cssf);
2916
	} else {
2917
		$distant = false;
2918
		$cssf = $css;
2919
		// 1. regarder d'abord si un fichier avec la bonne direction n'est pas aussi
2920
		//propose (rien a faire dans ce cas)
2921
		$f = preg_replace(',(_rtl)?\.css$,i', '_' . $ndir . '.css', $css);
2922
		if (@file_exists($f)) {
2923
			return $f;
2924
		}
2925
	}
2926
2927
	// 2.
2928
	$dir_var = sous_repertoire(_DIR_VAR, 'cache-css');
2929
	$f = $dir_var
2930
		. preg_replace(',.*/(.*?)(_rtl)?\.css,', '\1', $cssf)
2931
		. '.' . substr(md5($cssf), 0, 4) . '_' . $ndir . '.css';
2932
2933
	// la css peut etre distante (url absolue !)
2934
	if ($distant) {
2935
		include_spip('inc/distant');
2936
		$res = recuperer_url($css);
2937
		if (!$res or !$contenu = $res['page']) {
2938
			return $css;
2939
		}
2940
	} else {
2941 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...
2942
			and (_VAR_MODE != 'recalcul')
2943
		) {
2944
			return $f;
2945
		}
2946
		if (!lire_fichier($css, $contenu)) {
2947
			return $css;
2948
		}
2949
	}
2950
2951
2952
	// Inverser la direction gauche-droite en utilisant CSSTidy qui gere aussi les shorthands
2953
	include_spip("lib/csstidy/class.csstidy");
2954
	$parser = new csstidy();
2955
	$parser->set_cfg('optimise_shorthands', 0);
2956
	$parser->set_cfg('reverse_left_and_right', true);
2957
	$parser->parse($contenu);
2958
2959
	$contenu = $parser->print->plain();
2960
2961
2962
	// reperer les @import auxquels il faut propager le direction_css
2963
	preg_match_all(",\@import\s*url\s*\(\s*['\"]?([^'\"/][^:]*)['\"]?\s*\),Uims", $contenu, $regs);
2964
	$src = array();
2965
	$src_direction_css = array();
2966
	$src_faux_abs = array();
2967
	$d = dirname($css);
2968
	foreach ($regs[1] as $k => $import_css) {
2969
		$css_direction = direction_css("$d/$import_css", $voulue);
2970
		// si la css_direction est dans le meme path que la css d'origine, on tronque le path, elle sera passee en absolue
2971
		if (substr($css_direction, 0, strlen($d) + 1) == "$d/") {
2972
			$css_direction = substr($css_direction, strlen($d) + 1);
2973
		} // si la css_direction commence par $dir_var on la fait passer pour une absolue
2974
		elseif (substr($css_direction, 0, strlen($dir_var)) == $dir_var) {
2975
			$css_direction = substr($css_direction, strlen($dir_var));
2976
			$src_faux_abs["/@@@@@@/" . $css_direction] = $css_direction;
2977
			$css_direction = "/@@@@@@/" . $css_direction;
2978
		}
2979
		$src[] = $regs[0][$k];
2980
		$src_direction_css[] = str_replace($import_css, $css_direction, $regs[0][$k]);
2981
	}
2982
	$contenu = str_replace($src, $src_direction_css, $contenu);
2983
2984
	$contenu = urls_absolues_css($contenu, $css);
2985
2986
	// virer les fausses url absolues que l'on a mis dans les import
2987
	if (count($src_faux_abs)) {
2988
		$contenu = str_replace(array_keys($src_faux_abs), $src_faux_abs, $contenu);
2989
	}
2990
2991
	if (!ecrire_fichier($f, $contenu)) {
2992
		return $css;
2993
	}
2994
2995
	return $f;
2996
}
2997
2998
2999
/**
3000
 * Transforme les urls relatives d'un fichier CSS en absolues
3001
 *
3002
 * Récupère le chemin d'une css existante et crée (ou recrée) dans `_DIR_VAR/cache_css/`
3003
 * une css dont les url relatives sont passées en url absolues
3004
 *
3005
 * Le calcul n'est pas refait si le fichier cache existe déjà et que
3006
 * la source n'a pas été modifiée depuis.
3007
 *
3008
 * @uses recuperer_page() si l'URL source n'est pas sur le même site
3009
 * @uses urls_absolues_css()
3010
 *
3011
 * @param string $css
3012
 *     Chemin ou URL du fichier CSS source
3013
 * @return string
3014
 *     - Chemin du fichier CSS transformé (si source lisible et mise en cache réussie)
3015
 *     - Chemin ou URL du fichier CSS source sinon.
3016
 **/
3017
function url_absolue_css($css) {
3018
	if (!preg_match(',\.css$,i', $css, $r)) {
3019
		return $css;
3020
	}
3021
3022
	$url_absolue_css = url_absolue($css);
3023
3024
	$f = basename($css, '.css');
3025
	$f = sous_repertoire(_DIR_VAR, 'cache-css')
3026
		. preg_replace(",(.*?)(_rtl|_ltr)?$,", "\\1-urlabs-" . substr(md5("$css-urlabs"), 0, 4) . "\\2", $f)
3027
		. '.css';
3028
3029 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...
3030
		return $f;
3031
	}
3032
3033
	if ($url_absolue_css == $css) {
3034
		if (strncmp($GLOBALS['meta']['adresse_site'], $css, $l = strlen($GLOBALS['meta']['adresse_site'])) != 0
3035
			or !lire_fichier(_DIR_RACINE . substr($css, $l), $contenu)
3036
		) {
3037
			include_spip('inc/distant');
3038
			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...
3039
				return $css;
3040
			}
3041
		}
3042
	} elseif (!lire_fichier($css, $contenu)) {
3043
		return $css;
3044
	}
3045
3046
	// passer les url relatives a la css d'origine en url absolues
3047
	$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...
3048
3049
	// ecrire la css
3050
	if (!ecrire_fichier($f, $contenu)) {
3051
		return $css;
3052
	}
3053
3054
	return $f;
3055
}
3056
3057
3058
/**
3059
 * Récupère la valeur d'une clé donnée
3060
 * dans un tableau (ou un objet).
3061
 *
3062
 * @filtre
3063
 * @link https://www.spip.net/4572
3064
 * @example
3065
 *     ```
3066
 *     [(#VALEUR|table_valeur{cle/sous/element})]
3067
 *     ```
3068
 *
3069
 * @param mixed $table
3070
 *     Tableau ou objet PHP
3071
 *     (ou chaîne serialisée de tableau, ce qui permet d'enchaîner le filtre)
3072
 * @param string $cle
3073
 *     Clé du tableau (ou paramètre public de l'objet)
3074
 *     Cette clé peut contenir des caractères / pour sélectionner
3075
 *     des sous éléments dans le tableau, tel que `sous/element/ici`
3076
 *     pour obtenir la valeur de `$tableau['sous']['element']['ici']`
3077
 * @param mixed $defaut
3078
 *     Valeur par defaut retournée si la clé demandée n'existe pas
3079
 * @param bool  $conserver_null
3080
 *     Permet de forcer la fonction à renvoyer la valeur null d'un index
3081
 *     et non pas $defaut comme cela est fait naturellement par la fonction
3082
 *     isset. On utilise alors array_key_exists() à la place de isset().
3083
 * 
3084
 * @return mixed
3085
 *     Valeur trouvée ou valeur par défaut.
3086
 **/
3087
function table_valeur($table, $cle, $defaut = '', $conserver_null = false) {
3088
	foreach (explode('/', $cle) as $k) {
3089
3090
		$table = is_string($table) ? @unserialize($table) : $table;
3091
3092
		if (is_object($table)) {
3093
			$table = (($k !== "") and isset($table->$k)) ? $table->$k : $defaut;
3094
		} elseif (is_array($table)) {
3095
			if ($conserver_null) {
3096
				$table = array_key_exists($k, $table) ? $table[$k] : $defaut;
3097
			} else {
3098
				$table = isset($table[$k]) ? $table[$k] : $defaut;
3099
			}
3100
		} else {
3101
			$table = $defaut;
3102
		}
3103
	}
3104
3105
	return $table;
3106
}
3107
3108
/**
3109
 * Retrouve un motif dans un texte à partir d'une expression régulière
3110
 *
3111
 * S'appuie sur la fonction `preg_match()` en PHP
3112
 *
3113
 * @example
3114
 *    - `[(#TITRE|match{toto})]`
3115
 *    - `[(#TEXTE|match{^ceci$,Uims})]`
3116
 *    - `[(#TEXTE|match{truc(...)$, UimsS, 1})]` Capture de la parenthèse indiquée
3117
 *    - `[(#TEXTE|match{truc(...)$, 1})]` Équivalent, sans indiquer les modificateurs
3118
 *
3119
 * @filtre
3120
 * @link https://www.spip.net/4299
3121
 * @link http://php.net/manual/fr/function.preg-match.php Pour des infos sur `preg_match()`
3122
 *
3123
 * @param string $texte
3124
 *     Texte dans lequel chercher
3125
 * @param string|int $expression
3126
 *     Expression régulière de recherche, sans le délimiteur
3127
 * @param string $modif
3128
 *     - string : Modificateurs de l'expression régulière
3129
 *     - int : Numéro de parenthèse capturante
3130
 * @param int $capte
3131
 *     Numéro de parenthèse capturante
3132
 * @return bool|string
3133
 *     - false : l'expression n'a pas été trouvée
3134
 *     - true : expression trouvée, mais pas la parenthèse capturante
3135
 *     - string : expression trouvée.
3136
 **/
3137
function filtre_match_dist($texte, $expression, $modif = "UimsS", $capte = 0) {
3138
	if (intval($modif) and $capte == 0) {
3139
		$capte = $modif;
3140
		$modif = "UimsS";
3141
	}
3142
	$expression = str_replace("\/", "/", $expression);
3143
	$expression = str_replace("/", "\/", $expression);
3144
3145
	if (preg_match('/' . $expression . '/' . $modif, $texte, $r)) {
3146
		if (isset($r[$capte])) {
3147
			return $r[$capte];
3148
		} else {
3149
			return true;
3150
		}
3151
	}
3152
3153
	return false;
3154
}
3155
3156
3157
/**
3158
 * Remplacement de texte à base d'expression régulière
3159
 *
3160
 * @filtre
3161
 * @link https://www.spip.net/4309
3162
 * @see match()
3163
 * @example
3164
 *     ```
3165
 *     [(#TEXTE|replace{^ceci$,cela,UimsS})]
3166
 *     ```
3167
 *
3168
 * @param string $texte
3169
 *     Texte
3170
 * @param string $expression
3171
 *     Expression régulière
3172
 * @param string $replace
3173
 *     Texte de substitution des éléments trouvés
3174
 * @param string $modif
3175
 *     Modificateurs pour l'expression régulière.
3176
 * @return string
3177
 *     Texte
3178
 **/
3179
function replace($texte, $expression, $replace = '', $modif = "UimsS") {
3180
	$expression = str_replace("\/", "/", $expression);
3181
	$expression = str_replace("/", "\/", $expression);
3182
3183
	return preg_replace('/' . $expression . '/' . $modif, $replace, $texte);
3184
}
3185
3186
3187
/**
3188
 * Cherche les documents numerotés dans un texte traite par `propre()`
3189
 *
3190
 * Affecte la liste des doublons['documents']
3191
 *
3192
 * @param array $doublons
3193
 *     Liste des doublons
3194
 * @param string $letexte
3195
 *     Le texte
3196
 * @return string
3197
 *     Le texte
3198
 **/
3199
function traiter_doublons_documents(&$doublons, $letexte) {
3200
3201
	// Verifier dans le texte & les notes (pas beau, helas)
3202
	$t = $letexte . $GLOBALS['les_notes'];
3203
3204
	if (strstr($t, 'spip_document_') // evite le preg_match_all si inutile
3205
		and preg_match_all(
3206
			',<[^>]+\sclass=["\']spip_document_([0-9]+)[\s"\'],imsS',
3207
			$t, $matches, PREG_PATTERN_ORDER)
3208
	) {
3209
		if (!isset($doublons['documents'])) {
3210
			$doublons['documents'] = "";
3211
		}
3212
		$doublons['documents'] .= "," . join(',', $matches[1]);
3213
	}
3214
3215
	return $letexte;
3216
}
3217
3218
/**
3219
 * Filtre vide qui ne renvoie rien
3220
 *
3221
 * @example
3222
 *     `[(#CALCUL|vide)]` n'affichera pas le résultat du calcul
3223
 * @filtre
3224
 *
3225
 * @param mixed $texte
3226
 * @return string Chaîne vide
3227
 **/
3228
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...
3229
	return "";
3230
}
3231
3232
//
3233
// Filtres pour le modele/emb (embed document)
3234
//
3235
3236
/**
3237
 * Écrit des balises HTML `<param...>` à partir d'un tableau de données tel que `#ENV`
3238
 *
3239
 * Permet d'écrire les balises `<param>` à indiquer dans un `<object>`
3240
 * en prenant toutes les valeurs du tableau transmis.
3241
 *
3242
 * Certaines clés spécifiques à SPIP et aux modèles embed sont omises :
3243
 * id, lang, id_document, date, date_redac, align, fond, recurs, emb, dir_racine
3244
 *
3245
 * @example `[(#ENV*|env_to_params)]`
3246
 *
3247
 * @filtre
3248
 * @link https://www.spip.net/4005
3249
 *
3250
 * @param array|string $env
3251
 *      Tableau cle => valeur des paramètres à écrire, ou chaine sérialisée de ce tableau
3252
 * @param array $ignore_params
3253
 *      Permet de compléter les clés ignorées du tableau.
3254
 * @return string
3255
 *      Code HTML résultant
3256
 **/
3257 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...
3258
	$ignore_params = array_merge(
3259
		array('id', 'lang', 'id_document', 'date', 'date_redac', 'align', 'fond', '', 'recurs', 'emb', 'dir_racine'),
3260
		$ignore_params
3261
	);
3262
	if (!is_array($env)) {
3263
		$env = unserialize($env);
3264
	}
3265
	$texte = "";
3266
	if ($env) {
3267
		foreach ($env as $i => $j) {
3268
			if (is_string($j) and !in_array($i, $ignore_params)) {
3269
				$texte .= "<param name='" . attribut_html($i) . "'\n\tvalue='" . attribut_html($j) . "' />";
3270
			}
3271
		}
3272
	}
3273
3274
	return $texte;
3275
}
3276
3277
/**
3278
 * Écrit des attributs HTML à partir d'un tableau de données tel que `#ENV`
3279
 *
3280
 * Permet d'écrire des attributs d'une balise HTML en utilisant les données du tableau transmis.
3281
 * Chaque clé deviendra le nom de l'attribut (et la valeur, sa valeur)
3282
 *
3283
 * Certaines clés spécifiques à SPIP et aux modèles embed sont omises :
3284
 * id, lang, id_document, date, date_redac, align, fond, recurs, emb, dir_racine
3285
 *
3286
 * @example `<embed src='#URL_DOCUMENT' [(#ENV*|env_to_attributs)] width='#GET{largeur}' height='#GET{hauteur}'></embed>`
3287
 * @filtre
3288
 *
3289
 * @param array|string $env
3290
 *      Tableau cle => valeur des attributs à écrire, ou chaine sérialisée de ce tableau
3291
 * @param array $ignore_params
3292
 *      Permet de compléter les clés ignorées du tableau.
3293
 * @return string
3294
 *      Code HTML résultant
3295
 **/
3296 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...
3297
	$ignore_params = array_merge(
3298
		array('id', 'lang', 'id_document', 'date', 'date_redac', 'align', 'fond', '', 'recurs', 'emb', 'dir_racine'),
3299
		$ignore_params
3300
	);
3301
	if (!is_array($env)) {
3302
		$env = unserialize($env);
3303
	}
3304
	$texte = "";
3305
	if ($env) {
3306
		foreach ($env as $i => $j) {
3307
			if (is_string($j) and !in_array($i, $ignore_params)) {
3308
				$texte .= attribut_html($i) . "='" . attribut_html($j) . "' ";
3309
			}
3310
		}
3311
	}
3312
3313
	return $texte;
3314
}
3315
3316
3317
/**
3318
 * Concatène des chaînes
3319
 *
3320
 * @filtre
3321
 * @link https://www.spip.net/4150
3322
 * @example
3323
 *     ```
3324
 *     #TEXTE|concat{texte1,texte2,...}
3325
 *     ```
3326
 *
3327
 * @return string Chaînes concaténés
3328
 **/
3329
function concat() {
3330
	$args = func_get_args();
3331
3332
	return join('', $args);
3333
}
3334
3335
3336
/**
3337
 * Retourne le contenu d'un ou plusieurs fichiers
3338
 *
3339
 * Les chemins sont cherchés dans le path de SPIP
3340
 *
3341
 * @see balise_INCLURE_dist() La balise `#INCLURE` peut appeler cette fonction
3342
 *
3343
 * @param array|string $files
3344
 *     - array : Liste de fichiers
3345
 *     - string : fichier ou fichiers séparés par `|`
3346
 * @param bool $script
3347
 *     - si true, considère que c'est un fichier js à chercher `javascript/`
3348
 * @return string
3349
 *     Contenu du ou des fichiers, concaténé
3350
 **/
3351
function charge_scripts($files, $script = true) {
3352
	$flux = "";
3353
	foreach (is_array($files) ? $files : explode("|", $files) as $file) {
3354
		if (!is_string($file)) {
3355
			continue;
3356
		}
3357
		if ($script) {
3358
			$file = preg_match(",^\w+$,", $file) ? "javascript/$file.js" : '';
3359
		}
3360
		if ($file) {
3361
			$path = find_in_path($file);
3362
			if ($path) {
3363
				$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 3361 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...
3364
			}
3365
		}
3366
	}
3367
3368
	return $flux;
3369
}
3370
3371
3372
/**
3373
 * Produit une balise img avec un champ alt d'office si vide
3374
 *
3375
 * Attention le htmlentities et la traduction doivent être appliqués avant.
3376
 *
3377
 * @param string $img
3378
 * @param string $alt
3379
 * @param string $atts
3380
 * @param string $title
3381
 * @param array $options
3382
 *   chemin_image : utiliser chemin_image sur $img fourni, ou non (oui par dafaut)
3383
 *   utiliser_suffixe_size : utiliser ou non le suffixe de taille dans le nom de fichier de l'image
3384
 *   sous forme -xx.png (pour les icones essentiellement) (oui par defaut)
3385
 *   variante_svg_si_possible: utiliser l'image -xx.svg au lieu de -32.png par exemple (si la variante svg est disponible)
3386
 * @return string
3387
 */
3388
function http_img_pack($img, $alt, $atts = '', $title = '', $options = array()) {
3389
3390
	$img_file = $img;
3391
	if ($p = strpos($img_file, '?')) {
3392
		$img_file = substr($img_file,0, $p);
3393
	}
3394
	if (!isset($options['chemin_image']) or $options['chemin_image'] == true) {
3395
		$img_file = chemin_image($img);
3396
	}
3397
	else {
3398
		if (!isset($options['variante_svg_si_possible']) or $options['variante_svg_si_possible'] == true){
3399
			// on peut fournir une icone generique -xx.svg qui fera le job dans toutes les tailles, et qui est prioritaire sur le png
3400
			// si il y a un .svg a la bonne taille (-16.svg) a cote, on l'utilise en remplacement du -16.png
3401
			if (preg_match(',-(\d+)[.](png|gif|svg)$,', $img_file, $m)
3402
			  and $variante_svg_generique = substr($img_file, 0, -strlen($m[0])) . "-xx.svg"
3403
			  and file_exists($variante_svg_generique)) {
3404
				if ($variante_svg_size = substr($variante_svg_generique,0,-6) . $m[1] . ".svg" and file_exists($variante_svg_size)) {
3405
					$img_file = $variante_svg_size;
3406
				}
3407
				else {
3408
					$img_file = $variante_svg_generique;
3409
				}
3410
			}
3411
		}
3412
	}
3413
	if (stripos($atts, 'width') === false) {
3414
		// utiliser directement l'info de taille presente dans le nom
3415
		if ((!isset($options['utiliser_suffixe_size'])
3416
				or $options['utiliser_suffixe_size'] == true
3417
			  or strpos($img_file, '-xx.svg') !== false)
3418
			and (preg_match(',-([0-9]+)[.](png|gif|svg)$,', $img, $regs)
3419
					 or preg_match(',\?([0-9]+)px$,', $img, $regs))
3420
		) {
3421
			$largeur = $hauteur = intval($regs[1]);
3422
		} else {
3423
			$taille = taille_image($img_file);
3424
			list($hauteur, $largeur) = $taille;
3425
			if (!$hauteur or !$largeur) {
3426
				return "";
3427
			}
3428
		}
3429
		$atts .= " width='" . $largeur . "' height='" . $hauteur . "'";
3430
	}
3431
3432
	if (file_exists($img_file)) {
3433
		$img_file = timestamp($img_file);
3434
	}
3435
	if ($alt === false) {
3436
		$alt = '';
3437
	}
3438
	elseif($alt or $alt==='') {
3439
		$alt = " alt='".attribut_html($alt)."'";
3440
	}
3441
	else {
3442
		$alt = " alt='".attribut_html($title)."'";
3443
	}
3444
	return "<img src='$img_file'$alt"
3445
	. ($title ? ' title="' . attribut_html($title) . '"' : '')
3446
	. " " . ltrim($atts)
3447
	. " />";
3448
}
3449
3450
/**
3451
 * Générer une directive `style='background:url()'` à partir d'un fichier image
3452
 *
3453
 * @param string $img
3454
 * @param string $att
3455
 * @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...
3456
 * @return string
3457
 */
3458
function http_style_background($img, $att = '', $size=null) {
3459
	if ($size and is_numeric($size)){
3460
		$size = trim($size) . "px";
3461
	}
3462
	return " style='background" .
3463
		($att ? "" : "-image") . ": url(\"" . chemin_image($img) . "\")" . ($att ? (' ' . $att) : '') . ";"
3464
		. ($size ? "background-size:{$size};" : '')
3465
		. "'";
3466
}
3467
3468
3469
function helper_filtre_balise_img_svg_arguments($alt_or_size, $class_or_size, $size) {
3470
	$args = [$alt_or_size, $class_or_size, $size];
3471
	while (is_null(end($args)) and count($args)) {
3472
		array_pop($args);
3473
	}
3474
	if (!count($args)) {
3475
		return [null, null, null];
3476
	}
3477
	if (count($args) < 3) {
3478
		$maybe_size = array_pop($args);
3479
		// @2x
3480
		// @1.5x
3481
		// 512
3482
		// 512x*
3483
		// 512x300
3484
		if (!strlen($maybe_size)
3485
			or !preg_match(',^(@\d+(\.\d+)?x|\d+(x\*)?|\d+x\d+)$,', trim($maybe_size))) {
3486
			$args[] = $maybe_size;
3487
			$maybe_size = null;
3488
		}
3489
		while (count($args)<2) {
3490
			$args[] = null; // default alt or class
3491
		}
3492
		$args[] = $maybe_size;
3493
	}
3494
	return $args;
3495
}
3496
3497
function helper_filtre_balise_img_svg_size($img, $size) {
3498
	// si size est de la forme '@2x' c'est un coeff multiplicateur sur la densite
3499
	if (strpos($size, '@') === 0 and substr($size,-1) === 'x') {
3500
		$coef = floatval(substr($size, 1, -1));
3501
		list($h, $w) = taille_image($img);
3502
		$height = intval(round($h / $coef));
3503
		$width = intval(round($w / $coef));
3504
	}
3505
	// sinon c'est une valeur seule si image caree ou largeurxhauteur
3506
	else {
3507
		$size = explode('x', $size, 2);
3508
		$size = array_map('trim', $size);
3509
		$height = $width = intval(array_shift($size));
3510
3511
		if (count($size) and reset($size)) {
3512
			$height = array_shift($size);
3513
			if ($height === '*') {
3514
				list($h, $w) = taille_image($img);
3515
				$height = intval(round($h * $width / $w));
3516
			}
3517
		}
3518
	}
3519
3520
	return [$width, $height];
3521
}
3522
3523
/**
3524
 * Générer une balise HTML `img` à partir d'un nom de fichier et/ou renseigne son alt/class/width/height
3525
 * selon les arguments passés
3526
 *
3527
 * Le class et le alt peuvent etre omis et dans ce cas size peut-être renseigné comme dernier argument :
3528
 * [(#FICHIER|balise_img{@2x})]
3529
 * [(#FICHIER|balise_img{1024})]
3530
 * [(#FICHIER|balise_img{1024x*})]
3531
 * [(#FICHIER|balise_img{1024x640})]
3532
 * [(#FICHIER|balise_img{'un nuage',1024x640})]
3533
 * [(#FICHIER|balise_img{'un nuage','spip_logo',1024x640})]
3534
 * Si le alt ou la class sont ambigu et peuvent etre interpretes comme une taille, il suffit d'indiquer une taille vide pour lever l'ambiguite
3535
 * [(#FICHIER|balise_img{'@2x','',''})]
3536
 *
3537
 * @uses http_img_pack()
3538
 *
3539
 * @param string $img
3540
 *   chemin vers un fichier ou balise `<img src='...' />` (generee par un filtre image par exemple)
3541
 * @param string $alt
3542
 *   texte alternatif ; une valeur nulle pour explicitement ne pas avoir de balise alt sur l'image (au lieu d'un alt vide)
3543
 * @param string $class
0 ignored issues
show
Documentation introduced by
Should the type for parameter $class 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...
3544
 *   attribut class ; null par defaut (ie si img est une balise, son attribut class sera inchange. pas de class inseree
3545
 * @param string|int $size
0 ignored issues
show
Documentation introduced by
Should the type for parameter $size not be string|integer|null?

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

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

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

Loading history...
3546
 *   taille imposee
3547
 *     @2x : pour imposer une densite x2 (widht et height seront divisees par 2)
3548
 *     largeur : pour une image carree
3549
 *     largeurx* : pour imposer uniquement la largeur (un attribut height sera aussi ajoute, mais calcule automatiquement pour respecter le ratio initial de l'image)
3550
 *     largeurxhauteur pour fixer les 2 dimensions
3551
 * @return string
3552
 *     Code HTML de la balise IMG
3553
 */
3554
function filtre_balise_img_dist($img, $alt = '', $class = null, $size=null) {
3555
3556
	list($alt, $class, $size) = helper_filtre_balise_img_svg_arguments($alt, $class, $size);
3557
3558
	$img = trim($img);
3559
	if (strpos($img, '<img') === 0) {
3560
		if (!is_null($alt)) {
3561
			$img = inserer_attribut($img, 'alt', $alt);
3562
		}
3563 View Code Duplication
		if (!is_null($class)) {
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...
3564
			if (strlen($class)) {
3565
				$img = inserer_attribut($img, 'class', $class);
3566
			}
3567
			else {
3568
				$img = vider_attribut($img, 'class');
3569
			}
3570
		}
3571
	}
3572
	else {
3573
		$img = http_img_pack($img, $alt, $class ? " class='" . attribut_html($class) . "'" : '', '',
3574
				array('chemin_image' => false, 'utiliser_suffixe_size' => false));
3575
		if (is_null($alt)) {
3576
			$img = vider_attribut($img, 'alt');
3577
		}
3578
	}
3579
3580
	if ($img and !is_null($size) and strlen($size = trim($size))) {
3581
		list($width, $height) = helper_filtre_balise_img_svg_size($img, $size);
3582
3583
		$img = inserer_attribut($img, 'width', $width);
3584
		$img = inserer_attribut($img, 'height', $height);
3585
	}
3586
3587
	return $img;
3588
}
3589
3590
3591
/**
3592
 * Inserer un svg inline
3593
 * http://www.accede-web.com/notices/html-css-javascript/6-images-icones/6-2-svg-images-vectorielles/
3594
 *
3595
 * pour l'inserer avec une balise <img>, utiliser le filtre |balise_img
3596
 *
3597
 * Le class et le alt peuvent etre omis et dans ce cas size peut-être renseigné comme dernier argument :
3598
 * [(#FICHIER|balise_svg{1024x640})]
3599
 * [(#FICHIER|balise_svg{'un nuage',1024x640})]
3600
 * [(#FICHIER|balise_svg{'un nuage','spip_logo',1024x640})]
3601
 * Si le alt ou la class sont ambigu et peuvent etre interpretes comme une taille, il suffit d'indiquer une taille vide pour lever l'ambiguite
3602
 * [(#FICHIER|balise_svg{'un nuage','@2x',''})]
3603
 *
3604
 * @param string $img
3605
 *   chemin vers un fichier ou balise `<svg ... >... </svg>` deja preparee
3606
 * @param string $alt
3607
 *   texte alternatif ; une valeur nulle pour explicitement ne pas avoir de balise alt sur l'image (au lieu d'un alt vide)
3608
 * @param string $class
0 ignored issues
show
Documentation introduced by
Should the type for parameter $class 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...
3609
 *   attribut class ; null par defaut (ie si img est une balise, son attribut class sera inchange. pas de class inseree
3610
 * @param string|int $size
0 ignored issues
show
Documentation introduced by
Should the type for parameter $size not be string|integer|null?

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

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

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

Loading history...
3611
 *   taille imposee
3612
 *     @2x : pour imposer une densite x2 (widht et height seront divisees par 2)
3613
 *     largeur : pour une image carree
3614
 *     largeurx* : pour imposer uniquement la largeur (un attribut height sera aussi ajoute, mais calcule automatiquement pour respecter le ratio initial de l'image)
3615
 *     largeurxhauteur pour fixer les 2 dimensions
3616
 * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be string|boolean?

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...
3617
 *     Code HTML de la balise SVG
3618
 */
3619
function filtre_balise_svg_dist($img, $alt = '', $class = null, $size=null) {
3620
3621
	$img = trim($img);
3622
	$img_file = $img;
3623
	if (strpos($img, '<svg') === false){
3624
		if ($p = strpos($img_file, '?')){
3625
			$img_file = substr($img_file, 0, $p);
3626
		}
3627
3628
		if (!$img_file or !$svg = file_get_contents($img_file)){
3629
			return '';
3630
		}
3631
	}
3632
3633
	if (!preg_match(",<svg\b[^>]*>,UimsS", $svg, $match)) {
0 ignored issues
show
Bug introduced by
The variable $svg 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...
3634
		return '';
3635
	}
3636
3637
	list($alt, $class, $size) = helper_filtre_balise_img_svg_arguments($alt, $class, $size);
3638
3639
	$balise_svg = $match[0];
3640
	$balise_svg_source = $balise_svg;
3641
3642
	// entete XML à supprimer
3643
	$svg = preg_replace(',^\s*<\?xml[^>]*\?' . '>,', '', $svg);
3644
3645
	// IE est toujours mon ami
3646
	$balise_svg = inserer_attribut($balise_svg, 'focusable', 'false');
3647
3648
	// regler la classe
3649 View Code Duplication
	if (!is_null($class)) {
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...
3650
		if (strlen($class)) {
3651
			$balise_svg = inserer_attribut($balise_svg, 'class', $class);
3652
		}
3653
		else {
3654
			$balise_svg = vider_attribut($balise_svg, 'class');
3655
		}
3656
	}
3657
3658
	// regler le alt
3659
	if ($alt){
3660
		$balise_svg = inserer_attribut($balise_svg, 'role', 'img');
3661
		$id = "img-svg-title-" . substr(md5("$img_file:$svg:$alt"),0,4);
3662
		$balise_svg = inserer_attribut($balise_svg, 'aria-labelledby', $id);
3663
		$title = "<title id=\"$id\">" . entites_html($alt)."</title>\n";
3664
		$balise_svg .= $title;
3665
	}
3666
	else {
3667
		$balise_svg = inserer_attribut($balise_svg, 'aria-hidden', 'true');
3668
	}
3669
3670
	$svg = str_replace($balise_svg_source, $balise_svg, $svg);
3671
3672
	if (!is_null($size) and strlen($size = trim($size))) {
3673
		list($width, $height) = helper_filtre_balise_img_svg_size($svg, $size);
3674
3675
		if (!function_exists('svg_redimensionner')) {
3676
			include_spip('inc/svg');
3677
		}
3678
		$svg = svg_redimensionner($svg, $width, $height);
3679
	}
3680
3681
	return $svg;
3682
}
3683
3684
3685
3686
/**
3687
 * Affiche chaque valeur d'un tableau associatif en utilisant un modèle
3688
 *
3689
 * @example
3690
 *     - `[(#ENV*|unserialize|foreach)]`
3691
 *     - `[(#ARRAY{a,un,b,deux}|foreach)]`
3692
 *
3693
 * @filtre
3694
 * @link https://www.spip.net/4248
3695
 *
3696
 * @param array $tableau
3697
 *     Tableau de données à afficher
3698
 * @param string $modele
3699
 *     Nom du modèle à utiliser
3700
 * @return string
3701
 *     Code HTML résultant
3702
 **/
3703
function filtre_foreach_dist($tableau, $modele = 'foreach') {
3704
	$texte = '';
3705
	if (is_array($tableau)) {
3706
		foreach ($tableau as $k => $v) {
3707
			$res = recuperer_fond('modeles/' . $modele,
3708
				array_merge(array('cle' => $k), (is_array($v) ? $v : array('valeur' => $v)))
3709
			);
3710
			$texte .= $res;
3711
		}
3712
	}
3713
3714
	return $texte;
3715
}
3716
3717
3718
/**
3719
 * Obtient des informations sur les plugins actifs
3720
 *
3721
 * @filtre
3722
 * @uses liste_plugin_actifs() pour connaître les informations affichables
3723
 *
3724
 * @param string $plugin
3725
 *     Préfixe du plugin ou chaîne vide
3726
 * @param string $type_info
3727
 *     Type d'info demandée
3728
 * @param bool $reload
3729
 *     true (à éviter) pour forcer le recalcul du cache des informations des plugins.
3730
 * @return array|string|bool
3731
 *
3732
 *     - Liste sérialisée des préfixes de plugins actifs (si $plugin = '')
3733
 *     - Suivant $type_info, avec $plugin un préfixe
3734
 *         - est_actif : renvoie true s'il est actif, false sinon
3735
 *         - x : retourne l'information x du plugin si présente (et plugin actif)
3736
 *         - tout : retourne toutes les informations du plugin actif
3737
 **/
3738
function filtre_info_plugin_dist($plugin, $type_info, $reload = false) {
3739
	include_spip('inc/plugin');
3740
	$plugin = strtoupper($plugin);
3741
	$plugins_actifs = liste_plugin_actifs();
3742
3743
	if (!$plugin) {
3744
		return serialize(array_keys($plugins_actifs));
3745
	} elseif (empty($plugins_actifs[$plugin]) and !$reload) {
3746
		return '';
3747
	} elseif (($type_info == 'est_actif') and !$reload) {
3748
		return $plugins_actifs[$plugin] ? 1 : 0;
3749
	} elseif (isset($plugins_actifs[$plugin][$type_info]) and !$reload) {
3750
		return $plugins_actifs[$plugin][$type_info];
3751
	} else {
3752
		$get_infos = charger_fonction('get_infos', 'plugins');
3753
		// On prend en compte les extensions
3754
		if (!is_dir($plugins_actifs[$plugin]['dir_type'])) {
3755
			$dir_plugins = constant($plugins_actifs[$plugin]['dir_type']);
3756
		} else {
3757
			$dir_plugins = $plugins_actifs[$plugin]['dir_type'];
3758
		}
3759
		if (!$infos = $get_infos($plugins_actifs[$plugin]['dir'], $reload, $dir_plugins)) {
3760
			return '';
3761
		}
3762
		if ($type_info == 'tout') {
3763
			return $infos;
3764
		} elseif ($type_info == 'est_actif') {
3765
			return $infos ? 1 : 0;
3766
		} else {
3767
			return strval($infos[$type_info]);
3768
		}
3769
	}
3770
}
3771
3772
3773
/**
3774
 * Affiche la puce statut d'un objet, avec un menu rapide pour changer
3775
 * de statut si possibilité de l'avoir
3776
 *
3777
 * @see inc_puce_statut_dist()
3778
 *
3779
 * @filtre
3780
 *
3781
 * @param int $id_objet
3782
 *     Identifiant de l'objet
3783
 * @param string $statut
3784
 *     Statut actuel de l'objet
3785
 * @param int $id_rubrique
3786
 *     Identifiant du parent
3787
 * @param string $type
3788
 *     Type d'objet
3789
 * @param bool $ajax
3790
 *     Indique s'il ne faut renvoyer que le coeur du menu car on est
3791
 *     dans une requete ajax suite à un post de changement rapide
3792
 * @return string
3793
 *     Code HTML de l'image de puce de statut à insérer (et du menu de changement si présent)
3794
 */
3795
function puce_changement_statut($id_objet, $statut, $id_rubrique, $type, $ajax = false) {
3796
	$puce_statut = charger_fonction('puce_statut', 'inc');
3797
3798
	return $puce_statut($id_objet, $statut, $id_rubrique, $type, $ajax);
3799
}
3800
3801
3802
/**
3803
 * Affiche la puce statut d'un objet, avec un menu rapide pour changer
3804
 * de statut si possibilité de l'avoir
3805
 *
3806
 * Utilisable sur tout objet qui a declaré ses statuts
3807
 *
3808
 * @example
3809
 *     [(#STATUT|puce_statut{article})] affiche une puce passive
3810
 *     [(#STATUT|puce_statut{article,#ID_ARTICLE,#ID_RUBRIQUE})] affiche une puce avec changement rapide
3811
 *
3812
 * @see inc_puce_statut_dist()
3813
 *
3814
 * @filtre
3815
 *
3816
 * @param string $statut
3817
 *     Statut actuel de l'objet
3818
 * @param string $objet
3819
 *     Type d'objet
3820
 * @param int $id_objet
3821
 *     Identifiant de l'objet
3822
 * @param int $id_parent
3823
 *     Identifiant du parent
3824
 * @return string
3825
 *     Code HTML de l'image de puce de statut à insérer (et du menu de changement si présent)
3826
 */
3827
function filtre_puce_statut_dist($statut, $objet, $id_objet = 0, $id_parent = 0) {
3828
	static $puce_statut = null;
3829
	if (!$puce_statut) {
3830
		$puce_statut = charger_fonction('puce_statut', 'inc');
3831
	}
3832
3833
	return $puce_statut($id_objet, $statut, $id_parent, $objet, false,
3834
		objet_info($objet, 'editable') ? _ACTIVER_PUCE_RAPIDE : false);
3835
}
3836
3837
3838
/**
3839
 * Encoder un contexte pour l'ajax
3840
 *
3841
 * Encoder le contexte, le signer avec une clé, le crypter
3842
 * avec le secret du site, le gziper si possible.
3843
 *
3844
 * L'entrée peut-être sérialisée (le `#ENV**` des fonds ajax et ajax_stat)
3845
 *
3846
 * @see  decoder_contexte_ajax()
3847
 * @uses calculer_cle_action()
3848
 *
3849
 * @param string|array $c
3850
 *   contexte, peut etre un tableau serialize
3851
 * @param string $form
3852
 *   nom du formulaire eventuel
3853
 * @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...
3854
 *   contenu a emboiter dans le conteneur ajax
3855
 * @param string $ajaxid
3856
 *   ajaxid pour cibler le bloc et forcer sa mise a jour
3857
 * @return string
3858
 *   hash du contexte
3859
 */
3860
function encoder_contexte_ajax($c, $form = '', $emboite = null, $ajaxid = '') {
3861
	if (is_string($c)
3862
		and @unserialize($c) !== false
3863
	) {
3864
		$c = unserialize($c);
3865
	}
3866
3867
	// supprimer les parametres debut_x
3868
	// pour que la pagination ajax ne soit pas plantee
3869
	// si on charge la page &debut_x=1 : car alors en cliquant sur l'item 0,
3870
	// le debut_x=0 n'existe pas, et on resterait sur 1
3871
	if (is_array($c)) {
3872
		foreach ($c as $k => $v) {
3873
			if (strpos($k, 'debut_') === 0) {
3874
				unset($c[$k]);
3875
			}
3876
		}
3877
	}
3878
3879
	if (!function_exists('calculer_cle_action')) {
3880
		include_spip("inc/securiser_action");
3881
	}
3882
3883
	$c = serialize($c);
3884
	$cle = calculer_cle_action($form . $c);
3885
	$c = "$cle:$c";
3886
3887
	// on ne stocke pas les contextes dans des fichiers en cache
3888
	// par defaut, sauf si cette configuration a été forcée
3889
	// OU que la longueur de l’argument géneré est plus long
3890
	// que ce qui est toléré.
3891
	$cache_contextes_ajax = (defined('_CACHE_CONTEXTES_AJAX') and _CACHE_CONTEXTES_AJAX);
3892
	if (!$cache_contextes_ajax) {
3893
		$env = $c;
3894
		if (function_exists('gzdeflate') && function_exists('gzinflate')) {
3895
			$env = gzdeflate($env);
3896
		}
3897
		$env = _xor($env);
3898
		$env = base64_encode($env);
3899
		$len = strlen($env);
3900
		// Si l’url est trop longue pour le navigateur
3901
		$max_len = _CACHE_CONTEXTES_AJAX_SUR_LONGUEUR;
3902
		if ($len > $max_len) {
3903
			$cache_contextes_ajax = true;
3904
			spip_log("Contextes AJAX forces en fichiers !"
3905
				. " Cela arrive lorsque la valeur du contexte" 
3906
				. " depasse la longueur maximale autorisee ($max_len). Ici : $len."
3907
				, _LOG_AVERTISSEMENT);
3908
		}
3909
		// Sinon si Suhosin est actif et a une la valeur maximale des variables en GET...
3910
		elseif (
3911
			$max_len = @ini_get('suhosin.get.max_value_length')
3912
			and $max_len < $len
3913
		) {
3914
			$cache_contextes_ajax = true;
3915
			spip_log("Contextes AJAX forces en fichiers !"
3916
				. " Cela arrive lorsque la valeur du contexte"
3917
				. " depasse la longueur maximale autorisee par Suhosin"
3918
				. " ($max_len) dans 'suhosin.get.max_value_length'. Ici : $len."
3919
				. " Vous devriez modifier les parametres de Suhosin"
3920
				. " pour accepter au moins 1024 caracteres.", _LOG_AVERTISSEMENT);
3921
		} 
3922
3923
	}
3924
3925
	if ($cache_contextes_ajax) {
3926
		$dir = sous_repertoire(_DIR_CACHE, 'contextes');
3927
		// stocker les contextes sur disque et ne passer qu'un hash dans l'url
3928
		$md5 = md5($c);
3929
		ecrire_fichier("$dir/c$md5", $c);
3930
		$env = $md5;
3931
	}
3932
3933
	if ($emboite === null) {
3934
		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...
3935
	}
3936
	if (!trim($emboite)) {
3937
		return "";
3938
	}
3939
	// toujours encoder l'url source dans le bloc ajax
3940
	$r = self();
3941
	$r = ' data-origin="' . $r . '"';
3942
	$class = 'ajaxbloc';
3943
	if ($ajaxid and is_string($ajaxid)) {
3944
		// ajaxid est normalement conforme a un nom de classe css
3945
		// on ne verifie pas la conformite, mais on passe entites_html par dessus par precaution
3946
		$class .= ' ajax-id-' . entites_html($ajaxid);
3947
	}
3948
3949
	return "<div class='$class' " . "data-ajax-env='$env'$r>\n$emboite</div><!--ajaxbloc-->\n";
3950
}
3951
3952
/**
3953
 * Décoder un hash de contexte pour l'ajax
3954
 *
3955
 * Précude inverse de `encoder_contexte_ajax()`
3956
 *
3957
 * @see  encoder_contexte_ajax()
3958
 * @uses calculer_cle_action()
3959
 *
3960
 * @param string $c
3961
 *   hash du contexte
3962
 * @param string $form
3963
 *   nom du formulaire eventuel
3964
 * @return array|string|bool
3965
 *   - array|string : contexte d'environnement, possiblement sérialisé
3966
 *   - false : erreur de décodage
3967
 */
3968
function decoder_contexte_ajax($c, $form = '') {
3969
	if (!function_exists('calculer_cle_action')) {
3970
		include_spip("inc/securiser_action");
3971
	}
3972
	if (((defined('_CACHE_CONTEXTES_AJAX') and _CACHE_CONTEXTES_AJAX) or strlen($c) == 32)
3973
		and $dir = sous_repertoire(_DIR_CACHE, 'contextes')
3974
		and lire_fichier("$dir/c$c", $contexte)
3975
	) {
3976
		$c = $contexte;
3977
	} else {
3978
		$c = @base64_decode($c);
3979
		$c = _xor($c);
3980
		if (function_exists('gzdeflate') && function_exists('gzinflate')) {
3981
			$c = @gzinflate($c);
3982
		}
3983
	}
3984
3985
	// extraire la signature en debut de contexte
3986
	// et la verifier avant de deserializer
3987
	// format : signature:donneesserializees
3988 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...
3989
		$cle = substr($c,0,$p);
3990
		$c = substr($c,$p+1);
3991
3992
		if ($cle == calculer_cle_action($form . $c)) {
3993
			$env = @unserialize($c);
3994
			return $env;
3995
		}
3996
	}
3997
3998
	return false;
3999
}
4000
4001
4002
/**
4003
 * Encrypte ou décrypte un message
4004
 *
4005
 * @link http://www.php.net/manual/fr/language.operators.bitwise.php#81358
4006
 *
4007
 * @param string $message
4008
 *    Message à encrypter ou décrypter
4009
 * @param null|string $key
4010
 *    Clé de cryptage / décryptage.
4011
 *    Une clé sera calculée si non transmise
4012
 * @return string
4013
 *    Message décrypté ou encrypté
4014
 **/
4015
function _xor($message, $key = null) {
4016
	if (is_null($key)) {
4017
		if (!function_exists('calculer_cle_action')) {
4018
			include_spip("inc/securiser_action");
4019
		}
4020
		$key = pack("H*", calculer_cle_action('_xor'));
4021
	}
4022
4023
	$keylen = strlen($key);
4024
	$messagelen = strlen($message);
4025
	for ($i = 0; $i < $messagelen; $i++) {
4026
		$message[$i] = ~($message[$i] ^ $key[$i % $keylen]);
4027
	}
4028
4029
	return $message;
4030
}
4031
4032
/**
4033
 * Retourne une URL de réponse de forum (aucune action ici)
4034
 *
4035
 * @see filtre_url_reponse_forum() du plugin forum (prioritaire)
4036
 * @note
4037
 *   La vraie fonction est dans le plugin forum,
4038
 *   mais on évite ici une erreur du compilateur en absence du plugin
4039
 * @param string $texte
4040
 * @return string
4041
 */
4042
function url_reponse_forum($texte) { return $texte; }
4043
4044
/**
4045
 * retourne une URL de suivi rss d'un forum (aucune action ici)
4046
 *
4047
 * @see filtre_url_rss_forum() du plugin forum (prioritaire)
4048
 * @note
4049
 *   La vraie fonction est dans le plugin forum,
4050
 *   mais on évite ici une erreur du compilateur en absence du plugin
4051
 * @param string $texte
4052
 * @return string
4053
 */
4054
function url_rss_forum($texte) { return $texte; }
4055
4056
4057
/**
4058
 * Génère des menus avec liens ou `<strong class='on'>` non clicable lorsque
4059
 * l'item est sélectionné
4060
 * Le parametre $on peut recevoir un selecteur simplifié de type 'a.active' 'strong.active.expose'
4061
 * pour préciser la balise (a, span ou strong uniquement) et la/les classes à utiliser quand on est expose
4062
 *
4063
 * @filtre
4064
 * @link https://www.spip.net/4004
4065
 * @example
4066
 *   ```
4067
 *   [(#URL_RUBRIQUE|lien_ou_expose{#TITRE, #ENV{test}|=={en_cours}})]
4068
 *   [(#URL_RUBRIQUE|lien_ou_expose{#TITRE, #ENV{test}|=={en_cours}|?{a.monlien.active}, 'monlien'})]
4069
 *   ```
4070
 *
4071
 *
4072
 * @param string $url
4073
 *   URL du lien
4074
 * @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...
4075
 *   Texte du lien
4076
 * @param bool $on
4077
 *   État exposé ou non (génère un lien). Si $on vaut true ou 1 ou ' ', l'etat expose est rendu par un <strong class='on'>...</strong>
4078
 *   Si $on est de la forme span.active.truc par exemple, l'etat expose est rendu par <span class='active truc'>...</span> Seules les balises a, span et strong sont acceptees dans ce cas
4079
 * @param string $class
4080
 *   Classes CSS ajoutées au lien
4081
 * @param string $title
4082
 *   Title ajouté au lien
4083
 * @param string $rel
4084
 *   Attribut `rel` ajouté au lien
4085
 * @param string $evt
4086
 *   Complement à la balise `a` pour gérer un événement javascript,
4087
 *   de la forme ` onclick='...'`
4088
 * @return string
4089
 *   Code HTML
4090
 */
4091
function lien_ou_expose($url, $libelle = null, $on = false, $class = "", $title = "", $rel = "", $evt = '') {
4092
	if ($on) {
4093
		$bal = 'strong';
4094
		$class = "";
4095
		$att = "";
4096
		// si $on passe la balise et optionnelement une ou ++classe
4097
		// a.active span.selected.active etc....
4098
		if (is_string($on) and (strncmp($on, 'a', 1)==0 or strncmp($on, 'span', 4)==0 or strncmp($on, 'strong', 6)==0)){
4099
			$on = explode(".", $on);
4100
			// on verifie que c'est exactement une des 3 balises a, span ou strong
4101
			if (in_array(reset($on), array('a', 'span', 'strong'))){
4102
				$bal = array_shift($on);
4103
				$class = implode(" ", $on);
4104
				if ($bal=="a"){
4105
					$att = 'href="#" ';
4106
				}
4107
			}
4108
		}
4109
		$att .= 'class="' . ($class ? attribut_html($class) . ' ' : '') . (defined('_LIEN_OU_EXPOSE_CLASS_ON') ? _LIEN_OU_EXPOSE_CLASS_ON : 'on') . '"';
4110
	} else {
4111
		$bal = 'a';
4112
		$att = "href='$url'"
4113
			. ($title ? " title='" . attribut_html($title) . "'" : '')
4114
			. ($class ? " class='" . attribut_html($class) . "'" : '')
4115
			. ($rel ? " rel='" . attribut_html($rel) . "'" : '')
4116
			. $evt;
4117
	}
4118
	if ($libelle === null) {
4119
		$libelle = $url;
4120
	}
4121
4122
	return "<$bal $att>$libelle</$bal>";
4123
}
4124
4125
4126
/**
4127
 * Afficher un message "un truc"/"N trucs"
4128
 * Les items sont à indiquer comme pour la fonction _T() sous la forme :
4129
 * "module:chaine"
4130
 *
4131
 * @param int $nb : le nombre
4132
 * @param string $chaine_un : l'item de langue si $nb vaut un
4133
 * @param string $chaine_plusieurs : l'item de lanque si $nb >= 2
4134
 * @param string $var : La variable à remplacer par $nb dans l'item de langue (facultatif, défaut "nb")
4135
 * @param array $vars : Les autres variables nécessaires aux chaines de langues (facultatif)
4136
 * @return string : la chaine de langue finale en utilisant la fonction _T()
4137
 */
4138
function singulier_ou_pluriel($nb, $chaine_un, $chaine_plusieurs, $var = 'nb', $vars = array()) {
4139
	static $local_singulier_ou_pluriel = array();
4140
4141
	// si nb=0 ou pas de $vars valide on retourne une chaine vide, a traiter par un |sinon
4142
	if (!is_numeric($nb) or $nb == 0) {
4143
		return "";
4144
	}
4145
	if (!is_array($vars)) {
4146
		return "";
4147
	}
4148
4149
	$langue = $GLOBALS['spip_lang'];
4150
	if (!isset($local_singulier_ou_pluriel[$langue])) {
4151
		$local_singulier_ou_pluriel[$langue] = false;
4152
		if ($f = charger_fonction("singulier_ou_pluriel_${langue}", 'inc', true)
4153
		  or $f = charger_fonction("singulier_ou_pluriel", 'inc', true)) {
4154
			$local_singulier_ou_pluriel[$langue] = $f;
4155
		}
4156
	}
4157
4158
	// si on a une surcharge on l'utilise
4159
	if ($local_singulier_ou_pluriel[$langue]) {
4160
		return ($local_singulier_ou_pluriel[$langue])($nb, $chaine_un, $chaine_plusieurs, $var, $vars);
4161
	}
4162
4163
	// sinon traitement par defaut
4164
	$vars[$var] = $nb;
4165
	if ($nb >= 2) {
4166
		return _T($chaine_plusieurs, $vars);
4167
	} else {
4168
		return _T($chaine_un, $vars);
4169
	}
4170
}
4171
4172
4173
/**
4174
 * Fonction de base pour une icone dans un squelette.
4175
 * Il peut s'agir soit d'un simple lien, structure html : `<span><a><img><b>texte</b></span>`,
4176
 * soit d'un bouton d'action, structure identique au retour de `#BOUTON_ACTION`.
4177
 *
4178
 * @param string $type
4179
 *  'lien' ou 'bouton'
4180
 * @param string $lien
4181
 *  url
4182
 * @param string $texte
4183
 *  texte du lien / alt de l'image
4184
 * @param string $fond
4185
 *  objet avec ou sans son extension et sa taille (article, article-24, article-24.png)
4186
 * @param string $fonction
4187
 *  new/del/edit
4188
 * @param string $class
4189
 *  Classes supplémentaires (horizontale, verticale, ajax…)
4190
 * @param string $javascript
4191
 *  code javascript tel que "onclick='...'" par exemple
4192
 *  ou texte du message de confirmation s'il s'agit d'un bouton
4193
 * @return string
4194
 */
4195
function prepare_icone_base($type, $lien, $texte, $fond, $fonction = '', $class = '', $javascript = '') {
4196
4197
	$class_lien = $class_bouton = $class;
4198
4199
	// Normaliser la fonction et compléter la classe en fonction
4200
	if (in_array($fonction, ['del', 'supprimer.gif'])) {
4201
		$class_lien .= ' danger';
4202
		$class_bouton .= ' btn_danger';
4203
	} elseif ($fonction == 'rien.gif') {
4204
		$fonction = '';
4205
	} elseif ($fonction == 'delsafe') {
4206
		$fonction = 'del';
4207
	}
4208
4209
	$fond_origine = $fond;
4210
	// Remappage des icone : article-24.png+new => article-new-24.png
4211 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...
4212
		list($fond, $fonction) = $icone_renommer($fond, $fonction);
4213
	}
4214
4215
	// Ajouter le type d'objet dans la classe
4216
	$objet_type = substr(basename($fond), 0, -4);
4217
	$class_lien .= " $objet_type";
4218
	$class_bouton .= " $objet_type";
4219
4220
	// Texte
4221
	$alt = attribut_html($texte);
4222
	$title = " title=\"$alt\""; // est-ce pertinent de doubler le alt par un title ?
4223
4224
	// Liens : préparer les classes ajax
4225
	$ajax = '';
4226
	if ($type === 'lien') {
4227
		if (strpos($class_lien, 'ajax') !== false) {
4228
			$ajax = 'ajax';
4229
			if (strpos($class_lien, 'preload') !== false) {
4230
				$ajax .= ' preload';
4231
			}
4232
			if (strpos($class_lien, 'nocache') !== false) {
4233
				$ajax .= ' nocache';
4234
			}
4235
			$ajax = " class='$ajax'";
4236
		}
4237
	}
4238
4239
	// Repérer la taille et l'ajouter dans la classe
4240
	$size = 24;
4241
	if (preg_match("/-([0-9]{1,3})[.](gif|png|svg)$/i", $fond, $match)
4242
	  or preg_match("/-([0-9]{1,3})([.](gif|png|svg))?$/i", $fond_origine, $match)) {
4243
		$size = $match[1];
4244
	}
4245
	$class_lien .= " s$size";
4246
	$class_bouton .= " s$size";
4247
4248
	// Icône
4249
	$icone = http_img_pack($fond, $alt, "width='$size' height='$size'");
4250
	$icone = "<span class=\"icone-image".($fonction ? " icone-fonction icone-fonction-$fonction" : "") . "\">$icone</span>";
4251
4252
	// Markup final
4253
	if ($type == 'lien') {
4254
		return "<span class='icone $class_lien'>"
4255
		. "<a href='$lien'$title$ajax$javascript>"
4256
		. $icone
4257
		. "<b>$texte</b>"
4258
		. "</a></span>\n";
4259
	} else {
4260
		return bouton_action("$icone $texte", $lien, $class_bouton, $javascript, $alt);
4261
	}
4262
}
4263
4264
/**
4265
 * Crée un lien ayant une icone
4266
 *
4267
 * @uses prepare_icone_base()
4268
 *
4269
 * @param string $lien
4270
 *     URL du lien
4271
 * @param string $texte
4272
 *     Texte du lien
4273
 * @param string $fond
4274
 *     Objet avec ou sans son extension et sa taille (article, article-24, article-24.png)
4275
 * @param string $fonction
4276
 *     Fonction du lien (`edit`, `new`, `del`)
4277
 * @param string $class
4278
 *     Classe CSS, tel que `left`, `right` pour définir un alignement
4279
 * @param string $javascript
4280
 *     Javascript ajouté sur le lien
4281
 * @return string
4282
 *     Code HTML du lien
4283
 **/
4284
function icone_base($lien, $texte, $fond, $fonction = "", $class = "", $javascript = "") {
4285
	return prepare_icone_base('lien', $lien, $texte, $fond, $fonction, $class, $javascript);
4286
}
4287
4288
/**
4289
 * Crée un lien précédé d'une icone au dessus du texte
4290
 *
4291
 * @uses icone_base()
4292
 * @see  icone_verticale() Pour un usage dans un code PHP.
4293
 *
4294
 * @filtre
4295
 * @example
4296
 *     ```
4297
 *     [(#AUTORISER{voir,groupemots,#ID_GROUPE})
4298
 *         [(#URL_ECRIRE{groupe_mots,id_groupe=#ID_GROUPE}
4299
 *            |icone_verticale{<:mots:icone_voir_groupe_mots:>,groupe_mots-24.png,'',left})]
4300
 *    ]
4301
 *     ```
4302
 *
4303
 * @param string $lien
4304
 *     URL du lien
4305
 * @param string $texte
4306
 *     Texte du lien
4307
 * @param string $fond
4308
 *     Objet avec ou sans son extension et sa taille (article, article-24, article-24.png)
4309
 * @param string $fonction
4310
 *     Fonction du lien (`edit`, `new`, `del`)
4311
 * @param string $class
4312
 *     Classe CSS à ajouter, tel que `left`, `right`, `center` pour définir un alignement.
4313
 *     Il peut y en avoir plusieurs : `left ajax`
4314
 * @param string $javascript
4315
 *     Javascript ajouté sur le lien
4316
 * @return string
4317
 *     Code HTML du lien
4318
 **/
4319
function filtre_icone_verticale_dist($lien, $texte, $fond, $fonction = "", $class = "", $javascript = "") {
4320
	return icone_base($lien, $texte, $fond, $fonction, "verticale $class", $javascript);
4321
}
4322
4323
/**
4324
 * Crée un lien précédé d'une icone horizontale
4325
 *
4326
 * @uses icone_base()
4327
 * @see  icone_horizontale() Pour un usage dans un code PHP.
4328
 *
4329
 * @filtre
4330
 * @example
4331
 *     En tant que filtre dans un squelettes :
4332
 *     ```
4333
 *     [(#URL_ECRIRE{sites}|icone_horizontale{<:sites:icone_voir_sites_references:>,site-24.png})]
4334
 *
4335
 *     [(#AUTORISER{supprimer,groupemots,#ID_GROUPE}|oui)
4336
 *         [(#URL_ACTION_AUTEUR{supprimer_groupe_mots,#ID_GROUPE,#URL_ECRIRE{mots}}
4337
 *             |icone_horizontale{<:mots:icone_supprimer_groupe_mots:>,groupe_mots,del})]
4338
 *     ]
4339
 *     ```
4340
 *
4341
 *     En tant que filtre dans un code php :
4342
 *     ```
4343
 *     $icone_horizontale=chercher_filtre('icone_horizontale');
4344
 *     $icone = $icone_horizontale(generer_url_ecrire("stats_visites","id_article=$id_article"),
4345
 *         _T('statistiques:icone_evolution_visites', array('visites' => $visites)),
4346
 *         "statistique-24.png");
4347
 *     ```
4348
 *
4349
 * @param string $lien
4350
 *     URL du lien
4351
 * @param string $texte
4352
 *     Texte du lien
4353
 * @param string $fond
4354
 *     Objet avec ou sans son extension et sa taille (article, article-24, article-24.png)
4355
 * @param string $fonction
4356
 *     Fonction du lien (`edit`, `new`, `del`)
4357
 * @param string $class
4358
 *     Classe CSS à ajouter
4359
 * @param string $javascript
4360
 *     Javascript ajouté sur le lien
4361
 * @return string
4362
 *     Code HTML du lien
4363
 **/
4364
function filtre_icone_horizontale_dist($lien, $texte, $fond, $fonction = "", $class = "", $javascript = "") {
4365
	return icone_base($lien, $texte, $fond, $fonction, "horizontale $class", $javascript);
4366
}
4367
4368
/**
4369
 * Crée un bouton d'action intégrant une icone horizontale
4370
 *
4371
 * @uses prepare_icone_base()
4372
 *
4373
 * @filtre
4374
 * @example
4375
 *     ```
4376
 *     [(#URL_ACTION_AUTEUR{supprimer_mot, #ID_MOT, #URL_ECRIRE{groupe_mots,id_groupe=#ID_GROUPE}}
4377
 *         |bouton_action_horizontal{<:mots:info_supprimer_mot:>,mot-24.png,del,#ARRAY{bouton,btn_large}})]
4378
 *     ```
4379
 *
4380
 * @param string $lien
4381
 *     URL de l'action
4382
 * @param string $texte
4383
 *     Texte du bouton
4384
 * @param string $fond
4385
 *     Objet avec ou sans son extension et sa taille (article, article-24, article-24.png)
4386
 * @param string $fonction
4387
 *     Fonction du bouton (`edit`, `new`, `del`)
4388
 * @param string $class
4389
 *     Classes à ajouter au bouton, à l'exception de `ajax` qui est placé sur le formulaire.
4390
 * @param string $confirm
4391
 *     Message de confirmation à ajouter en javascript sur le bouton
4392
 * @return string
4393
 *     Code HTML du lien
4394
 **/
4395
function filtre_bouton_action_horizontal_dist($lien, $texte, $fond, $fonction = "", $class = "", $confirm = "") {
4396
	return prepare_icone_base('bouton', $lien, $texte, $fond, $fonction, $class, $confirm);
4397
}
4398
4399
/**
4400
 * Filtre `icone` pour compatibilité mappé sur `icone_base`
4401
 *
4402
 * @uses icone_base()
4403
 * @see  filtre_icone_verticale_dist()
4404
 *
4405
 * @filtre
4406
 * @deprecated Utiliser le filtre `icone_verticale`
4407
 *
4408
 * @param string $lien
4409
 *     URL du lien
4410
 * @param string $texte
4411
 *     Texte du lien
4412
 * @param string $fond
4413
 *     Nom de l'image utilisée
4414
 * @param string $align
4415
 *     Classe CSS d'alignement (`left`, `right`, `center`)
4416
 * @param string $fonction
4417
 *     Fonction du lien (`edit`, `new`, `del`)
4418
 * @param string $class
4419
 *     Classe CSS à ajouter
4420
 * @param string $javascript
4421
 *     Javascript ajouté sur le lien
4422
 * @return string
4423
 *     Code HTML du lien
4424
 */
4425
function filtre_icone_dist($lien, $texte, $fond, $align = "", $fonction = "", $class = "", $javascript = "") {
4426
	return icone_base($lien, $texte, $fond, $fonction, "verticale $align $class", $javascript);
4427
}
4428
4429
4430
/**
4431
 * Explose un texte en tableau suivant un séparateur
4432
 *
4433
 * @note
4434
 *     Inverse l'écriture de la fonction PHP de même nom
4435
 *     pour que le filtre soit plus pratique dans les squelettes
4436
 *
4437
 * @filtre
4438
 * @example
4439
 *     ```
4440
 *     [(#GET{truc}|explode{-})]
4441
 *     ```
4442
 *
4443
 * @param string $a Texte
4444
 * @param string $b Séparateur
4445
 * @return array Liste des éléments
4446
 */
4447
function filtre_explode_dist($a, $b) { return explode($b, $a); }
4448
4449
/**
4450
 * Implose un tableau en chaine en liant avec un séparateur
4451
 *
4452
 * @note
4453
 *     Inverse l'écriture de la fonction PHP de même nom
4454
 *     pour que le filtre soit plus pratique dans les squelettes
4455
 *
4456
 * @filtre
4457
 * @example
4458
 *     ```
4459
 *     [(#GET{truc}|implode{-})]
4460
 *     ```
4461
 *
4462
 * @param array $a Tableau
4463
 * @param string $b Séparateur
4464
 * @return string Texte
4465
 */
4466
function filtre_implode_dist($a, $b) { return is_array($a) ? implode($b, $a) : $a; }
4467
4468
/**
4469
 * Produire les styles privés qui associent item de menu avec icone en background
4470
 *
4471
 * @return string Code CSS
4472
 */
4473
function bando_images_background() {
4474
	include_spip('inc/bandeau');
4475
	// recuperer tous les boutons et leurs images
4476
	$boutons = definir_barre_boutons(definir_barre_contexte(), true, false);
4477
4478
	$res = "";
4479
	foreach ($boutons as $page => $detail) {
4480
		if ($detail->icone and strlen(trim($detail->icone))) {
4481
			$res .= "\n.navigation_avec_icones #bando1_$page {background-image:url(" . $detail->icone . ");}";
4482
		}
4483
		$selecteur = (in_array($page, array('outils_rapides', 'outils_collaboratifs')) ? "" : ".navigation_avec_icones ");
4484
		if (is_array($detail->sousmenu)) {
4485
			foreach ($detail->sousmenu as $souspage => $sousdetail) {
4486
				if ($sousdetail->icone and strlen(trim($sousdetail->icone))) {
4487
					$res .= "\n$selecteur.bando2_$souspage {background-image:url(" . $sousdetail->icone . ");}";
4488
				}
4489
			}
4490
		}
4491
	}
4492
4493
	return $res;
4494
}
4495
4496
/**
4497
 * Générer un bouton_action
4498
 * utilisé par #BOUTON_ACTION
4499
 *
4500
 * @param string $libelle
4501
 *   Libellé du bouton
4502
 * @param string $url
4503
 *   URL d'action sécurisée, généralement obtenue avec generer_action_auteur()
4504
 * @param string $class
4505
 *   Classes à ajouter au bouton, à l'exception de `ajax` qui est placé sur le formulaire.
4506
 * @param string $confirm
4507
 *   Message de confirmation oui/non avant l'action
4508
 * @param string $title
4509
 *   Attribut title à ajouter au bouton
4510
 * @param string $callback
4511
 *   Callback js a appeler lors de l'évènement action et avant execution de l'action
4512
 *   (ou après confirmation éventuelle si $confirm est non vide).
4513
 *   Si la callback renvoie false, elle annule le déclenchement de l'action.
4514
 * @return string
4515
 */
4516
function bouton_action($libelle, $url, $class = '', $confirm = '', $title = '', $callback = '') {
4517
4518
	// Classes : dispatcher `ajax` sur le formulaire
4519
	$class_form = '';
4520
	if (strpos($class, 'ajax') !== false) {
4521
		$class_form = 'ajax';
4522
		$class = str_replace('ajax', '', $class);
4523
	}
4524
	$class_btn = 'submit ' . trim($class);
4525
4526
	if ($confirm) {
4527
		$confirm = "confirm(\"" . attribut_html($confirm) . "\")";
4528
		if ($callback) {
4529
			$callback = "$confirm?($callback):false";
4530
		} else {
4531
			$callback = $confirm;
4532
		}
4533
	}
4534
	$onclick = $callback ? " onclick='return " . addcslashes($callback, "'") . "'" : "";
4535
	$title = $title ? " title='$title'" : '';
4536
4537
	return "<form class='bouton_action_post $class_form' method='post' action='$url'><div>" . form_hidden($url)
4538
	. "<button type='submit' class='$class_btn'$title$onclick>$libelle</button></div></form>";
4539
}
4540
4541
/**
4542
 * Donner n'importe quelle information sur un objet de maniere generique.
4543
 *
4544
 * La fonction va gerer en interne deux cas particuliers les plus utilises :
4545
 * l'URL et le titre (qui n'est pas forcemment le champ SQL "titre").
4546
 *
4547
 * On peut ensuite personnaliser les autres infos en creant une fonction
4548
 * generer_<nom_info>_entite($id_objet, $type_objet, $ligne).
4549
 * $ligne correspond a la ligne SQL de tous les champs de l'objet, les fonctions
4550
 * de personnalisation n'ont donc pas a refaire de requete.
4551
 *
4552
 * @param int $id_objet
4553
 * @param string $type_objet
4554
 * @param string $info
4555
 * @param string $etoile
4556
 * @return string
4557
 */
4558
function generer_info_entite($id_objet, $type_objet, $info, $etoile = "") {
4559
	static $trouver_table = null;
4560
	static $objets;
4561
4562
	// On verifie qu'on a tout ce qu'il faut
4563
	$id_objet = intval($id_objet);
4564
	if (!($id_objet and $type_objet and $info)) {
4565
		return '';
4566
	}
4567
4568
	// si on a deja note que l'objet n'existe pas, ne pas aller plus loin
4569
	if (isset($objets[$type_objet]) and $objets[$type_objet] === false) {
4570
		return '';
4571
	}
4572
4573
	// Si on demande l'url, on retourne direct la fonction
4574
	if ($info == 'url') {
4575
		return generer_url_entite($id_objet, $type_objet);
4576
	}
4577
4578
	// Sinon on va tout chercher dans la table et on garde en memoire
4579
	$demande_titre = ($info == 'titre');
4580
4581
	// 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
4582
	if (!isset($objets[$type_objet][$id_objet])
4583
		or
4584
		($demande_titre and !isset($objets[$type_objet][$id_objet]['titre']))
4585
	) {
4586
		if (!$trouver_table) {
4587
			$trouver_table = charger_fonction('trouver_table', 'base');
4588
		}
4589
		$desc = $trouver_table(table_objet_sql($type_objet));
4590
		if (!$desc) {
4591
			return $objets[$type_objet] = false;
4592
		}
4593
4594
		// Si on demande le titre, on le gere en interne
4595
		$champ_titre = "";
4596
		if ($demande_titre) {
4597
			// si pas de titre declare mais champ titre, il sera peuple par le select *
4598
			$champ_titre = (!empty($desc['titre'])) ? ', ' . $desc['titre'] : '';
4599
		}
4600
		include_spip('base/abstract_sql');
4601
		include_spip('base/connect_sql');
4602
		$objets[$type_objet][$id_objet] = sql_fetsel(
4603
			'*' . $champ_titre,
4604
			$desc['table_sql'],
4605
			id_table_objet($type_objet) . ' = ' . intval($id_objet)
4606
		);
4607
	}
4608
4609
	// Si la fonction generer_TRUC_TYPE existe, on l'utilise pour formater $info_generee
4610
	if ($generer = charger_fonction("generer_${info}_${type_objet}", '', true)) {
4611
		$info_generee = $generer($id_objet, $objets[$type_objet][$id_objet]);
4612
	} // Si la fonction generer_TRUC_entite existe, on l'utilise pour formater $info_generee
4613
	else {
4614
		if ($generer = charger_fonction("generer_${info}_entite", '', true)) {
4615
			$info_generee = $generer($id_objet, $type_objet, $objets[$type_objet][$id_objet]);
4616
		} // Sinon on prend directement le champ SQL tel quel
4617
		else {
4618
			$info_generee = (isset($objets[$type_objet][$id_objet][$info]) ? $objets[$type_objet][$id_objet][$info] : '');
4619
		}
4620
	}
4621
4622
	// On va ensuite appliquer les traitements automatiques si besoin
4623
	if (!$etoile) {
4624
		// FIXME: on fournit un ENV minimum avec id et type et connect=''
4625
		// mais ce fonctionnement est a ameliorer !
4626
		$info_generee = appliquer_traitement_champ($info_generee, $info, table_objet($type_objet),
4627
			array('id_objet' => $id_objet, 'objet' => $type_objet, ''));
4628
	}
4629
4630
	return $info_generee;
4631
}
4632
4633
/**
4634
 * Appliquer a un champ SQL le traitement qui est configure pour la balise homonyme dans les squelettes
4635
 *
4636
 * @param string $texte
4637
 * @param string $champ
4638
 * @param string $table_objet
4639
 * @param array $env
4640
 * @param string $connect
4641
 * @return string
4642
 */
4643
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...
4644
	if (!$champ) {
4645
		return $texte;
4646
	}
4647
	
4648
	// On charge toujours les filtres de texte car la majorité des traitements les utilisent
4649
	// et il ne faut pas partir du principe que c'est déjà chargé (form ajax, etc)
4650
	include_spip('inc/texte');
4651
	
4652
	$champ = strtoupper($champ);
4653
	$traitements = isset($GLOBALS['table_des_traitements'][$champ]) ? $GLOBALS['table_des_traitements'][$champ] : false;
4654
	if (!$traitements or !is_array($traitements)) {
4655
		return $texte;
4656
	}
4657
4658
	$traitement = '';
4659
	if ($table_objet and (!isset($traitements[0]) or count($traitements) > 1)) {
4660
		// necessaire pour prendre en charge les vieux appels avec un table_objet_sql en 3e arg
4661
		$table_objet = table_objet($table_objet);
4662
		if (isset($traitements[$table_objet])) {
4663
			$traitement = $traitements[$table_objet];
4664
		}
4665
	}
4666
	if (!$traitement and isset($traitements[0])) {
4667
		$traitement = $traitements[0];
4668
	}
4669
	// (sinon prendre le premier de la liste par defaut ?)
4670
4671
	if (!$traitement) {
4672
		return $texte;
4673
	}
4674
4675
	$traitement = str_replace('%s', "'" . texte_script($texte) . "'", $traitement);
4676
4677
	// Fournir $connect et $Pile[0] au traitement si besoin
4678
	$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...
4679
	eval("\$texte = $traitement;");
4680
4681
	return $texte;
4682
}
4683
4684
4685
/**
4686
 * Generer un lien (titre clicable vers url) vers un objet
4687
 *
4688
 * @param int $id_objet
4689
 * @param $objet
4690
 * @param int $longueur
4691
 * @param null|string $connect
4692
 * @return string
4693
 */
4694
function generer_lien_entite($id_objet, $objet, $longueur = 80, $connect = null) {
4695
	include_spip('inc/liens');
4696
	$titre = traiter_raccourci_titre($id_objet, $objet, $connect);
4697
	// lorsque l'objet n'est plus declare (plugin desactive par exemple)
4698
	// le raccourcis n'est plus valide
4699
	$titre = isset($titre['titre']) ? typo($titre['titre']) : '';
4700
	// on essaye avec generer_info_entite ?
4701
	if (!strlen($titre) and !$connect) {
4702
		$titre = generer_info_entite($id_objet, $objet, 'titre');
4703
	}
4704
	if (!strlen($titre)) {
4705
		$titre = _T('info_sans_titre');
4706
	}
4707
	$url = generer_url_entite($id_objet, $objet, '', '', $connect);
4708
4709
	return "<a href='$url' class='$objet'>" . couper($titre, $longueur) . "</a>";
4710
}
4711
4712
4713
/**
4714
 * Englobe (Wrap) un texte avec des balises
4715
 *
4716
 * @example `wrap('mot','<b>')` donne `<b>mot</b>'`
4717
 *
4718
 * @filtre
4719
 * @uses extraire_balises()
4720
 *
4721
 * @param string $texte
4722
 * @param string $wrap
4723
 * @return string
4724
 */
4725
function wrap($texte, $wrap) {
4726
	$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...
4727
	if (preg_match_all(",<([a-z]\w*)\b[^>]*>,UimsS", $wrap, $regs, PREG_PATTERN_ORDER)) {
4728
		$texte = $wrap . $texte;
4729
		$regs = array_reverse($regs[1]);
4730
		$wrap = "</" . implode("></", $regs) . ">";
4731
		$texte = $texte . $wrap;
4732
	}
4733
4734
	return $texte;
4735
}
4736
4737
4738
/**
4739
 * afficher proprement n'importe quoi
4740
 * On affiche in fine un pseudo-yaml qui premet de lire humainement les tableaux et de s'y reperer
4741
 *
4742
 * Les textes sont retournes avec simplement mise en forme typo
4743
 *
4744
 * 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
4745
 * les tableaux-listes (qui n'ont que des cles numeriques), sont affiches sous forme de liste separee par des virgules :
4746
 * c'est VOULU !
4747
 *
4748
 * @param $u
4749
 * @param string $join
4750
 * @param int $indent
4751
 * @return array|mixed|string
4752
 */
4753
function filtre_print_dist($u, $join = "<br />", $indent = 0) {
4754
	if (is_string($u)) {
4755
		$u = typo($u);
4756
4757
		return $u;
4758
	}
4759
4760
	// caster $u en array si besoin
4761
	if (is_object($u)) {
4762
		$u = (array)$u;
4763
	}
4764
4765
	if (is_array($u)) {
4766
		$out = "";
4767
		// toutes les cles sont numeriques ?
4768
		// et aucun enfant n'est un tableau
4769
		// liste simple separee par des virgules
4770
		$numeric_keys = array_map('is_numeric', array_keys($u));
4771
		$array_values = array_map('is_array', $u);
4772
		$object_values = array_map('is_object', $u);
4773
		if (array_sum($numeric_keys) == count($numeric_keys)
4774
			and !array_sum($array_values)
4775
			and !array_sum($object_values)
4776
		) {
4777
			return join(", ", array_map('filtre_print_dist', $u));
4778
		}
4779
4780
		// sinon on passe a la ligne et on indente
4781
		$i_str = str_pad("", $indent, " ");
4782
		foreach ($u as $k => $v) {
4783
			$out .= $join . $i_str . "$k: " . filtre_print_dist($v, $join, $indent + 2);
4784
		}
4785
4786
		return $out;
4787
	}
4788
4789
	// on sait pas quoi faire...
4790
	return $u;
4791
}
4792
4793
4794
/**
4795
 * Renvoyer l'info d'un objet
4796
 * telles que definies dans declarer_tables_objets_sql
4797
 *
4798
 * @param string $objet
4799
 * @param string $info
4800
 * @return string|array
4801
 */
4802
function objet_info($objet, $info) {
4803
	$table = table_objet_sql($objet);
4804
	$infos = lister_tables_objets_sql($table);
4805
4806
	return (isset($infos[$info]) ? $infos[$info] : '');
4807
}
4808
4809
/**
4810
 * Filtre pour afficher 'Aucun truc' ou '1 truc' ou 'N trucs'
4811
 * avec la bonne chaîne de langue en fonction de l'objet utilisé
4812
 *
4813
 * @param int $nb
4814
 *     Nombre d'éléments
4815
 * @param string $objet
4816
 *     Objet
4817
 * @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...
4818
 *     Texte traduit du comptage, tel que '3 articles'
4819
 */
4820
function objet_afficher_nb($nb, $objet) {
4821
	if (!$nb) {
4822
		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...
4823
	} else {
4824
		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...
4825
	}
4826
}
4827
4828
/**
4829
 * Filtre pour afficher l'img icone d'un objet
4830
 *
4831
 * @param string $objet
4832
 * @param int $taille
4833
 * @param string $class
4834
 * @return string
4835
 */
4836
function objet_icone($objet, $taille = 24, $class='') {
4837
	$icone = objet_info($objet, 'icone_objet') . "-" . $taille . ".png";
4838
	$icone = chemin_image($icone);
4839
	$balise_img = charger_filtre('balise_img');
4840
4841
	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...
4842
}
4843
4844
/**
4845
 * Renvoyer une traduction d'une chaine de langue contextuelle à un objet si elle existe,
4846
 * la traduction de la chaine generique
4847
 *
4848
 * Ex : [(#ENV{objet}|objet_label{trad_reference})]
4849
 * va chercher si une chaine objet:trad_reference existe et renvoyer sa trad le cas echeant
4850
 * sinon renvoie la trad de la chaine trad_reference
4851
 * Si la chaine fournie contient un prefixe il est remplacé par celui de l'objet pour chercher la chaine contextuelle
4852
 *
4853
 * Les arguments $args et $options sont ceux de la fonction _T
4854
 *
4855
 * @param string $objet
4856
 * @param string $chaine
4857
 * @param array $args
4858
 * @param array $options
4859
 * @return string
4860
 */
4861
function objet_T($objet, $chaine, $args = array(), $options = array()){
4862
	$chaine = explode(':',$chaine);
4863
	if ($t = _T($objet . ':' . end($chaine), $args, array_merge($options, array('force'=>false)))) {
4864
		return $t;
4865
	}
4866
	$chaine = implode(':',$chaine);
4867
	return _T($chaine, $args, $options);
4868
}
4869
4870
/**
4871
 * Fonction de secours pour inserer le head_css de facon conditionnelle
4872
 *
4873
 * Appelée en filtre sur le squelette qui contient #INSERT_HEAD,
4874
 * elle vérifie l'absence éventuelle de #INSERT_HEAD_CSS et y suplée si besoin
4875
 * pour assurer la compat avec les squelettes qui n'utilisent pas.
4876
 *
4877
 * @param string $flux Code HTML
4878
 * @return string      Code HTML
4879
 */
4880
function insert_head_css_conditionnel($flux) {
4881
	if (strpos($flux, '<!-- insert_head_css -->') === false
4882
		and $p = strpos($flux, '<!-- insert_head -->')
4883
	) {
4884
		// plutot avant le premier js externe (jquery) pour etre non bloquant
4885
		if ($p1 = stripos($flux, '<script src=') and $p1 < $p) {
4886
			$p = $p1;
4887
		}
4888
		$flux = substr_replace($flux, pipeline('insert_head_css', '<!-- insert_head_css -->'), $p, 0);
4889
	}
4890
4891
	return $flux;
4892
}
4893
4894
/**
4895
 * Produire un fichier statique à partir d'un squelette dynamique
4896
 *
4897
 * Permet ensuite à Apache de le servir en statique sans repasser
4898
 * par spip.php à chaque hit sur le fichier.
4899
 *
4900
 * Si le format (css ou js) est passe dans `contexte['format']`, on l'utilise
4901
 * sinon on regarde si le fond finit par .css ou .js, sinon on utilie "html"
4902
 *
4903
 * @uses urls_absolues_css()
4904
 *
4905
 * @param string $fond
4906
 * @param array $contexte
4907
 * @param array $options
4908
 * @param string $connect
4909
 * @return string
4910
 */
4911
function produire_fond_statique($fond, $contexte = array(), $options = array(), $connect = '') {
4912
	if (isset($contexte['format'])) {
4913
		$extension = $contexte['format'];
4914
		unset($contexte['format']);
4915
	} else {
4916
		$extension = "html";
4917
		if (preg_match(',[.](css|js|json)$,', $fond, $m)) {
4918
			$extension = $m[1];
4919
		}
4920
	}
4921
	// recuperer le contenu produit par le squelette
4922
	$options['raw'] = true;
4923
	$cache = recuperer_fond($fond, $contexte, $options, $connect);
4924
4925
	// calculer le nom de la css
4926
	$dir_var = sous_repertoire(_DIR_VAR, 'cache-' . $extension);
4927
	$nom_safe = preg_replace(",\W,", '_', str_replace('.', '_', $fond));
4928
	$contexte_implicite = calculer_contexte_implicite();
4929
4930
	// par defaut on hash selon les contextes qui sont a priori moins variables
4931
	// mais on peut hasher selon le contenu a la demande, si plusieurs contextes produisent un meme contenu
4932
	// reduit la variabilite du nom et donc le nombre de css concatenees possibles in fine
4933
	if (isset($options['hash_on_content']) and $options['hash_on_content']) {
4934
		$hash = md5($contexte_implicite['host'] . '::'. $cache);
4935
	}
4936
	else {
4937
		unset($contexte_implicite['notes']); // pas pertinent pour signaler un changeemnt de contenu pour des css/js
4938
		ksort($contexte);
4939
		$hash = md5($fond . json_encode($contexte_implicite) . json_encode($contexte) . $connect);
4940
	}
4941
	$filename = $dir_var . $extension . "dyn-$nom_safe-" . substr($hash, 0, 8) . ".$extension";
4942
4943
	// mettre a jour le fichier si il n'existe pas
4944
	// ou trop ancien
4945
	// le dernier fichier produit est toujours suffixe par .last
4946
	// et recopie sur le fichier cible uniquement si il change
4947
	if (!file_exists($filename)
4948
		or !file_exists($filename . ".last")
4949
		or (isset($cache['lastmodified']) and $cache['lastmodified'] and filemtime($filename . ".last") < $cache['lastmodified'])
4950
		or (defined('_VAR_MODE') and _VAR_MODE == 'recalcul')
4951
	) {
4952
		$contenu = $cache['texte'];
4953
		// passer les urls en absolu si c'est une css
4954
		if ($extension == "css") {
4955
			$contenu = urls_absolues_css($contenu,
4956
				test_espace_prive() ? generer_url_ecrire('accueil') : generer_url_public($fond));
4957
		}
4958
4959
		$comment = '';
4960
		// ne pas insérer de commentaire si c'est du json
4961
		if ($extension != "json") {
4962
			$comment = "/* #PRODUIRE{fond=$fond";
4963
			foreach ($contexte as $k => $v) {
4964
				$comment .= ",$k=$v";
4965
			}
4966
			// pas de date dans le commentaire car sinon ca invalide le md5 et force la maj
4967
			// mais on peut mettre un md5 du contenu, ce qui donne un aperu rapide si la feuille a change ou non
4968
			$comment .= "}\n   md5:" . md5($contenu) . " */\n";
4969
		}
4970
		// et ecrire le fichier si il change
4971
		ecrire_fichier_calcule_si_modifie($filename, $comment . $contenu, false, true);
4972
	}
4973
4974
	return timestamp($filename);
4975
}
4976
4977
/**
4978
 * Ajouter un timestamp a une url de fichier
4979
 * [(#CHEMIN{monfichier}|timestamp)]
4980
 *
4981
 * @param string $fichier
4982
 *    Le chemin du fichier sur lequel on souhaite ajouter le timestamp
4983
 * @return string
4984
 *    $fichier auquel on a ajouté le timestamp
4985
 */
4986
function timestamp($fichier) {
4987
	if (!$fichier
4988
		or !file_exists($fichier)
4989
		or !$m = filemtime($fichier)
4990
	) {
4991
		return $fichier;
4992
	}
4993
4994
	return "$fichier?$m";
4995
}
4996
4997
/**
4998
 * Supprimer le timestamp d'une url
4999
 *
5000
 * @param string $url
5001
 * @return string
5002
 */
5003
function supprimer_timestamp($url) {
5004
	if (strpos($url, "?") === false) {
5005
		return $url;
5006
	}
5007
5008
	return preg_replace(",\?[[:digit:]]+$,", "", $url);
5009
}
5010
5011
/**
5012
 * Nettoyer le titre d'un email
5013
 *
5014
 * Éviter une erreur lorsqu'on utilise `|nettoyer_titre_email` dans un squelette de mail
5015
 *
5016
 * @filtre
5017
 * @uses nettoyer_titre_email()
5018
 *
5019
 * @param string $titre
5020
 * @return string
5021
 */
5022
function filtre_nettoyer_titre_email_dist($titre) {
5023
	include_spip('inc/envoyer_mail');
5024
5025
	return nettoyer_titre_email($titre);
5026
}
5027
5028
/**
5029
 * Afficher le sélecteur de rubrique
5030
 *
5031
 * Il permet de placer un objet dans la hiérarchie des rubriques de SPIP
5032
 *
5033
 * @uses chercher_rubrique()
5034
 *
5035
 * @param string $titre
5036
 * @param int $id_objet
5037
 * @param int $id_parent
5038
 * @param string $objet
5039
 * @param int $id_secteur
5040
 * @param bool $restreint
5041
 * @param bool $actionable
5042
 *   true : fournit le selecteur dans un form directement postable
5043
 * @param bool $retour_sans_cadre
5044
 * @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...
5045
 */
5046
function filtre_chercher_rubrique_dist(
5047
	$titre,
5048
	$id_objet,
5049
	$id_parent,
5050
	$objet,
5051
	$id_secteur,
5052
	$restreint,
5053
	$actionable = false,
5054
	$retour_sans_cadre = false
5055
) {
5056
	include_spip('inc/filtres_ecrire');
5057
5058
	return chercher_rubrique($titre, $id_objet, $id_parent, $objet, $id_secteur, $restreint, $actionable,
5059
		$retour_sans_cadre);
5060
}
5061
5062
/**
5063
 * Rediriger une page suivant une autorisation,
5064
 * et ce, n'importe où dans un squelette, même dans les inclusions.
5065
 *
5066
 * En l'absence de redirection indiquée, la fonction redirige par défaut
5067
 * sur une 403 dans l'espace privé et 404 dans l'espace public.
5068
 *
5069
 * @example
5070
 *     ```
5071
 *     [(#AUTORISER{non}|sinon_interdire_acces)]
5072
 *     [(#AUTORISER{non}|sinon_interdire_acces{#URL_PAGE{login}, 401})]
5073
 *     ```
5074
 *
5075
 * @filtre
5076
 * @param bool $ok
5077
 *     Indique si l'on doit rediriger ou pas
5078
 * @param string $url
5079
 *     Adresse eventuelle vers laquelle rediriger
5080
 * @param int $statut
5081
 *     Statut HTML avec lequel on redirigera
5082
 * @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...
5083
 *     message d'erreur
5084
 * @return string|void
5085
 *     Chaîne vide si l'accès est autorisé
5086
 */
5087
function sinon_interdire_acces($ok = false, $url = '', $statut = 0, $message = null) {
5088
	if ($ok) {
5089
		return '';
5090
	}
5091
5092
	// Vider tous les tampons
5093
	$level = @ob_get_level();
5094
	while ($level--) {
5095
		@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...
5096
	}
5097
5098
	include_spip('inc/headers');
5099
5100
	// S'il y a une URL, on redirige (si pas de statut, la fonction mettra 302 par défaut)
5101
	if ($url) {
5102
		redirige_par_entete($url, '', $statut);
5103
	}
5104
5105
	// ecriture simplifiee avec message en 3eme argument (= statut 403)
5106
	if (!is_numeric($statut) and is_null($message)) {
5107
		$message = $statut;
5108
		$statut = 0;
5109
	}
5110
	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...
5111
		$message = '';
5112
	}
5113
	$statut = intval($statut);
5114
5115
	// Si on est dans l'espace privé, on génère du 403 Forbidden par defaut ou du 404
5116
	if (test_espace_prive()) {
5117
		if (!$statut or !in_array($statut, array(404, 403))) {
5118
			$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...
5119
		}
5120
		http_status(403);
5121
		$echec = charger_fonction('403', 'exec');
5122
		$echec($message);
5123
	} else {
5124
		// Sinon dans l'espace public on redirige vers une 404 par défaut, car elle toujours présente normalement
5125
		if (!$statut) {
5126
			$statut = 404;
5127
		}
5128
		// Dans tous les cas on modifie l'entité avec ce qui est demandé
5129
		http_status($statut);
5130
		// Si le statut est une erreur et qu'il n'y a pas de redirection on va chercher le squelette du même nom
5131
		if ($statut >= 400) {
5132
			echo recuperer_fond("$statut", array('erreur' => $message));
5133
		}
5134
	}
5135
5136
5137
	exit;
5138
}
5139
5140
/**
5141
 * Assurer le fonctionnement de |compacte meme sans l'extension compresseur
5142
 *
5143
 * @param string $source
5144
 * @param null|string $format
5145
 * @return string
5146
 */
5147
function filtre_compacte_dist($source, $format = null) {
5148
	if (function_exists('compacte')) {
5149
		return compacte($source, $format);
5150
	}
5151
5152
	return $source;
5153
}
5154
5155
5156
/**
5157
 * Afficher partiellement un mot de passe que l'on ne veut pas rendre lisible par un champ hidden
5158
 * @param string $passe
5159
 * @param bool $afficher_partiellement
5160
 * @param int|null $portion_pourcent
5161
 * @return string
5162
 */
5163
function spip_affiche_mot_de_passe_masque($passe, $afficher_partiellement = false, $portion_pourcent = null) {
5164
	$l = strlen($passe);
5165
5166
	if ($l<=8 or !$afficher_partiellement){
5167
		if (!$l) {
5168
			return ''; // montrer qu'il y a pas de mot de passe si il y en a pas
5169
		}
5170
		return str_pad('',$afficher_partiellement ? $l : 16,'*');
5171
	}
5172
5173
	if (is_null($portion_pourcent)) {
5174
		if (!defined('_SPIP_AFFICHE_MOT_DE_PASSE_MASQUE_PERCENT')) {
5175
			define('_SPIP_AFFICHE_MOT_DE_PASSE_MASQUE_PERCENT', 20); // 20%
5176
		}
5177
		$portion_pourcent = _SPIP_AFFICHE_MOT_DE_PASSE_MASQUE_PERCENT;
5178
	}
5179
	if ($portion_pourcent >= 100) {
5180
		return $passe;
5181
	}
5182
	$e = intval(ceil($l * $portion_pourcent / 100 / 2));
5183
	$e = max($e, 0);
5184
	$mid = str_pad('',$l-2*$e,'*');
5185
	if ($e>0 and strlen($mid)>8){
5186
		$mid = '***...***';
5187
	}
5188
	return substr($passe,0,$e) . $mid . ($e > 0 ? substr($passe,-$e) : '');
5189
}
5190
5191
5192
/**
5193
 * Cette fonction permet de transformer un texte clair en nom court pouvant servir d'identifiant, class, id, url...
5194
 * en ne conservant que des caracteres alphanumeriques et un separateur
5195
 *
5196
 * @param string $texte
5197
 *   Texte à transformer en nom machine
5198
 * @param string $type
5199
 *
5200
 * @param array $options
5201
 *   string separateur : par défaut, un underscore `_`.
5202
 *   int longueur_maxi : par defaut 60
5203
 *   int longueur_mini : par defaut 0
5204
 *
5205
 * @return string
5206
 */
5207
function identifiant_slug($texte, $type = '', $options = array()) {
5208
5209
	$original = $texte;
5210
	$separateur = (isset($options['separateur'])?$options['separateur']:'_');
5211
	$longueur_maxi = (isset($options['longueur_maxi'])?$options['longueur_maxi']:60);
5212
	$longueur_mini = (isset($options['longueur_mini'])?$options['longueur_mini']:0);
5213
5214
	if (!function_exists('translitteration')) {
5215
		include_spip('inc/charsets');
5216
	}
5217
5218
	// pas de balise html
5219
	if (strpos($texte, '<') !== false) {
5220
		$texte = strip_tags($texte);
5221
	}
5222
	if (strpos($texte, '&') !== false) {
5223
		$texte = unicode2charset($texte);
5224
	}
5225
	// On enlève les espaces indésirables
5226
	$texte = trim($texte);
5227
5228
	// On enlève les accents et cie
5229
	$texte = translitteration($texte);
5230
5231
	// On remplace tout ce qui n'est pas un mot par un séparateur
5232
	$texte = preg_replace(',[\W_]+,ms', $separateur, $texte);
5233
5234
	// nettoyer les doubles occurences du separateur si besoin
5235
	while (strpos($texte, "$separateur$separateur") !== false) {
5236
		$texte = str_replace("$separateur$separateur", $separateur, $texte);
5237
	}
5238
5239
	// pas de separateur au debut ni a la fin
5240
	$texte = trim($texte, $separateur);
5241
5242
	// en minuscules
5243
	$texte = strtolower($texte);
5244
5245
	switch ($type) {
5246
		case 'class':
5247
		case 'id':
5248
		case 'anchor':
5249
			if (preg_match(',^\d,', $texte)) {
5250
				$texte = substr($type, 0, 1).$texte;
5251
			}
5252
	}
5253
5254
	if (strlen($texte)>$longueur_maxi) {
5255
		$texte = substr($texte, 0, $longueur_maxi);
5256
	}
5257
5258
	if (strlen($texte) < $longueur_mini and $longueur_mini < $longueur_maxi) {
5259
		if (preg_match(',^\d,', $texte)) {
5260
			$texte = ($type ? substr($type,0,1) : "s") . $texte;
5261
		}
5262
		$texte .= $separateur . md5($original);
5263
		$texte = substr($texte, 0, $longueur_mini);
5264
	}
5265
5266
	return $texte;
5267
}
5268
5269
/**
5270
 * Prépare un texte (issu d'une chaine de langue historique) pour un affichage en label ou titre
5271
 * 
5272
 * Enlève un ':' à la fin d'une chaine de caractère, ainsi que les espaces qui pourraient l'accompagner,
5273
 * Met la première lettre en majuscule (par défaut)
5274
 *
5275
 * Utile afficher dans un contexte de titre des chaines de langues qui contiennent des ':'
5276
 * 
5277
 * @exemple `<:info_maximum|uniformiser_label:>`
5278
 */
5279
function uniformiser_label(string $text, bool $ucfirst = true) : string {
5280
	$label = rtrim($text, " : \t\n\r\0\x0B\xc2\xa0");
5281
	if ($label and $label[-1] === ';') {
5282
		$label = preg_replace("#(\&nbsp;)+$#", "", $label);
5283
	}
5284
	if ($ucfirst) {
5285
		$label = spip_ucfirst($label);
5286
	}
5287
	return $label;
5288
}