Completed
Push — master ( 117d82...bd6ae7 )
by cam
04:12
created

filtres.php ➔ version_svn_courante()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 1
dl 0
loc 6
rs 10
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
// La matrice est necessaire pour ne filtrer _que_ des fonctions definies dans filtres_images
290
// et laisser passer les fonctions personnelles baptisees image_...
291
$GLOBALS['spip_matrice']['image_graver'] = true;//'inc/filtres_images_mini.php';
292
$GLOBALS['spip_matrice']['image_select'] = true;//'inc/filtres_images_mini.php';
293
$GLOBALS['spip_matrice']['image_reduire'] = true;//'inc/filtres_images_mini.php';
294
$GLOBALS['spip_matrice']['image_reduire_par'] = true;//'inc/filtres_images_mini.php';
295
$GLOBALS['spip_matrice']['image_passe_partout'] = true;//'inc/filtres_images_mini.php';
296
$GLOBALS['spip_matrice']['image_recadre_avec_fallback'] = true;//'inc/filtres_images_mini.php';
297
298
$GLOBALS['spip_matrice']['couleur_html_to_hex'] = 'inc/filtres_images_mini.php';
299
$GLOBALS['spip_matrice']['couleur_hex_to_hsl'] = 'inc/filtres_images_mini.php';
300
$GLOBALS['spip_matrice']['couleur_hex_to_rgb'] = 'inc/filtres_images_mini.php';
301
$GLOBALS['spip_matrice']['couleur_foncer'] = 'inc/filtres_images_mini.php';
302
$GLOBALS['spip_matrice']['couleur_eclaircir'] = 'inc/filtres_images_mini.php';
303
304
// ou pour inclure un script au moment ou l'on cherche le filtre
305
$GLOBALS['spip_matrice']['filtre_image_dist'] = 'inc/filtres_mime.php';
306
$GLOBALS['spip_matrice']['filtre_audio_dist'] = 'inc/filtres_mime.php';
307
$GLOBALS['spip_matrice']['filtre_video_dist'] = 'inc/filtres_mime.php';
308
$GLOBALS['spip_matrice']['filtre_application_dist'] = 'inc/filtres_mime.php';
309
$GLOBALS['spip_matrice']['filtre_message_dist'] = 'inc/filtres_mime.php';
310
$GLOBALS['spip_matrice']['filtre_multipart_dist'] = 'inc/filtres_mime.php';
311
$GLOBALS['spip_matrice']['filtre_text_dist'] = 'inc/filtres_mime.php';
312
$GLOBALS['spip_matrice']['filtre_text_csv_dist'] = 'inc/filtres_mime.php';
313
$GLOBALS['spip_matrice']['filtre_text_html_dist'] = 'inc/filtres_mime.php';
314
$GLOBALS['spip_matrice']['filtre_audio_x_pn_realaudio'] = 'inc/filtres_mime.php';
315
316
317
/**
318
 * Charge et exécute un filtre (graphique ou non)
319
 *
320
 * Recherche la fonction prévue pour un filtre (qui peut être un filtre graphique `image_*`)
321
 * et l'exécute avec les arguments transmis à la fonction, obtenus avec `func_get_args()`
322
 *
323
 * @api
324
 * @uses image_filtrer() Pour un filtre image
325
 * @uses chercher_filtre() Pour un autre filtre
326
 *
327
 * @param string $filtre
328
 *     Nom du filtre à appliquer
329
 * @return string
330
 *     Code HTML retourné par le filtre
331
 **/
332
function filtrer($filtre) {
333
	$tous = func_get_args();
334
	if (trouver_filtre_matrice($filtre) and substr($filtre, 0, 6) == 'image_') {
335
		return image_filtrer($tous);
336
	} elseif ($f = chercher_filtre($filtre)) {
337
		array_shift($tous);
338
		return call_user_func_array($f, $tous);
339
	} else {
340
		// le filtre n'existe pas, on provoque une erreur
341
		$msg = array('zbug_erreur_filtre', array('filtre' => texte_script($filtre)));
342
		erreur_squelette($msg);
343
		return '';
344
	}
345
}
346
347
/**
348
 * Cherche un filtre spécial indiqué dans la globale `spip_matrice`
349
 * et charge le fichier éventuellement associé contenant le filtre.
350
 *
351
 * Les filtres d'images par exemple sont déclarés de la sorte, tel que :
352
 * ```
353
 * $GLOBALS['spip_matrice']['image_reduire'] = true;
354
 * $GLOBALS['spip_matrice']['image_monochrome'] = 'filtres/images_complements.php';
355
 * ```
356
 *
357
 * @param string $filtre
358
 * @return bool true si on trouve le filtre dans la matrice, false sinon.
359
 */
360
function trouver_filtre_matrice($filtre) {
361
	if (isset($GLOBALS['spip_matrice'][$filtre]) and is_string($f = $GLOBALS['spip_matrice'][$filtre])) {
362
		find_in_path($f, '', true);
363
		$GLOBALS['spip_matrice'][$filtre] = true;
364
	}
365
	return !empty($GLOBALS['spip_matrice'][$filtre]);
366
}
367
368
369
/**
370
 * Filtre `set` qui sauve la valeur en entrée dans une variable
371
 *
372
 * La valeur pourra être retrouvée avec `#GET{variable}`.
373
 *
374
 * @example
375
 *     `[(#CALCUL|set{toto})]` enregistre le résultat de `#CALCUL`
376
 *     dans la variable `toto` et renvoie vide.
377
 *     C'est équivalent à `[(#SET{toto, #CALCUL})]` dans ce cas.
378
 *     `#GET{toto}` retourne la valeur sauvegardée.
379
 *
380
 * @example
381
 *     `[(#CALCUL|set{toto,1})]` enregistre le résultat de `#CALCUL`
382
 *      dans la variable toto et renvoie la valeur. Cela permet d'utiliser
383
 *      d'autres filtres ensuite. `#GET{toto}` retourne la valeur.
384
 *
385
 * @filtre
386
 * @param array $Pile Pile de données
387
 * @param mixed $val Valeur à sauver
388
 * @param string $key Clé d'enregistrement
389
 * @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...
390
 * @return mixed
391
 */
392
function filtre_set(&$Pile, $val, $key, $continue = null) {
393
	$Pile['vars'][$key] = $val;
394
	return $continue ? $val : '';
395
}
396
397
/**
398
 * Filtre `setenv` qui enregistre une valeur dans l'environnement du squelette
399
 *
400
 * La valeur pourra être retrouvée avec `#ENV{variable}`.
401
 * 
402
 * @example
403
 *     `[(#CALCUL|setenv{toto})]` enregistre le résultat de `#CALCUL`
404
 *      dans l'environnement toto et renvoie vide.
405
 *      `#ENV{toto}` retourne la valeur.
406
 *
407
 *      `[(#CALCUL|setenv{toto,1})]` enregistre le résultat de `#CALCUL`
408
 *      dans l'environnement toto et renvoie la valeur.
409
 *      `#ENV{toto}` retourne la valeur.
410
 *
411
 * @filtre
412
 *
413
 * @param array $Pile
414
 * @param mixed $val Valeur à enregistrer
415
 * @param mixed $key Nom de la variable
416
 * @param null|mixed $continue Si présent, retourne la valeur en sortie
417
 * @return string|mixed Retourne `$val` si `$continue` présent, sinon ''.
418
 */
419
function filtre_setenv(&$Pile, $val, $key, $continue = null) {
420
	$Pile[0][$key] = $val;
421
	return $continue ? $val : '';
422
}
423
424
/**
425
 * @param array $Pile
426
 * @param array|string $keys
427
 * @return string
428
 */
429
function filtre_sanitize_env(&$Pile, $keys) {
430
	$Pile[0] = spip_sanitize_from_request($Pile[0], $keys);
431
	return '';
432
}
433
434
435
/**
436
 * Filtre `debug` qui affiche un debug de la valeur en entrée
437
 *
438
 * Log la valeur dans `debug.log` et l'affiche si on est webmestre.
439
 *
440
 * @example
441
 *     `[(#TRUC|debug)]` affiche et log la valeur de `#TRUC`
442
 * @example
443
 *     `[(#TRUC|debug{avant}|calcul|debug{apres}|etc)]`
444
 *     affiche la valeur de `#TRUC` avant et après le calcul,
445
 *     en précisant "avant" et "apres".
446
 *
447
 * @filtre
448
 * @link https://www.spip.net/5695
449
 * @param mixed $val La valeur à debugguer
450
 * @param mixed|null $key Clé pour s'y retrouver
451
 * @return mixed Retourne la valeur (sans la modifier).
452
 */
453
function filtre_debug($val, $key = null) {
454
	$debug = (
455
		is_null($key) ? '' : (var_export($key, true) . " = ")
456
		) . var_export($val, true);
457
458
	include_spip('inc/autoriser');
459
	if (autoriser('webmestre')) {
460
		echo "<div class='spip_debug'>\n", $debug, "</div>\n";
461
	}
462
463
	spip_log($debug, 'debug');
464
465
	return $val;
466
}
467
468
469
/**
470
 * Exécute un filtre image
471
 *
472
 * Fonction générique d'entrée des filtres images.
473
 * Accepte en entrée :
474
 *
475
 * - un texte complet,
476
 * - un img-log (produit par #LOGO_XX),
477
 * - un tag `<img ...>` complet,
478
 * - un nom de fichier *local* (passer le filtre `|copie_locale` si on veut
479
 *   l'appliquer à un document distant).
480
 *
481
 * Applique le filtre demande à chacune des occurrences
482
 *
483
 * @param array $args
484
 *     Liste des arguments :
485
 *
486
 *     - le premier est le nom du filtre image à appliquer
487
 *     - le second est le texte sur lequel on applique le filtre
488
 *     - les suivants sont les arguments du filtre image souhaité.
489
 * @return string
490
 *     Texte qui a reçu les filtres
491
 **/
492
function image_filtrer($args) {
493
	$filtre = array_shift($args); # enlever $filtre
494
	$texte = array_shift($args);
495
	if (!strlen($texte)) {
496
		return;
497
	}
498
	find_in_path('filtres_images_mini.php', 'inc/', true);
499
	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...
500
	// Cas du nom de fichier local
501
	$is_file = trim($texte);
502
	if (strpos(substr($is_file, strlen(_DIR_RACINE)), '..') !== false
503
		  or strpbrk($is_file, "<>\n\r\t") !== false
504
		  or strpos($is_file, '/') === 0
505
	) {
506
		$is_file = false;
507
	}
508
	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...
509
		$is_local_file = function($path) {
510
			if (strpos($path, "?") !== false) {
511
				$path = supprimer_timestamp($path);
512
				// remove ?24px added by find_in_theme on .svg files
513
				$path = preg_replace(",\?[[:digit:]]+(px)$,", "", $path);
514
			}
515
			return file_exists($path);
516
		};
517
		if ($is_local_file($is_file) or tester_url_absolue($is_file)) {
518
			array_unshift($args, "<img src='$is_file' />");
519
			$res = call_user_func_array($filtre, $args);
520
			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...
521
			return $res;
522
		}
523
	}
524
525
	// Cas general : trier toutes les images, avec eventuellement leur <span>
526
	if (preg_match_all(
527
		',(<([a-z]+) [^<>]*spip_documents[^<>]*>)?\s*(<img\s.*>),UimsS',
528
		$texte, $tags, PREG_SET_ORDER)) {
529
		foreach ($tags as $tag) {
530
			$class = extraire_attribut($tag[3], 'class');
531
			if (!$class or
532
				(strpos($class, 'filtre_inactif') === false
533
					// compat historique a virer en 3.2
534
					and strpos($class, 'no_image_filtrer') === false)
535
			) {
536
				array_unshift($args, $tag[3]);
537
				if ($reduit = call_user_func_array($filtre, $args)) {
538
					// En cas de span spip_documents, modifier le style=...width:
539
					if ($tag[1]) {
540
						$w = extraire_attribut($reduit, 'width');
541
						if (!$w and preg_match(",width:\s*(\d+)px,S", extraire_attribut($reduit, 'style'), $regs)) {
542
							$w = $regs[1];
543
						}
544
						if ($w and ($style = extraire_attribut($tag[1], 'style'))) {
545
							$style = preg_replace(",width:\s*\d+px,S", "width:${w}px", $style);
546
							$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 545 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...
547
							$texte = str_replace($tag[1], $replace, $texte);
548
						}
549
					}
550
					// traiter aussi un eventuel mouseover
551
					if ($mouseover = extraire_attribut($reduit, 'onmouseover')) {
552
						if (preg_match(",this[.]src=['\"]([^'\"]+)['\"],ims", $mouseover, $match)) {
553
							$srcover = $match[1];
554
							array_shift($args);
555
							array_unshift($args, "<img src='" . $match[1] . "' />");
556
							$srcover_filter = call_user_func_array($filtre, $args);
557
							$srcover_filter = extraire_attribut($srcover_filter, 'src');
558
							$reduit = str_replace($srcover, $srcover_filter, $reduit);
559
						}
560
					}
561
					$texte = str_replace($tag[3], $reduit, $texte);
562
				}
563
				array_shift($args);
564
			}
565
		}
566
	}
567
	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...
568
	return $texte;
569
}
570
571
/**
572
 * Retourne les tailles d'une image
573
 *
574
 * Pour les filtres `largeur` et `hauteur`
575
 *
576
 * @param string $img
577
 *     Balise HTML `<img ... />` ou chemin de l'image (qui peut être une URL distante).
578
 * @return array
579
 *     Liste (hauteur, largeur) en pixels
580
 **/
581
function taille_image($img, $force_refresh = false) {
582
583
	static $largeur_img = array(), $hauteur_img = array();
584
	$srcWidth = 0;
585
	$srcHeight = 0;
586
587
	$src = extraire_attribut($img, 'src');
588
589
	if (!$src) {
590
		$src = $img;
591
	} else {
592
		$srcWidth = extraire_attribut($img, 'width');
593
		$srcHeight = extraire_attribut($img, 'height');
594
	}
595
596
	// ne jamais operer directement sur une image distante pour des raisons de perfo
597
	// la copie locale a toutes les chances d'etre la ou de resservir
598
	if (tester_url_absolue($src)) {
0 ignored issues
show
Bug introduced by
It seems like $src defined by extraire_attribut($img, 'src') on line 587 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...
599
		include_spip('inc/distant');
600
		$fichier = copie_locale($src);
0 ignored issues
show
Bug introduced by
It seems like $src defined by extraire_attribut($img, 'src') on line 587 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...
601
		$src = $fichier ? _DIR_RACINE . $fichier : $src;
602
	}
603 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...
604
		$src = substr($src, 0, $p);
605
	}
606
607
	$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...
608
	if (isset($largeur_img[$src]) and !$force_refresh) {
609
		$srcWidth = $largeur_img[$src];
610
	}
611
	if (isset($hauteur_img[$src]) and !$force_refresh) {
612
		$srcHeight = $hauteur_img[$src];
613
	}
614
	if (!$srcWidth or !$srcHeight) {
615
616
		if (file_exists($src)
617
			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...
618
		) {
619
			if (!$srcWidth) {
620
				$largeur_img[$src] = $srcWidth = $srcsize[0];
621
			}
622
			if (!$srcHeight) {
623
				$hauteur_img[$src] = $srcHeight = $srcsize[1];
624
			}
625
		}
626
		elseif(strpos($src, "<svg") !== false) {
627
			include_spip('inc/svg');
628
			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...
629
				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...
630
				if (!$srcWidth){
631
					$largeur_img[$src] = $srcWidth = $width;
632
				}
633
				if (!$srcHeight){
634
					$hauteur_img[$src] = $srcHeight = $height;
635
				}
636
			}
637
		}
638
		// $src peut etre une reference a une image temporaire dont a n'a que le log .src
639
		// on s'y refere, l'image sera reconstruite en temps utile si necessaire
640
		elseif (@file_exists($f = "$src.src")
641
			and lire_fichier($f, $valeurs)
642
			and $valeurs = unserialize($valeurs)
643
		) {
644
			if (!$srcWidth) {
645
				$largeur_img[$src] = $srcWidth = $valeurs["largeur_dest"];
646
			}
647
			if (!$srcHeight) {
648
				$hauteur_img[$src] = $srcHeight = $valeurs["hauteur_dest"];
649
			}
650
		}
651
	}
652
653
	return array($srcHeight, $srcWidth);
654
}
655
656
657
/**
658
 * Retourne la largeur d'une image
659
 *
660
 * @filtre
661
 * @link https://www.spip.net/4296
662
 * @uses taille_image()
663
 * @see  hauteur()
664
 *
665
 * @param string $img
666
 *     Balise HTML `<img ... />` ou chemin de l'image (qui peut être une URL distante).
667
 * @return int|null
668
 *     Largeur en pixels, NULL ou 0 si aucune image.
669
 **/
670
function largeur($img) {
671
	if (!$img) {
672
		return;
673
	}
674
	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...
675
676
	return $l;
677
}
678
679
/**
680
 * Retourne la hauteur d'une image
681
 *
682
 * @filtre
683
 * @link https://www.spip.net/4291
684
 * @uses taille_image()
685
 * @see  largeur()
686
 *
687
 * @param string $img
688
 *     Balise HTML `<img ... />` ou chemin de l'image (qui peut être une URL distante).
689
 * @return int|null
690
 *     Hauteur en pixels, NULL ou 0 si aucune image.
691
 **/
692
function hauteur($img) {
693
	if (!$img) {
694
		return;
695
	}
696
	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...
697
698
	return $h;
699
}
700
701
702
/**
703
 * Échappement des entités HTML avec correction des entités « brutes »
704
 *
705
 * Ces entités peuvent être générées par les butineurs lorsqu'on rentre des
706
 * caractères n'appartenant pas au charset de la page [iso-8859-1 par défaut]
707
 *
708
 * Attention on limite cette correction aux caracteres « hauts » (en fait > 99
709
 * pour aller plus vite que le > 127 qui serait logique), de manière à
710
 * préserver des eéhappements de caractères « bas » (par exemple `[` ou `"`)
711
 * et au cas particulier de `&amp;` qui devient `&amp;amp;` dans les URL
712
 *
713
 * @see corriger_toutes_entites_html()
714
 * @param string $texte
715
 * @return string
716
 **/
717
function corriger_entites_html($texte) {
718
	if (strpos($texte, '&amp;') === false) {
719
		return $texte;
720
	}
721
722
	return preg_replace(',&amp;(#[0-9][0-9][0-9]+;|amp;),iS', '&\1', $texte);
723
}
724
725
/**
726
 * Échappement des entités HTML avec correction des entités « brutes » ainsi
727
 * que les `&amp;eacute;` en `&eacute;`
728
 *
729
 * Identique à `corriger_entites_html()` en corrigeant aussi les
730
 * `&amp;eacute;` en `&eacute;`
731
 *
732
 * @see corriger_entites_html()
733
 * @param string $texte
734
 * @return string
735
 **/
736
function corriger_toutes_entites_html($texte) {
737
	if (strpos($texte, '&amp;') === false) {
738
		return $texte;
739
	}
740
741
	return preg_replace(',&amp;(#?[a-z0-9]+;),iS', '&\1', $texte);
742
}
743
744
/**
745
 * Échappe les `&` en `&amp;`
746
 *
747
 * @param string $texte
748
 * @return string
749
 **/
750
function proteger_amp($texte) {
751
	return str_replace('&', '&amp;', $texte);
752
}
753
754
755
/**
756
 * Échappe en entités HTML certains caractères d'un texte
757
 *
758
 * Traduira un code HTML en transformant en entités HTML les caractères
759
 * en dehors du charset de la page ainsi que les `"`, `<` et `>`.
760
 *
761
 * Ceci permet d’insérer le texte d’une balise dans un `<textarea> </textarea>`
762
 * sans dommages.
763
 *
764
 * @filtre
765
 * @link https://www.spip.net/4280
766
 *
767
 * @uses echappe_html()
768
 * @uses echappe_retour()
769
 * @uses proteger_amp()
770
 * @uses corriger_entites_html()
771
 * @uses corriger_toutes_entites_html()
772
 *
773
 * @param string $texte
774
 *   chaine a echapper
775
 * @param bool $tout
776
 *   corriger toutes les `&amp;xx;` en `&xx;`
777
 * @param bool $quote
778
 *   Échapper aussi les simples quotes en `&#039;`
779
 * @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...
780
 */
781
function entites_html($texte, $tout = false, $quote = true) {
782 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...
783
		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...
784
	) {
785
		return $texte;
786
	}
787
	include_spip('inc/texte');
788
	$flags = ($quote ? ENT_QUOTES : ENT_NOQUOTES);
789
	$flags |= ENT_HTML401;
790
	$texte = spip_htmlspecialchars(echappe_retour(echappe_html($texte, '', true), '', 'proteger_amp'), $flags);
791
	if ($tout) {
792
		return corriger_toutes_entites_html($texte);
793
	} else {
794
		return corriger_entites_html($texte);
795
	}
796
}
797
798
/**
799
 * Convertit les caractères spéciaux HTML dans le charset du site.
800
 *
801
 * @exemple
802
 *     Si le charset de votre site est `utf-8`, `&eacute;` ou `&#233;`
803
 *     sera transformé en `é`
804
 *
805
 * @filtre
806
 * @link https://www.spip.net/5513
807
 *
808
 * @param string $texte
809
 *     Texte à convertir
810
 * @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...
811
 *     Texte converti
812
 **/
813
function filtrer_entites($texte) {
814
	if (strpos($texte, '&') === false) {
815
		return $texte;
816
	}
817
	// filtrer
818
	$texte = html2unicode($texte);
819
	// remettre le tout dans le charset cible
820
	$texte = unicode2charset($texte);
821
	// cas particulier des " et ' qu'il faut filtrer aussi
822
	// (on le faisait deja avec un &quot;)
823 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...
824
		$texte = str_replace(array("&#039;", "&#39;", "&#034;", "&#34;"), array("'", "'", '"', '"'), $texte);
825
	}
826
827
	return $texte;
828
}
829
830
831
if (!function_exists('filtre_filtrer_entites_dist')) {
832
	/**
833
	 * Version sécurisée de filtrer_entites
834
	 * 
835
	 * @uses interdire_scripts()
836
	 * @uses filtrer_entites()
837
	 * 
838
	 * @param string $t
839
	 * @return string
840
	 */
841
	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...
842
		include_spip('inc/texte');
843
		return interdire_scripts(filtrer_entites($t));
844
	}
845
}
846
847
848
/**
849
 * Supprime des caractères illégaux
850
 *
851
 * Remplace les caractères de controle par le caractère `-`
852
 *
853
 * @link http://www.w3.org/TR/REC-xml/#charsets
854
 *
855
 * @param string|array $texte
856
 * @return string|array
857
 **/
858
function supprimer_caracteres_illegaux($texte) {
859
	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";
860
	static $to = null;
861
862
	if (is_array($texte)) {
863
		return array_map('supprimer_caracteres_illegaux', $texte);
864
	}
865
866
	if (!$to) {
867
		$to = str_repeat('-', strlen($from));
868
	}
869
870
	return strtr($texte, $from, $to);
871
}
872
873
/**
874
 * Correction de caractères
875
 *
876
 * Supprimer les caracteres windows non conformes et les caracteres de controle illégaux
877
 *
878
 * @param string|array $texte
879
 * @return string|array
880
 **/
881
function corriger_caracteres($texte) {
882
	$texte = corriger_caracteres_windows($texte);
883
	$texte = supprimer_caracteres_illegaux($texte);
884
885
	return $texte;
886
}
887
888
/**
889
 * Encode du HTML pour transmission XML notamment dans les flux RSS
890
 *
891
 * Ce filtre transforme les liens en liens absolus, importe les entitées html et échappe les tags html.
892
 *
893
 * @filtre
894
 * @link https://www.spip.net/4287
895
 *
896
 * @param string $texte
897
 *     Texte à transformer
898
 * @return string
899
 *     Texte encodé pour XML
900
 */
901
function texte_backend($texte) {
902
903
	static $apostrophe = array("&#8217;", "'"); # n'allouer qu'une fois
904
905
	// si on a des liens ou des images, les passer en absolu
906
	$texte = liens_absolus($texte);
907
908
	// echapper les tags &gt; &lt;
909
	$texte = preg_replace(',&(gt|lt);,S', '&amp;\1;', $texte);
910
911
	// importer les &eacute;
912
	$texte = filtrer_entites($texte);
913
914
	// " -> &quot; et tout ce genre de choses
915
	$u = $GLOBALS['meta']['pcre_u'];
916
	$texte = str_replace("&nbsp;", " ", $texte);
917
	$texte = preg_replace('/\s{2,}/S' . $u, " ", $texte);
918
	// ne pas echapper les sinqle quotes car certains outils de syndication gerent mal
919
	$texte = entites_html($texte, false, false);
920
	// mais bien echapper les double quotes !
921
	$texte = str_replace('"', '&#034;', $texte);
922
923
	// verifier le charset
924
	$texte = charset2unicode($texte);
925
926
	// Caracteres problematiques en iso-latin 1
927
	if (isset($GLOBALS['meta']['charset']) and $GLOBALS['meta']['charset'] == 'iso-8859-1') {
928
		$texte = str_replace(chr(156), '&#156;', $texte);
929
		$texte = str_replace(chr(140), '&#140;', $texte);
930
		$texte = str_replace(chr(159), '&#159;', $texte);
931
	}
932
933
	// l'apostrophe curly pose probleme a certains lecteure de RSS
934
	// et le caractere apostrophe alourdit les squelettes avec PHP
935
	// ==> on les remplace par l'entite HTML
936
	return str_replace($apostrophe, "'", $texte);
937
}
938
939
/**
940
 * Encode et quote du HTML pour transmission XML notamment dans les flux RSS
941
 *
942
 * Comme texte_backend(), mais avec addslashes final pour squelettes avec PHP (rss)
943
 *
944
 * @uses texte_backend()
945
 * @filtre
946
 *
947
 * @param string $texte
948
 *     Texte à transformer
949
 * @return string
950
 *     Texte encodé et quote pour XML
951
 */
952
function texte_backendq($texte) {
953
	return addslashes(texte_backend($texte));
954
}
955
956
957
/**
958
 * Enlève un numéro préfixant un texte
959
 *
960
 * Supprime `10. ` dans la chaine `10. Titre`
961
 *
962
 * @filtre
963
 * @link https://www.spip.net/4314
964
 * @see recuperer_numero() Pour obtenir le numéro
965
 * @example
966
 *     ```
967
 *     [<h1>(#TITRE|supprimer_numero)</h1>]
968
 *     ```
969
 *
970
 * @param string $texte
971
 *     Texte
972
 * @return int|string
973
 *     Numéro de titre, sinon chaîne vide
974
 **/
975
function supprimer_numero($texte) {
976
	return preg_replace(
977
		",^[[:space:]]*([0-9]+)([.)]|" . chr(194) . '?' . chr(176) . ")[[:space:]]+,S",
978
		"", $texte);
979
}
980
981
/**
982
 * Récupère un numéro préfixant un texte
983
 *
984
 * Récupère le numéro `10` dans la chaine `10. Titre`
985
 *
986
 * @filtre
987
 * @link https://www.spip.net/5514
988
 * @see supprimer_numero() Pour supprimer le numéro
989
 * @see balise_RANG_dist() Pour obtenir un numéro de titre
990
 * @example
991
 *     ```
992
 *     [(#TITRE|recuperer_numero)]
993
 *     ```
994
 *
995
 * @param string $texte
996
 *     Texte
997
 * @return int|string
998
 *     Numéro de titre, sinon chaîne vide
999
 **/
1000
function recuperer_numero($texte) {
1001
	if (preg_match(
1002
		",^[[:space:]]*([0-9]+)([.)]|" . chr(194) . '?' . chr(176) . ")[[:space:]]+,S",
1003
		$texte, $regs)) {
1004
		return strval($regs[1]);
1005
	} else {
1006
		return '';
1007
	}
1008
}
1009
1010
/**
1011
 * Suppression basique et brutale de tous les tags
1012
 *
1013
 * Supprime tous les tags `<...>`.
1014
 * Utilisé fréquemment pour écrire des RSS.
1015
 *
1016
 * @filtre
1017
 * @link https://www.spip.net/4315
1018
 * @example
1019
 *     ```
1020
 *     <title>[(#TITRE|supprimer_tags|texte_backend)]</title>
1021
 *     ```
1022
 *
1023
 * @note
1024
 *     Ce filtre supprime aussi les signes inférieurs `<` rencontrés.
1025
 *
1026
 * @param string $texte
1027
 *     Texte à échapper
1028
 * @param string $rempl
1029
 *     Inutilisé.
1030
 * @return string
1031
 *     Texte converti
1032
 **/
1033
function supprimer_tags($texte, $rempl = "") {
1034
	$texte = preg_replace(",<(!--|\w|/|!\[endif|!\[if)[^>]*>,US", $rempl, $texte);
1035
	// ne pas oublier un < final non ferme car coupe
1036
	$texte = preg_replace(",<(!--|\w|/).*$,US", $rempl, $texte);
1037
	// mais qui peut aussi etre un simple signe plus petit que
1038
	$texte = str_replace('<', '&lt;', $texte);
1039
1040
	return $texte;
1041
}
1042
1043
/**
1044
 * Convertit les chevrons de tag en version lisible en HTML
1045
 *
1046
 * Transforme les chevrons de tag `<...>` en entité HTML.
1047
 *
1048
 * @filtre
1049
 * @link https://www.spip.net/5515
1050
 * @example
1051
 *     ```
1052
 *     <pre>[(#TEXTE|echapper_tags)]</pre>
1053
 *     ```
1054
 *
1055
 * @param string $texte
1056
 *     Texte à échapper
1057
 * @param string $rempl
1058
 *     Inutilisé.
1059
 * @return string
1060
 *     Texte converti
1061
 **/
1062
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...
1063
	$texte = preg_replace("/<([^>]*)>/", "&lt;\\1&gt;", $texte);
1064
1065
	return $texte;
1066
}
1067
1068
/**
1069
 * Convertit un texte HTML en texte brut
1070
 *
1071
 * Enlève les tags d'un code HTML, élimine les doubles espaces.
1072
 *
1073
 * @filtre
1074
 * @link https://www.spip.net/4317
1075
 * @example
1076
 *     ```
1077
 *     <title>[(#TITRE|textebrut) - ][(#NOM_SITE_SPIP|textebrut)]</title>
1078
 *     ```
1079
 *
1080
 * @param string $texte
1081
 *     Texte à convertir
1082
 * @return string
1083
 *     Texte converti
1084
 **/
1085
function textebrut($texte) {
1086
	$u = $GLOBALS['meta']['pcre_u'];
1087
	$texte = preg_replace('/\s+/S' . $u, " ", $texte);
1088
	$texte = preg_replace("/<(p|br)( [^>]*)?" . ">/iS", "\n\n", $texte);
1089
	$texte = preg_replace("/^\n+/", "", $texte);
1090
	$texte = preg_replace("/\n+$/", "", $texte);
1091
	$texte = preg_replace("/\n +/", "\n", $texte);
1092
	$texte = supprimer_tags($texte);
1093
	$texte = preg_replace("/(&nbsp;| )+/S", " ", $texte);
1094
	// nettoyer l'apostrophe curly qui pose probleme a certains rss-readers, lecteurs de mail...
1095
	$texte = str_replace("&#8217;", "'", $texte);
1096
1097
	return $texte;
1098
}
1099
1100
1101
/**
1102
 * Remplace les liens SPIP en liens ouvrant dans une nouvelle fenetre (target=blank)
1103
 *
1104
 * @filtre
1105
 * @link https://www.spip.net/4297
1106
 *
1107
 * @param string $texte
1108
 *     Texte avec des liens
1109
 * @return string
1110
 *     Texte avec liens ouvrants
1111
 **/
1112
function liens_ouvrants($texte) {
1113
	if (preg_match_all(",(<a\s+[^>]*https?://[^>]*class=[\"']spip_(out|url)\b[^>]+>),imsS",
1114
		$texte, $liens, PREG_PATTERN_ORDER)) {
1115
		foreach ($liens[0] as $a) {
1116
			$rel = 'noopener noreferrer ' . extraire_attribut($a, 'rel');
1117
			$ablank = inserer_attribut($a, 'rel', $rel);
1118
			$ablank = inserer_attribut($ablank, 'target', '_blank');
1119
			$texte = str_replace($a, $ablank, $texte);
1120
		}
1121
	}
1122
1123
	return $texte;
1124
}
1125
1126
/**
1127
 * Ajouter un attribut rel="nofollow" sur tous les liens d'un texte
1128
 *
1129
 * @param string $texte
1130
 * @return string
1131
 */
1132
function liens_nofollow($texte) {
1133
	if (stripos($texte, "<a") === false) {
1134
		return $texte;
1135
	}
1136
1137
	if (preg_match_all(",<a\b[^>]*>,UimsS", $texte, $regs, PREG_PATTERN_ORDER)) {
1138
		foreach ($regs[0] as $a) {
1139
			$rel = extraire_attribut($a, "rel");
1140
			if (strpos($rel, "nofollow") === false) {
1141
				$rel = "nofollow" . ($rel ? " $rel" : "");
1142
				$anofollow = inserer_attribut($a, "rel", $rel);
1143
				$texte = str_replace($a, $anofollow, $texte);
1144
			}
1145
		}
1146
	}
1147
1148
	return $texte;
1149
}
1150
1151
/**
1152
 * Transforme les sauts de paragraphe HTML `p` en simples passages à la ligne `br`
1153
 *
1154
 * @filtre
1155
 * @link https://www.spip.net/4308
1156
 * @example
1157
 *     ```
1158
 *     [<div>(#DESCRIPTIF|PtoBR)[(#NOTES|PtoBR)]</div>]
1159
 *     ```
1160
 *
1161
 * @param string $texte
1162
 *     Texte à transformer
1163
 * @return string
1164
 *     Texte sans paraghaphes
1165
 **/
1166
function PtoBR($texte) {
1167
	$u = $GLOBALS['meta']['pcre_u'];
1168
	$texte = preg_replace("@</p>@iS", "\n", $texte);
1169
	$texte = preg_replace("@<p\b.*>@UiS", "<br />", $texte);
1170
	$texte = preg_replace("@^\s*<br />@S" . $u, "", $texte);
1171
1172
	return $texte;
1173
}
1174
1175
1176
/**
1177
 * Assure qu'un texte ne vas pas déborder d'un bloc
1178
 * par la faute d'un mot trop long (souvent des URLs)
1179
 *
1180
 * Ne devrait plus être utilisé et fait directement en CSS par un style
1181
 * `word-wrap:break-word;`
1182
 *
1183
 * @note
1184
 *   Pour assurer la compatibilité du filtre, on encapsule le contenu par
1185
 *   un `div` ou `span` portant ce style CSS inline.
1186
 *
1187
 * @filtre
1188
 * @link https://www.spip.net/4298
1189
 * @link http://www.alsacreations.com/tuto/lire/1038-gerer-debordement-contenu-css.html
1190
 * @deprecated Utiliser le style CSS `word-wrap:break-word;`
1191
 *
1192
 * @param string $texte Texte
1193
 * @return string Texte encadré du style CSS
1194
 */
1195
function lignes_longues($texte) {
1196
	if (!strlen(trim($texte))) {
1197
		return $texte;
1198
	}
1199
	include_spip('inc/texte');
1200
	$tag = preg_match(',</?(' . _BALISES_BLOCS . ')[>[:space:]],iS', $texte) ?
1201
		'div' : 'span';
1202
1203
	return "<$tag style='word-wrap:break-word;'>$texte</$tag>";
1204
}
1205
1206
/**
1207
 * Passe un texte en majuscules, y compris les accents, en HTML
1208
 *
1209
 * Encadre le texte du style CSS `text-transform: uppercase;`.
1210
 * Le cas spécifique du i turc est géré.
1211
 *
1212
 * @filtre
1213
 * @example
1214
 *     ```
1215
 *     [(#EXTENSION|majuscules)]
1216
 *     ```
1217
 *
1218
 * @param string $texte Texte
1219
 * @return string Texte en majuscule
1220
 */
1221
function majuscules($texte) {
1222
	if (!strlen($texte)) {
1223
		return '';
1224
	}
1225
1226
	// Cas du turc
1227
	if ($GLOBALS['spip_lang'] == 'tr') {
1228
		# remplacer hors des tags et des entites
1229 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...
1230
			foreach ($regs as $n => $match) {
1231
				$texte = str_replace($match[0], "@@SPIP_TURC$n@@", $texte);
1232
			}
1233
		}
1234
1235
		$texte = str_replace('i', '&#304;', $texte);
1236
1237 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...
1238
			foreach ($regs as $n => $match) {
1239
				$texte = str_replace("@@SPIP_TURC$n@@", $match[0], $texte);
1240
			}
1241
		}
1242
	}
1243
1244
	// Cas general
1245
	return "<span style='text-transform: uppercase;'>$texte</span>";
1246
}
1247
1248
/**
1249
 * Retourne une taille en octets humainement lisible
1250
 *
1251
 * Tel que "127.4 ko" ou "3.1 Mo"
1252
 *
1253
 * @example
1254
 *     - `[(#TAILLE|taille_en_octets)]`
1255
 *     - `[(#VAL{123456789}|taille_en_octets)]` affiche `117.7 Mo`
1256
 *
1257
 * @filtre
1258
 * @link https://www.spip.net/4316
1259
 * @param int $taille
1260
 * @return string
1261
 **/
1262
function taille_en_octets($taille) {
1263
	if (!defined('_KILOBYTE')) {
1264
		/**
1265
		 * Définit le nombre d'octets dans un Kilobyte
1266
		 *
1267
		 * @var int
1268
		 **/
1269
		define('_KILOBYTE', 1024);
1270
	}
1271
1272
	if ($taille < 1) {
1273
		return '';
1274
	}
1275
	if ($taille < _KILOBYTE) {
1276
		$taille = _T('taille_octets', array('taille' => $taille));
1277 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...
1278
		$taille = _T('taille_ko', array('taille' => round($taille / _KILOBYTE, 1)));
1279
	} elseif ($taille < _KILOBYTE * _KILOBYTE * _KILOBYTE) {
1280
		$taille = _T('taille_mo', array('taille' => round($taille / _KILOBYTE / _KILOBYTE, 1)));
1281 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...
1282
		$taille = _T('taille_go', array('taille' => round($taille / _KILOBYTE / _KILOBYTE / _KILOBYTE, 2)));
1283
	}
1284
1285
	return $taille;
1286
}
1287
1288
1289
/**
1290
 * Rend une chaine utilisable sans dommage comme attribut HTML
1291
 *
1292
 * @example `<a href="#URL_ARTICLE" title="[(#TITRE|attribut_html)]">#TITRE</a>`
1293
 *
1294
 * @filtre
1295
 * @link https://www.spip.net/4282
1296
 * @uses textebrut()
1297
 * @uses texte_backend()
1298
 *
1299
 * @param string $texte
1300
 *     Texte à mettre en attribut
1301
 * @param bool $textebrut
1302
 *     Passe le texte en texte brut (enlève les balises html) ?
1303
 * @return string
1304
 *     Texte prêt pour être utilisé en attribut HTML
1305
 **/
1306
function attribut_html($texte, $textebrut = true) {
1307
	$u = $GLOBALS['meta']['pcre_u'];
1308
	if ($textebrut) {
1309
		$texte = preg_replace(array(",\n,", ",\s(?=\s),msS" . $u), array(" ", ""), textebrut($texte));
1310
	}
1311
	$texte = texte_backend($texte);
1312
	$texte = str_replace(array("'", '"'), array('&#039;', '&#034;'), $texte);
1313
1314
	return preg_replace(array("/&(amp;|#38;)/", "/&(?![A-Za-z]{0,4}\w{2,3};|#[0-9]{2,5};)/"), array("&", "&#38;"),
1315
		$texte);
1316
}
1317
1318
1319
/**
1320
 * Vider les URL nulles
1321
 *
1322
 * - Vide les URL vides comme `http://` ou `mailto:` (sans rien d'autre)
1323
 * - échappe les entités et gère les `&amp;`
1324
 *
1325
 * @uses entites_html()
1326
 *
1327
 * @param string $url
1328
 *     URL à vérifier et échapper
1329
 * @param bool $entites
1330
 *     `true` pour échapper les entités HTML.
1331
 * @return string
1332
 *     URL ou chaîne vide
1333
 **/
1334
function vider_url($url, $entites = true) {
1335
	# un message pour abs_url
1336
	$GLOBALS['mode_abs_url'] = 'url';
1337
	$url = trim($url);
1338
	$r = ",^(?:" . _PROTOCOLES_STD . '):?/?/?$,iS';
1339
1340
	return preg_match($r, $url) ? '' : ($entites ? entites_html($url) : $url);
1341
}
1342
1343
1344
/**
1345
 * Maquiller une adresse e-mail
1346
 *
1347
 * Remplace `@` par 3 caractères aléatoires.
1348
 *
1349
 * @uses creer_pass_aleatoire()
1350
 *
1351
 * @param string $texte Adresse email
1352
 * @return string Adresse email maquillée
1353
 **/
1354
function antispam($texte) {
1355
	include_spip('inc/acces');
1356
	$masque = creer_pass_aleatoire(3);
1357
1358
	return preg_replace("/@/", " $masque ", $texte);
1359
}
1360
1361
/**
1362
 * Vérifie un accès à faible sécurité
1363
 *
1364
 * Vérifie qu'un visiteur peut accéder à la page demandée,
1365
 * qui est protégée par une clé, calculée à partir du low_sec de l'auteur,
1366
 * et des paramètres le composant l'appel (op, args)
1367
 *
1368
 * @example
1369
 *     `[(#ID_AUTEUR|securiser_acces{#ENV{cle}, rss, #ENV{op}, #ENV{args}}|sinon_interdire_acces)]`
1370
 *
1371
 * @see  bouton_spip_rss() pour générer un lien de faible sécurité pour les RSS privés
1372
 * @see  afficher_low_sec() pour calculer une clé valide
1373
 * @uses verifier_low_sec()
1374
 *
1375
 * @filtre
1376
 * @param int $id_auteur
1377
 *     L'auteur qui demande la page
1378
 * @param string $cle
1379
 *     La clé à tester
1380
 * @param string $dir
1381
 *     Un type d'accès (nom du répertoire dans lequel sont rangés les squelettes demandés, tel que 'rss')
1382
 * @param string $op
1383
 *     Nom de l'opération éventuelle
1384
 * @param string $args
1385
 *     Nom de l'argument calculé
1386
 * @return bool
1387
 *     True si on a le droit d'accès, false sinon.
1388
 **/
1389
function securiser_acces($id_auteur, $cle, $dir, $op = '', $args = '') {
1390
	include_spip('inc/acces');
1391
	if ($op) {
1392
		$dir .= " $op $args";
1393
	}
1394
1395
	return verifier_low_sec($id_auteur, $cle, $dir);
1396
}
1397
1398
/**
1399
 * Retourne le second paramètre lorsque
1400
 * le premier est considere vide, sinon retourne le premier paramètre.
1401
 *
1402
 * En php `sinon($a, 'rien')` retourne `$a`, ou `'rien'` si `$a` est vide.
1403
 * En filtre SPIP `|sinon{#TEXTE, rien}` : affiche `#TEXTE` ou `rien` si `#TEXTE` est vide,
1404
 *
1405
 * @filtre
1406
 * @see filtre_logique() pour la compilation du filtre dans un squelette
1407
 * @link https://www.spip.net/4313
1408
 * @note
1409
 *     L'utilisation de `|sinon` en tant que filtre de squelette
1410
 *     est directement compilé dans `public/references` par la fonction `filtre_logique()`
1411
 *
1412
 * @param mixed $texte
1413
 *     Contenu de reference a tester
1414
 * @param mixed $sinon
1415
 *     Contenu a retourner si le contenu de reference est vide
1416
 * @return mixed
1417
 *     Retourne $texte, sinon $sinon.
1418
 **/
1419
function sinon($texte, $sinon = '') {
1420
	if ($texte or (!is_array($texte) and strlen($texte))) {
1421
		return $texte;
1422
	} else {
1423
		return $sinon;
1424
	}
1425
}
1426
1427
/**
1428
 * Filtre `|choixsivide{vide, pas vide}` alias de `|?{si oui, si non}` avec les arguments inversés
1429
 *
1430
 * @example
1431
 *     `[(#TEXTE|choixsivide{vide, plein})]` affiche vide si le `#TEXTE`
1432
 *     est considéré vide par PHP (chaîne vide, false, 0, tableau vide, etc…).
1433
 *     C'est l'équivalent de `[(#TEXTE|?{plein, vide})]`
1434
 *
1435
 * @filtre
1436
 * @see choixsiegal()
1437
 * @link https://www.spip.net/4189
1438
 *
1439
 * @param mixed $a
1440
 *     La valeur à tester
1441
 * @param mixed $vide
1442
 *     Ce qui est retourné si `$a` est considéré vide
1443
 * @param mixed $pasvide
1444
 *     Ce qui est retourné sinon
1445
 * @return mixed
1446
 **/
1447
function choixsivide($a, $vide, $pasvide) {
1448
	return $a ? $pasvide : $vide;
1449
}
1450
1451
/**
1452
 * Filtre `|choixsiegal{valeur, sioui, sinon}`
1453
 *
1454
 * @example
1455
 *     `#LANG_DIR|choixsiegal{ltr,left,right}` retourne `left` si
1456
 *      `#LANG_DIR` vaut `ltr` et `right` sinon.
1457
 *
1458
 * @filtre
1459
 * @link https://www.spip.net/4148
1460
 *
1461
 * @param mixed $a1
1462
 *     La valeur à tester
1463
 * @param mixed $a2
1464
 *     La valeur de comparaison
1465
 * @param mixed $v
1466
 *     Ce qui est retourné si la comparaison est vraie
1467
 * @param mixed $f
1468
 *     Ce qui est retourné sinon
1469
 * @return mixed
1470
 **/
1471
function choixsiegal($a1, $a2, $v, $f) {
1472
	return ($a1 == $a2) ? $v : $f;
1473
}
1474
1475
//
1476
// Export iCal
1477
//
1478
1479
/**
1480
 * Adapte un texte pour être inséré dans une valeur d'un export ICAL
1481
 *
1482
 * Passe le texte en utf8, enlève les sauts de lignes et échappe les virgules.
1483
 *
1484
 * @example `SUMMARY:[(#TITRE|filtrer_ical)]`
1485
 * @filtre
1486
 *
1487
 * @param string $texte
1488
 * @return string
1489
 **/
1490
function filtrer_ical($texte) {
1491
	#include_spip('inc/charsets');
1492
	$texte = html2unicode($texte);
1493
	$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...
1494
	$texte = preg_replace("/\n/", " ", $texte);
1495
	$texte = preg_replace("/,/", "\,", $texte);
1496
1497
	return $texte;
1498
}
1499
1500
1501
/**
1502
 * Transforme les sauts de ligne simples en sauts forcés avec `_ `
1503
 *
1504
 * Ne modifie pas les sauts de paragraphe (2 sauts consécutifs au moins),
1505
 * ou les retours à l'intérieur de modèles ou de certaines balises html.
1506
 *
1507
 * @note
1508
 *     Cette fonction pouvait être utilisée pour forcer les alinéas,
1509
 *     (retours à la ligne sans saut de paragraphe), mais ce traitement
1510
 *     est maintenant automatique.
1511
 *     Cf. plugin Textwheel et la constante _AUTOBR
1512
 *
1513
 * @uses echappe_html()
1514
 * @uses echappe_retour()
1515
 *
1516
 * @param string $texte
1517
 * @param string $delim
1518
 *      Ce par quoi sont remplacés les sauts
1519
 * @return string
1520
 **/
1521
function post_autobr($texte, $delim = "\n_ ") {
1522
	if (!function_exists('echappe_html')) {
1523
		include_spip('inc/texte_mini');
1524
	}
1525
	$texte = str_replace("\r\n", "\r", $texte);
1526
	$texte = str_replace("\r", "\n", $texte);
1527
1528 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...
1529
		$texte = substr($texte, 0, -strlen($fin = $fin[0]));
1530
	} else {
1531
		$fin = '';
1532
	}
1533
1534
	$texte = echappe_html($texte, '', true);
1535
1536
	// echapper les modeles
1537
	if (strpos($texte, "<") !== false) {
1538
		include_spip('inc/lien');
1539
		if (defined('_PREG_MODELE')) {
1540
			$preg_modeles = "@" . _PREG_MODELE . "@imsS";
1541
			$texte = echappe_html($texte, '', true, $preg_modeles);
1542
		}
1543
	}
1544
1545
	$debut = '';
1546
	$suite = $texte;
1547
	while ($t = strpos('-' . $suite, "\n", 1)) {
1548
		$debut .= substr($suite, 0, $t - 1);
1549
		$suite = substr($suite, $t);
1550
		$car = substr($suite, 0, 1);
1551
		if (($car <> '-') and ($car <> '_') and ($car <> "\n") and ($car <> "|") and ($car <> "}")
1552
			and !preg_match(',^\s*(\n|</?(quote|div|dl|dt|dd)|$),S', ($suite))
1553
			and !preg_match(',</?(quote|div|dl|dt|dd)> *$,iS', $debut)
1554
		) {
1555
			$debut .= $delim;
1556
		} else {
1557
			$debut .= "\n";
1558
		}
1559 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...
1560
			$debut .= $regs[0];
1561
			$suite = substr($suite, strlen($regs[0]));
1562
		}
1563
	}
1564
	$texte = $debut . $suite;
1565
1566
	$texte = echappe_retour($texte);
1567
1568
	return $texte . $fin;
1569
}
1570
1571
1572
/**
1573
 * Expression régulière pour obtenir le contenu des extraits idiomes `<:module:cle:>`
1574
 *
1575
 * @var string
1576
 */
1577
define('_EXTRAIRE_IDIOME', '@<:(?:([a-z0-9_]+):)?([a-z0-9_]+):>@isS');
1578
1579
/**
1580
 * Extrait une langue des extraits idiomes (`<:module:cle_de_langue:>`)
1581
 *
1582
 * Retrouve les balises `<:cle_de_langue:>` d'un texte et remplace son contenu
1583
 * par l'extrait correspondant à la langue demandée (si possible), sinon dans la
1584
 * langue par défaut du site.
1585
 *
1586
 * Ne pas mettre de span@lang=fr si on est déjà en fr.
1587
 *
1588
 * @filtre
1589
 * @uses inc_traduire_dist()
1590
 * @uses code_echappement()
1591
 * @uses echappe_retour()
1592
 *
1593
 * @param string $letexte
1594
 * @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...
1595
 *     Langue à retrouver (si vide, utilise la langue en cours).
1596
 * @param array $options Options {
1597
 * @type bool $echappe_span
1598
 *         True pour échapper les balises span (false par défaut)
1599
 * @type string $lang_defaut
1600
 *         Code de langue : permet de définir la langue utilisée par défaut,
1601
 *         en cas d'absence de traduction dans la langue demandée.
1602
 *         Par défaut la langue du site.
1603
 *         Indiquer 'aucune' pour ne pas retourner de texte si la langue
1604
 *         exacte n'a pas été trouvée.
1605
 * }
1606
 * @return string
1607
 **/
1608
function extraire_idiome($letexte, $lang = null, $options = array()) {
1609
	static $traduire = false;
1610
	if ($letexte
1611
		and preg_match_all(_EXTRAIRE_IDIOME, $letexte, $regs, PREG_SET_ORDER)
1612
	) {
1613
		if (!$traduire) {
1614
			$traduire = charger_fonction('traduire', 'inc');
1615
			include_spip('inc/lang');
1616
		}
1617
		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...
1618
			$lang = $GLOBALS['spip_lang'];
1619
		}
1620
		// Compatibilité avec le prototype de fonction précédente qui utilisait un boolean
1621
		if (is_bool($options)) {
1622
			$options = array('echappe_span' => $options);
1623
		}
1624 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...
1625
			$options = array_merge($options, array('echappe_span' => false));
1626
		}
1627
1628
		foreach ($regs as $reg) {
1629
			$cle = ($reg[1] ? $reg[1] . ':' : '') . $reg[2];
1630
			$desc = $traduire($cle, $lang, true);
1631
			$l = $desc->langue;
1632
			// si pas de traduction, on laissera l'écriture de l'idiome entier dans le texte.
1633
			if (strlen($desc->texte)) {
1634
				$trad = code_echappement($desc->texte, 'idiome', false);
1635
				if ($l !== $lang) {
1636
					$trad = str_replace("'", '"', inserer_attribut($trad, 'lang', $l));
1637
				}
1638 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...
1639
					$trad = str_replace("'", '"', inserer_attribut($trad, 'dir', lang_dir($l)));
1640
				}
1641
				if (!$options['echappe_span']) {
1642
					$trad = echappe_retour($trad, 'idiome');
1643
				}
1644
				$letexte = str_replace($reg[0], $trad, $letexte);
1645
			}
1646
		}
1647
	}
1648
	return $letexte;
1649
}
1650
1651
/**
1652
 * Expression régulière pour obtenir le contenu des extraits polyglottes `<multi>`
1653
 *
1654
 * @var string
1655
 */
1656
define('_EXTRAIRE_MULTI', "@<multi>(.*?)</multi>@sS");
1657
1658
1659
/**
1660
 * Extrait une langue des extraits polyglottes (`<multi>`)
1661
 *
1662
 * Retrouve les balises `<multi>` d'un texte et remplace son contenu
1663
 * par l'extrait correspondant à la langue demandée.
1664
 *
1665
 * Si la langue demandée n'est pas trouvée dans le multi, ni une langue
1666
 * approchante (exemple `fr` si on demande `fr_TU`), on retourne l'extrait
1667
 * correspondant à la langue par défaut (option 'lang_defaut'), qui est
1668
 * par défaut la langue du site. Et si l'extrait n'existe toujours pas
1669
 * dans cette langue, ça utilisera la première langue utilisée
1670
 * dans la balise `<multi>`.
1671
 *
1672
 * Ne pas mettre de span@lang=fr si on est déjà en fr.
1673
 *
1674
 * @filtre
1675
 * @link https://www.spip.net/5332
1676
 *
1677
 * @uses extraire_trads()
1678
 * @uses approcher_langue()
1679
 * @uses lang_typo()
1680
 * @uses code_echappement()
1681
 * @uses echappe_retour()
1682
 *
1683
 * @param string $letexte
1684
 * @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...
1685
 *     Langue à retrouver (si vide, utilise la langue en cours).
1686
 * @param array $options Options {
1687
 * @type bool $echappe_span
1688
 *         True pour échapper les balises span (false par défaut)
1689
 * @type string $lang_defaut
1690
 *         Code de langue : permet de définir la langue utilisée par défaut,
1691
 *         en cas d'absence de traduction dans la langue demandée.
1692
 *         Par défaut la langue du site.
1693
 *         Indiquer 'aucune' pour ne pas retourner de texte si la langue
1694
 *         exacte n'a pas été trouvée.
1695
 * }
1696
 * @return string
1697
 **/
1698
function extraire_multi($letexte, $lang = null, $options = array()) {
1699
1700
	if ($letexte
1701
		and preg_match_all(_EXTRAIRE_MULTI, $letexte, $regs, PREG_SET_ORDER)
1702
	) {
1703
		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...
1704
			$lang = $GLOBALS['spip_lang'];
1705
		}
1706
1707
		// Compatibilité avec le prototype de fonction précédente qui utilisait un boolean
1708
		if (is_bool($options)) {
1709
			$options = array('echappe_span' => $options, 'lang_defaut' => _LANGUE_PAR_DEFAUT);
1710
		}
1711 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...
1712
			$options = array_merge($options, array('echappe_span' => false));
1713
		}
1714
		if (!isset($options['lang_defaut'])) {
1715
			$options = array_merge($options, array('lang_defaut' => _LANGUE_PAR_DEFAUT));
1716
		}
1717
1718
		include_spip('inc/lang');
1719
		foreach ($regs as $reg) {
1720
			// chercher la version de la langue courante
1721
			$trads = extraire_trads($reg[1]);
1722
			if ($l = approcher_langue($trads, $lang)) {
1723
				$trad = $trads[$l];
1724
			} else {
1725
				if ($options['lang_defaut'] == 'aucune') {
1726
					$trad = '';
1727
				} else {
1728
					// langue absente, prendre le fr ou une langue précisée (meme comportement que inc/traduire.php)
1729
					// ou la premiere dispo
1730
					// mais typographier le texte selon les regles de celle-ci
1731
					// Attention aux blocs multi sur plusieurs lignes
1732
					if (!$l = approcher_langue($trads, $options['lang_defaut'])) {
1733
						$l = key($trads);
1734
					}
1735
					$trad = $trads[$l];
1736
					$typographie = charger_fonction(lang_typo($l), 'typographie');
1737
					$trad = $typographie($trad);
1738
					// Tester si on echappe en span ou en div
1739
					// il ne faut pas echapper en div si propre produit un seul paragraphe
1740
					include_spip('inc/texte');
1741
					$trad_propre = preg_replace(",(^<p[^>]*>|</p>$),Uims", "", propre($trad));
1742
					$mode = preg_match(',</?(' . _BALISES_BLOCS . ')[>[:space:]],iS', $trad_propre) ? 'div' : 'span';
1743
					if ($mode === 'div') {
1744
						$trad = rtrim($trad) . "\n\n";
1745
					}
1746
					$trad = code_echappement($trad, 'multi', false, $mode);
1747
					$trad = str_replace("'", '"', inserer_attribut($trad, 'lang', $l));
1748 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...
1749
						$trad = str_replace("'", '"', inserer_attribut($trad, 'dir', lang_dir($l)));
1750
					}
1751
					if (!$options['echappe_span']) {
1752
						$trad = echappe_retour($trad, 'multi');
1753
					}
1754
				}
1755
			}
1756
			$letexte = str_replace($reg[0], $trad, $letexte);
1757
		}
1758
	}
1759
1760
	return $letexte;
1761
}
1762
1763
/**
1764
 * Convertit le contenu d'une balise `<multi>` en un tableau
1765
 *
1766
 * Exemple de blocs.
1767
 * - `texte par défaut [fr] en français [en] en anglais`
1768
 * - `[fr] en français [en] en anglais`
1769
 *
1770
 * @param string $bloc
1771
 *     Le contenu intérieur d'un bloc multi
1772
 * @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...
1773
 *     Peut retourner un code de langue vide, lorsqu'un texte par défaut est indiqué.
1774
 **/
1775
function extraire_trads($bloc) {
1776
	$lang = '';
1777
// ce reg fait planter l'analyse multi s'il y a de l'{italique} dans le champ
1778
//	while (preg_match("/^(.*?)[{\[]([a-z_]+)[}\]]/siS", $bloc, $regs)) {
1779
	while (preg_match("/^(.*?)[\[]([a-z_]+)[\]]/siS", $bloc, $regs)) {
1780
		$texte = trim($regs[1]);
1781
		if ($texte or $lang) {
1782
			$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...
1783
		}
1784
		$bloc = substr($bloc, strlen($regs[0]));
1785
		$lang = $regs[2];
1786
	}
1787
	$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...
1788
1789
	return $trads;
1790
}
1791
1792
1793
/**
1794
 * Calculer l'initiale d'un nom
1795
 *
1796
 * @param string $nom
1797
 * @return string L'initiale en majuscule
1798
 */
1799
function filtre_initiale($nom) {
1800
	return spip_substr(trim(strtoupper(extraire_multi($nom))), 0, 1);
1801
}
1802
1803
1804
/**
1805
 * Retourne la donnée si c'est la première fois qu'il la voit
1806
 *
1807
 * Il est possible de gérer différentes "familles" de données avec
1808
 * le second paramètre.
1809
 *
1810
 * @filtre
1811
 * @link https://www.spip.net/4320
1812
 * @example
1813
 *     ```
1814
 *     [(#ID_SECTEUR|unique)]
1815
 *     [(#ID_SECTEUR|unique{tete})] n'a pas d'incidence sur
1816
 *     [(#ID_SECTEUR|unique{pied})]
1817
 *     [(#ID_SECTEUR|unique{pied,1})] affiche le nombre d'éléments.
1818
 *     Préférer totefois #TOTAL_UNIQUE{pied}
1819
 *     ```
1820
 *
1821
 * @todo
1822
 *    Ameliorations possibles :
1823
 *
1824
 *    1) si la donnée est grosse, mettre son md5 comme clé
1825
 *    2) purger $mem quand on change de squelette (sinon bug inclusions)
1826
 *
1827
 * @param string $donnee
1828
 *      Donnée que l'on souhaite unique
1829
 * @param string $famille
1830
 *      Famille de stockage (1 unique donnée par famille)
1831
 *
1832
 *      - _spip_raz_ : (interne) Vide la pile de mémoire et la retourne
1833
 *      - _spip_set_ : (interne) Affecte la pile de mémoire avec la donnée
1834
 * @param bool $cpt
1835
 *      True pour obtenir le nombre d'éléments différents stockés
1836
 * @return string|int|array|null|void
1837
 *
1838
 *      - string : Donnée si c'est la première fois qu'elle est vue
1839
 *      - void : si la donnée a déjà été vue
1840
 *      - int : si l'on demande le nombre d'éléments
1841
 *      - array (interne) : si on dépile
1842
 *      - null (interne) : si on empile
1843
 **/
1844
function unique($donnee, $famille = '', $cpt = false) {
1845
	static $mem = array();
1846
	// permettre de vider la pile et de la restaurer
1847
	// pour le calcul de introduction...
1848
	if ($famille == '_spip_raz_') {
1849
		$tmp = $mem;
1850
		$mem = array();
1851
1852
		return $tmp;
1853
	} elseif ($famille == '_spip_set_') {
1854
		$mem = $donnee;
1855
1856
		return;
1857
	}
1858
	// eviter une notice
1859
	if (!isset($mem[$famille])) {
1860
		$mem[$famille] = array();
1861
	}
1862
	if ($cpt) {
1863
		return count($mem[$famille]);
1864
	}
1865
	// eviter une notice
1866
	if (!isset($mem[$famille][$donnee])) {
1867
		$mem[$famille][$donnee] = 0;
1868
	}
1869
	if (!($mem[$famille][$donnee]++)) {
1870
		return $donnee;
1871
	}
1872
}
1873
1874
1875
/**
1876
 * Filtre qui alterne des valeurs en fonction d'un compteur
1877
 *
1878
 * Affiche à tour de rôle et dans l'ordre, un des arguments transmis
1879
 * à chaque incrément du compteur.
1880
 *
1881
 * S'il n'y a qu'un seul argument, et que c'est un tableau,
1882
 * l'alternance se fait sur les valeurs du tableau.
1883
 *
1884
 * Souvent appliqué à l'intérieur d'une boucle, avec le compteur `#COMPTEUR_BOUCLE`
1885
 *
1886
 * @example
1887
 *     - `[(#COMPTEUR_BOUCLE|alterner{bleu,vert,rouge})]`
1888
 *     - `[(#COMPTEUR_BOUCLE|alterner{#LISTE{bleu,vert,rouge}})]`
1889
 *
1890
 * @filtre
1891
 * @link https://www.spip.net/4145
1892
 *
1893
 * @param int $i
1894
 *     Le compteur
1895
 * @param array $args
0 ignored issues
show
Documentation introduced by
Consider making the type for parameter $args a bit more specific; maybe use array[].
Loading history...
1896
 *     Liste des éléments à alterner
1897
 * @return mixed
1898
 *     Une des valeurs en fonction du compteur.
1899
 **/
1900
function alterner($i, ...$args) {
1901
	// recuperer les arguments (attention fonctions un peu space)
1902
	$num = count($args);
1903
1904
	if ($num === 1 && is_array($args[0])) {
1905
		$args = $args[0];
1906
		$num = count($args);
1907
	}
1908
1909
	// renvoyer le i-ieme argument, modulo le nombre d'arguments
1910
	return $args[(intval($i) - 1) % $num];
1911
}
1912
1913
1914
/**
1915
 * Récupérer un attribut d'une balise HTML
1916
 *
1917
 * la regexp est mortelle : cf. `tests/unit/filtres/extraire_attribut.php`
1918
 * Si on a passé un tableau de balises, renvoyer un tableau de résultats
1919
 * (dans ce cas l'option `$complet` n'est pas disponible)
1920
 *
1921
 * @param string|array $balise
1922
 *     Texte ou liste de textes dont on veut extraire des balises
1923
 * @param string $attribut
1924
 *     Nom de l'attribut désiré
1925
 * @param bool $complet
1926
 *     True pour retourner un tableau avec
1927
 *     - le texte de la balise
1928
 *     - l'ensemble des résultats de la regexp ($r)
1929
 * @return string|array
1930
 *     - Texte de l'attribut retourné, ou tableau des texte d'attributs
1931
 *       (si 1er argument tableau)
1932
 *     - Tableau complet (si 2e argument)
1933
 **/
1934
function extraire_attribut($balise, $attribut, $complet = false) {
1935
	if (is_array($balise)) {
1936
		array_walk(
1937
			$balise,
1938
			function(&$a, $key, $t){
1939
				$a = extraire_attribut($a, $t);
1940
			},
1941
			$attribut
1942
		);
1943
1944
		return $balise;
1945
	}
1946
	if (preg_match(
1947
		',(^.*?<(?:(?>\s*)(?>[\w:.-]+)(?>(?:=(?:"[^"]*"|\'[^\']*\'|[^\'"]\S*))?))*?)(\s+'
1948
		. $attribut
1949
		. '(?:=\s*("[^"]*"|\'[^\']*\'|[^\'"]\S*))?)()((?:[\s/][^>]*)?>.*),isS',
1950
1951
		$balise, $r)) {
1952
		if (isset($r[3][0]) and ($r[3][0] == '"' || $r[3][0] == "'")) {
1953
			$r[4] = substr($r[3], 1, -1);
1954
			$r[3] = $r[3][0];
1955
		} elseif ($r[3] !== '') {
1956
			$r[4] = $r[3];
1957
			$r[3] = '';
1958
		} else {
1959
			$r[4] = trim($r[2]);
1960
		}
1961
		$att = $r[4];
1962 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...
1963
			$att = str_replace(array("&#039;", "&#39;", "&#034;", "&#34;"), array("'", "'", '"', '"'), $att);
1964
		}
1965
		$att = filtrer_entites($att);
1966
	} else {
1967
		$att = null;
1968
	}
1969
1970
	if ($complet) {
1971
		return array($att, $r);
1972
	} else {
1973
		return $att;
1974
	}
1975
}
1976
1977
/**
1978
 * Insérer (ou modifier) un attribut html dans une balise
1979
 *
1980
 * @example
1981
 *     - `[(#LOGO_ARTICLE|inserer_attribut{class, logo article})]`
1982
 *     - `[(#LOGO_ARTICLE|inserer_attribut{alt, #TTTRE|attribut_html|couper{60}})]`
1983
 *     - `[(#FICHIER|image_reduire{40}|inserer_attribut{data-description, #DESCRIPTIF})]`
1984
 *       Laissera les balises HTML de la valeur (ici `#DESCRIPTIF`) si on n'applique pas le
1985
 *       filtre `attribut_html` dessus.
1986
 *
1987
 * @filtre
1988
 * @link https://www.spip.net/4294
1989
 * @uses attribut_html()
1990
 * @uses extraire_attribut()
1991
 *
1992
 * @param string $balise
1993
 *     Code html de la balise (ou contenant une balise)
1994
 * @param string $attribut
1995
 *     Nom de l'attribut html à modifier
1996
 * @param string $val
1997
 *     Valeur de l'attribut à appliquer
1998
 * @param bool $proteger
1999
 *     Prépare la valeur en tant qu'attribut de balise (mais conserve les balises html).
2000
 * @param bool $vider
2001
 *     True pour vider l'attribut. Une chaîne vide pour `$val` fera pareil.
2002
 * @return string
2003
 *     Code html modifié
2004
 **/
2005
function inserer_attribut($balise, $attribut, $val, $proteger = true, $vider = false) {
2006
	// preparer l'attribut
2007
	// supprimer les &nbsp; etc mais pas les balises html
2008
	// qui ont un sens dans un attribut value d'un input
2009
	if ($proteger) {
2010
		$val = attribut_html($val, false);
2011
	}
2012
2013
	// echapper les ' pour eviter tout bug
2014
	$val = str_replace("'", "&#039;", $val);
2015
	if ($vider and strlen($val) == 0) {
2016
		$insert = '';
2017
	} else {
2018
		$insert = " $attribut='$val'";
2019
	}
2020
2021
	list($old, $r) = extraire_attribut($balise, $attribut, true);
2022
2023
	if ($old !== null) {
2024
		// Remplacer l'ancien attribut du meme nom
2025
		$balise = $r[1] . $insert . $r[5];
2026
	} else {
2027
		// preferer une balise " />" (comme <img />)
2028
		if (preg_match(',/>,', $balise)) {
2029
			$balise = preg_replace(",\s?/>,S", $insert . " />", $balise, 1);
2030
		} // sinon une balise <a ...> ... </a>
2031
		else {
2032
			$balise = preg_replace(",\s?>,S", $insert . ">", $balise, 1);
2033
		}
2034
	}
2035
2036
	return $balise;
2037
}
2038
2039
/**
2040
 * Supprime un attribut HTML
2041
 *
2042
 * @example `[(#LOGO_ARTICLE|vider_attribut{class})]`
2043
 *
2044
 * @filtre
2045
 * @link https://www.spip.net/4142
2046
 * @uses inserer_attribut()
2047
 * @see  extraire_attribut()
2048
 *
2049
 * @param string $balise Code HTML de l'élément
2050
 * @param string $attribut Nom de l'attribut à enlever
2051
 * @return string Code HTML sans l'attribut
2052
 **/
2053
function vider_attribut($balise, $attribut) {
2054
	return inserer_attribut($balise, $attribut, '', false, true);
2055
}
2056
2057
/**
2058
 * Fonction support pour les filtres |ajouter_class |supprimer_class |commuter_class
2059
 *
2060
 * @param string $balise
2061
 * @param string|array $class
2062
 * @param string $operation
2063
 * @return string
2064
 */
2065
function modifier_class($balise, $class, $operation='ajouter') {
2066
	if (is_string($class)) {
2067
		$class = explode(' ', trim($class));
2068
	}
2069
	$class = array_filter($class);
2070
2071
	// si la ou les classes ont des caracteres invalides on ne fait rien
2072
	if (preg_match(",[^\w-],", implode('', $class))) {
2073
		return $balise;
2074
	}
2075
2076
	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...
2077
		$class = array_unique($class);
2078
		$class_courante = extraire_attribut($balise, 'class');
2079
2080
		$class_new = $class_courante;
2081
		foreach ($class as $c) {
2082
			if ($c) {
2083
				$is_class_presente = false;
2084
				if (strpos($class_courante, $c) !== false
2085
					and preg_match("/(^|\s)".preg_quote($c)."($|\s)/", $class_courante)) {
2086
					$is_class_presente = true;
2087
				}
2088
				if (in_array($operation, ['ajouter', 'commuter'])
2089
					and !$is_class_presente) {
2090
					$class_new = rtrim($class_new) . " " . $c;
2091
				}
2092
				elseif (in_array($operation, ['supprimer', 'commuter'])
2093
					and $is_class_presente) {
2094
					$class_new = trim(preg_replace("/(^|\s)".preg_quote($c)."($|\s)/", "\\1", $class_new));
2095
				}
2096
			}
2097
		}
2098
2099
		if ($class_new !== $class_courante) {
2100
			if (strlen($class_new)) {
2101
				$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 2080 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...
2102
			}
2103
			elseif ($class_courante) {
2104
				$balise = vider_attribut($balise, 'class');
2105
			}
2106
		}
2107
	}
2108
2109
	return $balise;
2110
}
2111
2112
/**
2113
 * Ajoute une ou plusieurs classes sur une balise (si pas deja presentes)
2114
 * @param string $balise
2115
 * @param string|array $class
2116
 * @return string
2117
 */
2118
function ajouter_class($balise, $class){
2119
	return modifier_class($balise, $class, 'ajouter');
2120
}
2121
2122
/**
2123
 * Supprime une ou plusieurs classes sur une balise (si presentes)
2124
 * @param string $balise
2125
 * @param string|array $class
2126
 * @return string
2127
 */
2128
function supprimer_class($balise, $class){
2129
	return modifier_class($balise, $class, 'supprimer');
2130
}
2131
2132
/**
2133
 * Bascule une ou plusieurs classes sur une balise : ajoutees si absentes, supprimees si presentes
2134
 *
2135
 * @param string $balise
2136
 * @param string|array $class
2137
 * @return string
2138
 */
2139
function commuter_class($balise, $class){
2140
	return modifier_class($balise, $class, 'commuter');
2141
}
2142
2143
/**
2144
 * Un filtre pour déterminer le nom du statut des inscrits
2145
 *
2146
 * @param void|int $id
2147
 * @param string $mode
2148
 * @return string
2149
 */
2150
function tester_config($id, $mode = '') {
2151
	include_spip('action/inscrire_auteur');
2152
2153
	return tester_statut_inscription($mode, $id);
2154
}
2155
2156
//
2157
// Quelques fonctions de calcul arithmetique
2158
//
2159
function floatstr($a) { return str_replace(',','.',(string)floatval($a)); }
2160
function strize($f, $a, $b) { return floatstr($f(floatstr($a),floatstr($b))); }
2161
2162
/**
2163
 * Additionne 2 nombres
2164
 *
2165
 * @filtre
2166
 * @link https://www.spip.net/4307
2167
 * @see moins()
2168
 * @example
2169
 *     ```
2170
 *     [(#VAL{28}|plus{14})]
2171
 *     ```
2172
 *
2173
 * @param int $a
2174
 * @param int $b
2175
 * @return int $a+$b
2176
 **/
2177
function plus($a, $b) {
2178
	return $a + $b;
2179
}
2180
function strplus($a, $b) {return strize('plus', $a, $b);}
2181
/**
2182
 * Soustrait 2 nombres
2183
 *
2184
 * @filtre
2185
 * @link https://www.spip.net/4302
2186
 * @see plus()
2187
 * @example
2188
 *     ```
2189
 *     [(#VAL{28}|moins{14})]
2190
 *     ```
2191
 *
2192
 * @param int $a
2193
 * @param int $b
2194
 * @return int $a-$b
2195
 **/
2196
function moins($a, $b) {
2197
	return $a - $b;
2198
}
2199
function strmoins($a, $b) {return strize('moins', $a, $b);}
2200
2201
/**
2202
 * Multiplie 2 nombres
2203
 *
2204
 * @filtre
2205
 * @link https://www.spip.net/4304
2206
 * @see div()
2207
 * @see modulo()
2208
 * @example
2209
 *     ```
2210
 *     [(#VAL{28}|mult{14})]
2211
 *     ```
2212
 *
2213
 * @param int $a
2214
 * @param int $b
2215
 * @return int $a*$b
2216
 **/
2217
function mult($a, $b) {
2218
	return $a * $b;
2219
}
2220
function strmult($a, $b) {return strize('mult', $a, $b);}
2221
2222
/**
2223
 * Divise 2 nombres
2224
 *
2225
 * @filtre
2226
 * @link https://www.spip.net/4279
2227
 * @see mult()
2228
 * @see modulo()
2229
 * @example
2230
 *     ```
2231
 *     [(#VAL{28}|div{14})]
2232
 *     ```
2233
 *
2234
 * @param int $a
2235
 * @param int $b
2236
 * @return int $a/$b (ou 0 si $b est nul)
2237
 **/
2238
function div($a, $b) {
2239
	return $b ? $a / $b : 0;
2240
}
2241
function strdiv($a, $b) {return strize('div', $a, $b);}
2242
2243
/**
2244
 * Retourne le modulo 2 nombres
2245
 *
2246
 * @filtre
2247
 * @link https://www.spip.net/4301
2248
 * @see mult()
2249
 * @see div()
2250
 * @example
2251
 *     ```
2252
 *     [(#VAL{28}|modulo{14})]
2253
 *     ```
2254
 *
2255
 * @param int $nb
2256
 * @param int $mod
2257
 * @param int $add
2258
 * @return int ($nb % $mod) + $add
2259
 **/
2260
function modulo($nb, $mod, $add = 0) {
2261
	return ($mod ? $nb % $mod : 0) + $add;
2262
}
2263
2264
2265
/**
2266
 * Vérifie qu'un nom (d'auteur) ne comporte pas d'autres tags que <multi>
2267
 * et ceux volontairement spécifiés dans la constante
2268
 *
2269
 * @param string $nom
2270
 *      Nom (signature) proposé
2271
 * @return bool
2272
 *      - false si pas conforme,
2273
 *      - true sinon
2274
 **/
2275
function nom_acceptable($nom) {
2276
	if (!is_string($nom)) {
2277
		return false;
2278
	}
2279
	if (!defined('_TAGS_NOM_AUTEUR')) {
2280
		define('_TAGS_NOM_AUTEUR', '');
2281
	}
2282
	$tags_acceptes = array_unique(explode(',', 'multi,' . _TAGS_NOM_AUTEUR));
2283
	foreach ($tags_acceptes as $tag) {
2284
		if (strlen($tag)) {
2285
			$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...
2286
			$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...
2287
			$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...
2288
			$remp2[] = '\x60/' . trim($tag) . '\x61';
2289
		}
2290
	}
2291
	$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...
2292
2293
	return str_replace('&lt;', '<', $v_nom) == $nom;
2294
}
2295
2296
2297
/**
2298
 * Vérifier la conformité d'une ou plusieurs adresses email (suivant RFC 822)
2299
 *
2300
 * @param string|array $adresses
2301
 *      Adresse ou liste d'adresse
2302
 *      si on fournit un tableau, il est filtre et la fonction renvoie avec uniquement les adresses email valides (donc possiblement vide)
2303
 * @return bool|string|array
2304
 *      - false si une des adresses n'est pas conforme,
2305
 *      - la normalisation de la dernière adresse donnée sinon
2306
 *      - renvoie un tableau si l'entree est un tableau
2307
 **/
2308
function email_valide($adresses) {
2309
	if (is_array($adresses)) {
2310
		$adresses = array_map('email_valide', $adresses);
2311
		$adresses = array_filter($adresses);
2312
		return $adresses;
2313
	}
2314
2315
	$email_valide = charger_fonction('email_valide', 'inc');
2316
	return $email_valide($adresses);
2317
}
2318
2319
/**
2320
 * Permet d'afficher un symbole à côté des liens pointant vers les
2321
 * documents attachés d'un article (liens ayant `rel=enclosure`).
2322
 *
2323
 * @filtre
2324
 * @link https://www.spip.net/4134
2325
 *
2326
 * @param string $tags Texte
2327
 * @return string Texte
2328
 **/
2329
function afficher_enclosures($tags) {
2330
	$s = array();
2331
	foreach (extraire_balises($tags, 'a') as $tag) {
2332
		if (extraire_attribut($tag, 'rel') == 'enclosure'
2333
			and $t = extraire_attribut($tag, 'href')
2334
		) {
2335
			$s[] = preg_replace(',>[^<]+</a>,S',
2336
				'>'
2337
				. 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 2333 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...
2338
					'title="' . attribut_html($t) . '"')
0 ignored issues
show
Bug introduced by
It seems like $t defined by extraire_attribut($tag, 'href') on line 2333 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...
2339
				. '</a>', $tag);
2340
		}
2341
	}
2342
2343
	return join('&nbsp;', $s);
2344
}
2345
2346
/**
2347
 * Filtre des liens HTML `<a>` selon la valeur de leur attribut `rel`
2348
 * et ne retourne que ceux là.
2349
 *
2350
 * @filtre
2351
 * @link https://www.spip.net/4187
2352
 *
2353
 * @param string $tags Texte
2354
 * @param string $rels Attribut `rel` à capturer (ou plusieurs séparés par des virgules)
2355
 * @return string Liens trouvés
2356
 **/
2357
function afficher_tags($tags, $rels = 'tag,directory') {
2358
	$s = array();
2359
	foreach (extraire_balises($tags, 'a') as $tag) {
2360
		$rel = extraire_attribut($tag, 'rel');
2361
		if (strstr(",$rels,", ",$rel,")) {
2362
			$s[] = $tag;
2363
		}
2364
	}
2365
2366
	return join(', ', $s);
2367
}
2368
2369
2370
/**
2371
 * Convertir les médias fournis par un flux RSS (podcasts)
2372
 * en liens conformes aux microformats
2373
 *
2374
 * Passe un `<enclosure url="fichier" length="5588242" type="audio/mpeg"/>`
2375
 * au format microformat `<a rel="enclosure" href="fichier" ...>fichier</a>`.
2376
 *
2377
 * Peut recevoir un `<link` ou un `<media:content` parfois.
2378
 *
2379
 * Attention : `length="zz"` devient `title="zz"`, pour rester conforme.
2380
 *
2381
 * @filtre
2382
 * @see microformat2enclosure() Pour l'inverse
2383
 *
2384
 * @param string $e Tag RSS `<enclosure>`
2385
 * @return string Tag HTML `<a>` avec microformat.
2386
 **/
2387
function enclosure2microformat($e) {
2388
	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...
2389
		$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...
2390
	}
2391
	$type = extraire_attribut($e, 'type');
2392
	if (!$length = extraire_attribut($e, 'length')) {
2393
		# <media:content : longeur dans fileSize. On tente.
2394
		$length = extraire_attribut($e, 'fileSize');
2395
	}
2396
	$fichier = basename($url);
2397
2398
	return '<a rel="enclosure"'
2399
	. ($url ? ' href="' . spip_htmlspecialchars($url) . '"' : '')
2400
	. ($type ? ' type="' . spip_htmlspecialchars($type) . '"' : '')
0 ignored issues
show
Bug introduced by
It seems like $type defined by extraire_attribut($e, 'type') on line 2391 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...
2401
	. ($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...
2402
	. '>' . $fichier . '</a>';
2403
}
2404
2405
/**
2406
 * Convertir les liens conformes aux microformats en médias pour flux RSS,
2407
 * par exemple pour les podcasts
2408
 *
2409
 * Passe un texte ayant des liens avec microformat
2410
 * `<a rel="enclosure" href="fichier" ...>fichier</a>`
2411
 * au format RSS `<enclosure url="fichier" ... />`.
2412
 *
2413
 * @filtre
2414
 * @see enclosure2microformat() Pour l'inverse
2415
 *
2416
 * @param string $tags Texte HTML ayant des tag `<a>` avec microformat
2417
 * @return string Tags RSS `<enclosure>`.
2418
 **/
2419
function microformat2enclosure($tags) {
2420
	$enclosures = array();
2421
	foreach (extraire_balises($tags, 'a') as $e) {
2422
		if (extraire_attribut($e, 'rel') == 'enclosure') {
2423
			$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...
2424
			$type = extraire_attribut($e, 'type');
2425
			if (!$length = intval(extraire_attribut($e, 'title'))) {
2426
				$length = intval(extraire_attribut($e, 'length'));
2427
			} # vieux data
2428
			$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...
2429
			$enclosures[] = '<enclosure'
2430
				. ($url ? ' url="' . spip_htmlspecialchars($url) . '"' : '')
2431
				. ($type ? ' type="' . spip_htmlspecialchars($type) . '"' : '')
0 ignored issues
show
Bug introduced by
It seems like $type defined by extraire_attribut($e, 'type') on line 2424 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...
2432
				. ($length ? ' length="' . $length . '"' : '')
2433
				. ' />';
2434
		}
2435
	}
2436
2437
	return join("\n", $enclosures);
2438
}
2439
2440
2441
/**
2442
 * Créer les éléments ATOM `<dc:subject>` à partir des tags
2443
 *
2444
 * Convertit les liens avec attribut `rel="tag"`
2445
 * en balise `<dc:subject></dc:subject>` pour les flux RSS au format Atom.
2446
 *
2447
 * @filtre
2448
 *
2449
 * @param string $tags Texte
2450
 * @return string Tags RSS Atom `<dc:subject>`.
2451
 **/
2452
function tags2dcsubject($tags) {
2453
	$subjects = '';
2454
	foreach (extraire_balises($tags, 'a') as $e) {
2455
		if (extraire_attribut($e, 'rel') == 'tag') {
2456
			$subjects .= '<dc:subject>'
2457
				. texte_backend(textebrut($e))
2458
				. '</dc:subject>' . "\n";
2459
		}
2460
	}
2461
2462
	return $subjects;
2463
}
2464
2465
/**
2466
 * Retourne la premiere balise html du type demandé
2467
 *
2468
 * Retourne le contenu d'une balise jusqu'à la première fermeture rencontrée
2469
 * du même type.
2470
 * Si on a passe un tableau de textes, retourne un tableau de resultats.
2471
 *
2472
 * @example `[(#DESCRIPTIF|extraire_balise{img})]`
2473
 *
2474
 * @filtre
2475
 * @link https://www.spip.net/4289
2476
 * @see extraire_balises()
2477
 * @note
2478
 *     Attention : les résultats peuvent être incohérents sur des balises imbricables,
2479
 *     tel que demander à extraire `div` dans le texte `<div> un <div> mot </div> absent </div>`,
2480
 *     ce qui retournerait `<div> un <div> mot </div>` donc.
2481
 *
2482
 * @param string|array $texte
2483
 *     Texte(s) dont on souhaite extraire une balise html
2484
 * @param string $tag
2485
 *     Nom de la balise html à extraire
2486
 * @return void|string|array
2487
 *     - Code html de la balise, sinon rien
2488
 *     - Tableau de résultats, si tableau en entrée.
2489
 **/
2490 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...
2491
	if (is_array($texte)) {
2492
		array_walk(
2493
			$texte,
2494
			function(&$a, $key, $t){
2495
				$a = extraire_balise($a, $t);
2496
			},
2497
			$tag
2498
		);
2499
2500
		return $texte;
2501
	}
2502
2503
	if (preg_match(
2504
		",<$tag\b[^>]*(/>|>.*</$tag\b[^>]*>|>),UimsS",
2505
		$texte, $regs)) {
2506
		return $regs[0];
2507
	}
2508
}
2509
2510
/**
2511
 * Extrait toutes les balises html du type demandé
2512
 *
2513
 * Retourne dans un tableau le contenu de chaque balise jusqu'à la première
2514
 * fermeture rencontrée du même type.
2515
 * Si on a passe un tableau de textes, retourne un tableau de resultats.
2516
 *
2517
 * @example `[(#TEXTE|extraire_balises{img}|implode{" - "})]`
2518
 *
2519
 * @filtre
2520
 * @link https://www.spip.net/5618
2521
 * @see extraire_balise()
2522
 * @note
2523
 *     Attention : les résultats peuvent être incohérents sur des balises imbricables,
2524
 *     tel que demander à extraire `div` dans un texte.
2525
 *
2526
 * @param string|array $texte
2527
 *     Texte(s) dont on souhaite extraire une balise html
2528
 * @param string $tag
2529
 *     Nom de la balise html à extraire
2530
 * @return array
2531
 *     - Liste des codes html des occurrences de la balise, sinon tableau vide
2532
 *     - Tableau de résultats, si tableau en entrée.
2533
 **/
2534 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...
2535
	if (is_array($texte)) {
2536
		array_walk(
2537
			$texte,
2538
			function(&$a, $key, $t){
2539
				$a = extraire_balises($a, $t);
2540
			},
2541
			$tag
2542
		);
2543
2544
		return $texte;
2545
	}
2546
2547
	if (preg_match_all(
2548
		",<${tag}\b[^>]*(/>|>.*</${tag}\b[^>]*>|>),UimsS",
2549
		$texte, $regs, PREG_PATTERN_ORDER)) {
2550
		return $regs[0];
2551
	} else {
2552
		return array();
2553
	}
2554
}
2555
2556
/**
2557
 * Indique si le premier argument est contenu dans le second
2558
 *
2559
 * Cette fonction est proche de `in_array()` en PHP avec comme principale
2560
 * différence qu'elle ne crée pas d'erreur si le second argument n'est pas
2561
 * un tableau (dans ce cas elle tentera de le désérialiser, et sinon retournera
2562
 * la valeur par défaut transmise).
2563
 *
2564
 * @example `[(#VAL{deux}|in_any{#LISTE{un,deux,trois}}|oui) ... ]`
2565
 *
2566
 * @filtre
2567
 * @see filtre_find() Assez proche, avec les arguments valeur et tableau inversés.
2568
 *
2569
 * @param string $val
2570
 *     Valeur à chercher dans le tableau
2571
 * @param array|string $vals
2572
 *     Tableau des valeurs. S'il ce n'est pas un tableau qui est transmis,
2573
 *     la fonction tente de la désérialiser.
2574
 * @param string $def
2575
 *     Valeur par défaut retournée si `$vals` n'est pas un tableau.
2576
 * @return string
2577
 *     - ' ' si la valeur cherchée est dans le tableau
2578
 *     - '' si la valeur n'est pas dans le tableau
2579
 *     - `$def` si on n'a pas transmis de tableau
2580
 **/
2581
function in_any($val, $vals, $def = '') {
2582
	if (!is_array($vals) and $v = unserialize($vals)) {
2583
		$vals = $v;
2584
	}
2585
2586
	return (!is_array($vals) ? $def : (in_array($val, $vals) ? ' ' : ''));
2587
}
2588
2589
2590
/**
2591
 * Retourne le résultat d'une expression mathématique simple
2592
 *
2593
 * N'accepte que les *, + et - (à ameliorer si on l'utilise vraiment).
2594
 *
2595
 * @filtre
2596
 * @example
2597
 *      ```
2598
 *      valeur_numerique("3*2") retourne 6
2599
 *      ```
2600
 *
2601
 * @param string $expr
2602
 *     Expression mathématique `nombre operateur nombre` comme `3*2`
2603
 * @return int
2604
 *     Résultat du calcul
2605
 **/
2606
function valeur_numerique($expr) {
2607
	$a = 0;
2608
	if (preg_match(',^[0-9]+(\s*[+*-]\s*[0-9]+)*$,S', trim($expr))) {
2609
		eval("\$a = $expr;");
2610
	}
2611
2612
	return intval($a);
2613
}
2614
2615
/**
2616
 * Retourne un calcul de règle de trois
2617
 *
2618
 * @filtre
2619
 * @example
2620
 *     ```
2621
 *     [(#VAL{6}|regledetrois{4,3})] retourne 8
2622
 *     ```
2623
 *
2624
 * @param int $a
2625
 * @param int $b
2626
 * @param int $c
2627
 * @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...
2628
 *      Retourne `$a*$b/$c`
2629
 **/
2630
function regledetrois($a, $b, $c) {
2631
	return round($a * $b / $c);
2632
}
2633
2634
2635
/**
2636
 * Crée des tags HTML input hidden pour chaque paramètre et valeur d'une URL
2637
 *
2638
 * Fournit la suite de Input-Hidden correspondant aux paramètres de
2639
 * l'URL donnée en argument, compatible avec les types_urls
2640
 *
2641
 * @filtre
2642
 * @link https://www.spip.net/4286
2643
 * @see balise_ACTION_FORMULAIRE()
2644
 *     Également pour transmettre les actions à un formulaire
2645
 * @example
2646
 *     ```
2647
 *     [(#ENV{action}|form_hidden)] dans un formulaire
2648
 *     ```
2649
 *
2650
 * @param string $action URL
2651
 * @return string Suite de champs input hidden
2652
 **/
2653
function form_hidden($action) {
2654
2655
	$contexte = array();
2656
	include_spip('inc/urls');
2657
	if ($p = urls_decoder_url($action, '')
2658
		and reset($p)
2659
	) {
2660
		$fond = array_shift($p);
2661
		if ($fond != '404') {
2662
			$contexte = array_shift($p);
2663
			$contexte['page'] = $fond;
2664
			$action = preg_replace('/([?]' . preg_quote($fond) . '[^&=]*[0-9]+)(&|$)/', '?&', $action);
2665
		}
2666
	}
2667
	// defaire ce qu'a injecte urls_decoder_url : a revoir en modifiant la signature de urls_decoder_url
2668
	if (defined('_DEFINIR_CONTEXTE_TYPE') and _DEFINIR_CONTEXTE_TYPE) {
2669
		unset($contexte['type']);
2670
	}
2671
	if (defined('_DEFINIR_CONTEXTE_TYPE_PAGE') and _DEFINIR_CONTEXTE_TYPE_PAGE) {
2672
		unset($contexte['type-page']);
2673
	}
2674
2675
	// on va remplir un tableau de valeurs en prenant bien soin de ne pas
2676
	// ecraser les elements de la forme mots[]=1&mots[]=2
2677
	$values = array();
2678
2679
	// d'abord avec celles de l'url
2680
	if (false !== ($p = strpos($action, '?'))) {
2681
		foreach (preg_split('/&(amp;)?/S', substr($action, $p + 1)) as $c) {
2682
			$c = explode('=', $c, 2);
2683
			$var = array_shift($c);
2684
			$val = array_shift($c);
2685
			if ($var) {
2686
				$val = rawurldecode($val);
2687
				$var = rawurldecode($var); // decoder les [] eventuels
2688 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...
2689
					$values[] = array($var, $val);
2690
				} else {
2691
					if (!isset($values[$var])) {
2692
						$values[$var] = array($var, $val);
2693
					}
2694
				}
2695
			}
2696
		}
2697
	}
2698
2699
	// ensuite avec celles du contexte, sans doublonner !
2700
	foreach ($contexte as $var => $val) {
2701 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...
2702
			$values[] = array($var, $val);
2703
		} else {
2704
			if (!isset($values[$var])) {
2705
				$values[$var] = array($var, $val);
2706
			}
2707
		}
2708
	}
2709
2710
	// puis on rassemble le tout
2711
	$hidden = array();
2712
	foreach ($values as $value) {
2713
		list($var, $val) = $value;
2714
		$hidden[] = '<input name="'
2715
			. entites_html($var)
2716
			. '"'
2717
			. (is_null($val)
2718
				? ''
2719
				: ' value="' . entites_html($val) . '"'
2720
			)
2721
			. ' type="hidden"' . "\n/>";
2722
	}
2723
2724
	return join("", $hidden);
2725
}
2726
2727
2728
/**
2729
 * Retourne la première valeur d'un tableau
2730
 *
2731
 * Plus précisément déplace le pointeur du tableau sur la première valeur et la retourne.
2732
 *
2733
 * @example `[(#LISTE{un,deux,trois}|reset)]` retourne 'un'
2734
 *
2735
 * @filtre
2736
 * @link http://php.net/manual/fr/function.reset.php
2737
 * @see filtre_end()
2738
 *
2739
 * @param array $array
2740
 * @return mixed|null|false
2741
 *    - null si $array n'est pas un tableau,
2742
 *    - false si le tableau est vide
2743
 *    - la première valeur du tableau sinon.
2744
 **/
2745
function filtre_reset($array) {
2746
	return !is_array($array) ? null : reset($array);
2747
}
2748
2749
/**
2750
 * Retourne la dernière valeur d'un tableau
2751
 *
2752
 * Plus précisément déplace le pointeur du tableau sur la dernière valeur et la retourne.
2753
 *
2754
 * @example `[(#LISTE{un,deux,trois}|end)]` retourne 'trois'
2755
 *
2756
 * @filtre
2757
 * @link http://php.net/manual/fr/function.end.php
2758
 * @see filtre_reset()
2759
 *
2760
 * @param array $array
2761
 * @return mixed|null|false
2762
 *    - null si $array n'est pas un tableau,
2763
 *    - false si le tableau est vide
2764
 *    - la dernière valeur du tableau sinon.
2765
 **/
2766
function filtre_end($array) {
2767
	return !is_array($array) ? null : end($array);
2768
}
2769
2770
/**
2771
 * Empile une valeur à la fin d'un tableau
2772
 *
2773
 * @example `[(#LISTE{un,deux,trois}|push{quatre}|print)]`
2774
 *
2775
 * @filtre
2776
 * @link https://www.spip.net/4571
2777
 * @link http://php.net/manual/fr/function.array-push.php
2778
 *
2779
 * @param array $array
2780
 * @param mixed $val
2781
 * @return array|string
2782
 *     - '' si $array n'est pas un tableau ou si echec.
2783
 *     - le tableau complété de la valeur sinon.
2784
 *
2785
 **/
2786
function filtre_push($array, $val) {
2787
	if (!is_array($array) or !array_push($array, $val)) {
2788
		return '';
2789
	}
2790
2791
	return $array;
2792
}
2793
2794
/**
2795
 * Indique si une valeur est contenue dans un tableau
2796
 *
2797
 * @example `[(#LISTE{un,deux,trois}|find{quatre}|oui) ... ]`
2798
 *
2799
 * @filtre
2800
 * @link https://www.spip.net/4575
2801
 * @see in_any() Assez proche, avec les paramètres tableau et valeur inversés.
2802
 *
2803
 * @param array $array
2804
 * @param mixed $val
2805
 * @return bool
2806
 *     - `false` si `$array` n'est pas un tableau
2807
 *     - `true` si la valeur existe dans le tableau, `false` sinon.
2808
 **/
2809
function filtre_find($array, $val) {
2810
	return (is_array($array) and in_array($val, $array));
2811
}
2812
2813
2814
/**
2815
 * Passer les url relatives à la css d'origine en url absolues
2816
 *
2817
 * @uses suivre_lien()
2818
 *
2819
 * @param string $contenu
2820
 *     Contenu du fichier CSS
2821
 * @param string $source
2822
 *     Chemin du fichier CSS
2823
 * @return string
2824
 *     Contenu avec urls en absolus
2825
 **/
2826
function urls_absolues_css($contenu, $source) {
2827
	$path = suivre_lien(url_absolue($source), './');
2828
2829
	return preg_replace_callback(
2830
		",url\s*\(\s*['\"]?([^'\"/#\s][^:]*)['\"]?\s*\),Uims",
2831
		function($x) use ($path) {
2832
			return "url('" . suivre_lien($path, $x[1]) . "')";
2833
		},
2834
		$contenu
2835
	);
2836
}
2837
2838
2839
/**
2840
 * Inverse le code CSS (left <--> right) d'une feuille de style CSS
2841
 *
2842
 * Récupère le chemin d'une CSS existante et :
2843
 *
2844
 * 1. regarde si une CSS inversée droite-gauche existe dans le meme répertoire
2845
 * 2. sinon la crée (ou la recrée) dans `_DIR_VAR/cache_css/`
2846
 *
2847
 * Si on lui donne à manger une feuille nommée `*_rtl.css` il va faire l'inverse.
2848
 *
2849
 * @filtre
2850
 * @example
2851
 *     ```
2852
 *     [<link rel="stylesheet" href="(#CHEMIN{css/perso.css}|direction_css)" type="text/css" />]
2853
 *     ```
2854
 * @param string $css
2855
 *     Chemin vers le fichier CSS
2856
 * @param string $voulue
2857
 *     Permet de forcer le sens voulu (en indiquant `ltr`, `rtl` ou un
2858
 *     code de langue). En absence, prend le sens de la langue en cours.
2859
 *
2860
 * @return string
2861
 *     Chemin du fichier CSS inversé
2862
 **/
2863
function direction_css($css, $voulue = '') {
2864
	if (!preg_match(',(_rtl)?\.css$,i', $css, $r)) {
2865
		return $css;
2866
	}
2867
	include_spip("inc/lang");
2868
	// si on a precise le sens voulu en argument, le prendre en compte
2869
	if ($voulue = strtolower($voulue)) {
2870
		if ($voulue != 'rtl' and $voulue != 'ltr') {
2871
			$voulue = lang_dir($voulue);
2872
		}
2873
	} else {
2874
		$voulue = lang_dir();
2875
	}
2876
2877
	$r = count($r) > 1;
2878
	$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...
2879
	$dir = $r ? 'rtl' : 'ltr';
2880
	$ndir = $r ? 'ltr' : 'rtl';
2881
2882
	if ($voulue == $dir) {
2883
		return $css;
2884
	}
2885
2886
	if (
2887
		// url absolue
2888
		preg_match(",^https?:,i", $css)
2889
		// ou qui contient un ?
2890
		or (($p = strpos($css, '?')) !== false)
2891
	) {
2892
		$distant = true;
2893
		$cssf = parse_url($css);
2894
		$cssf = $cssf['path'] . ($cssf['query'] ? "?" . $cssf['query'] : "");
2895
		$cssf = preg_replace(',[?:&=],', "_", $cssf);
2896
	} else {
2897
		$distant = false;
2898
		$cssf = $css;
2899
		// 1. regarder d'abord si un fichier avec la bonne direction n'est pas aussi
2900
		//propose (rien a faire dans ce cas)
2901
		$f = preg_replace(',(_rtl)?\.css$,i', '_' . $ndir . '.css', $css);
2902
		if (@file_exists($f)) {
2903
			return $f;
2904
		}
2905
	}
2906
2907
	// 2.
2908
	$dir_var = sous_repertoire(_DIR_VAR, 'cache-css');
2909
	$f = $dir_var
2910
		. preg_replace(',.*/(.*?)(_rtl)?\.css,', '\1', $cssf)
2911
		. '.' . substr(md5($cssf), 0, 4) . '_' . $ndir . '.css';
2912
2913
	// la css peut etre distante (url absolue !)
2914
	if ($distant) {
2915
		include_spip('inc/distant');
2916
		$res = recuperer_url($css);
2917
		if (!$res or !$contenu = $res['page']) {
2918
			return $css;
2919
		}
2920
	} else {
2921 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...
2922
			and (_VAR_MODE != 'recalcul')
2923
		) {
2924
			return $f;
2925
		}
2926
		if (!lire_fichier($css, $contenu)) {
2927
			return $css;
2928
		}
2929
	}
2930
2931
2932
	// Inverser la direction gauche-droite en utilisant CSSTidy qui gere aussi les shorthands
2933
	include_spip("lib/csstidy/class.csstidy");
2934
	$parser = new csstidy();
2935
	$parser->set_cfg('optimise_shorthands', 0);
2936
	$parser->set_cfg('reverse_left_and_right', true);
2937
	$parser->parse($contenu);
2938
2939
	$contenu = $parser->print->plain();
2940
2941
2942
	// reperer les @import auxquels il faut propager le direction_css
2943
	preg_match_all(",\@import\s*url\s*\(\s*['\"]?([^'\"/][^:]*)['\"]?\s*\),Uims", $contenu, $regs);
2944
	$src = array();
2945
	$src_direction_css = array();
2946
	$src_faux_abs = array();
2947
	$d = dirname($css);
2948
	foreach ($regs[1] as $k => $import_css) {
2949
		$css_direction = direction_css("$d/$import_css", $voulue);
2950
		// si la css_direction est dans le meme path que la css d'origine, on tronque le path, elle sera passee en absolue
2951
		if (substr($css_direction, 0, strlen($d) + 1) == "$d/") {
2952
			$css_direction = substr($css_direction, strlen($d) + 1);
2953
		} // si la css_direction commence par $dir_var on la fait passer pour une absolue
2954
		elseif (substr($css_direction, 0, strlen($dir_var)) == $dir_var) {
2955
			$css_direction = substr($css_direction, strlen($dir_var));
2956
			$src_faux_abs["/@@@@@@/" . $css_direction] = $css_direction;
2957
			$css_direction = "/@@@@@@/" . $css_direction;
2958
		}
2959
		$src[] = $regs[0][$k];
2960
		$src_direction_css[] = str_replace($import_css, $css_direction, $regs[0][$k]);
2961
	}
2962
	$contenu = str_replace($src, $src_direction_css, $contenu);
2963
2964
	$contenu = urls_absolues_css($contenu, $css);
2965
2966
	// virer les fausses url absolues que l'on a mis dans les import
2967
	if (count($src_faux_abs)) {
2968
		$contenu = str_replace(array_keys($src_faux_abs), $src_faux_abs, $contenu);
2969
	}
2970
2971
	if (!ecrire_fichier($f, $contenu)) {
2972
		return $css;
2973
	}
2974
2975
	return $f;
2976
}
2977
2978
2979
/**
2980
 * Transforme les urls relatives d'un fichier CSS en absolues
2981
 *
2982
 * Récupère le chemin d'une css existante et crée (ou recrée) dans `_DIR_VAR/cache_css/`
2983
 * une css dont les url relatives sont passées en url absolues
2984
 *
2985
 * Le calcul n'est pas refait si le fichier cache existe déjà et que
2986
 * la source n'a pas été modifiée depuis.
2987
 *
2988
 * @uses recuperer_page() si l'URL source n'est pas sur le même site
2989
 * @uses urls_absolues_css()
2990
 *
2991
 * @param string $css
2992
 *     Chemin ou URL du fichier CSS source
2993
 * @return string
2994
 *     - Chemin du fichier CSS transformé (si source lisible et mise en cache réussie)
2995
 *     - Chemin ou URL du fichier CSS source sinon.
2996
 **/
2997
function url_absolue_css($css) {
2998
	if (!preg_match(',\.css$,i', $css, $r)) {
2999
		return $css;
3000
	}
3001
3002
	$url_absolue_css = url_absolue($css);
3003
3004
	$f = basename($css, '.css');
3005
	$f = sous_repertoire(_DIR_VAR, 'cache-css')
3006
		. preg_replace(",(.*?)(_rtl|_ltr)?$,", "\\1-urlabs-" . substr(md5("$css-urlabs"), 0, 4) . "\\2", $f)
3007
		. '.css';
3008
3009 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...
3010
		return $f;
3011
	}
3012
3013
	if ($url_absolue_css == $css) {
3014
		if (strncmp($GLOBALS['meta']['adresse_site'], $css, $l = strlen($GLOBALS['meta']['adresse_site'])) != 0
3015
			or !lire_fichier(_DIR_RACINE . substr($css, $l), $contenu)
3016
		) {
3017
			include_spip('inc/distant');
3018
			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...
3019
				return $css;
3020
			}
3021
		}
3022
	} elseif (!lire_fichier($css, $contenu)) {
3023
		return $css;
3024
	}
3025
3026
	// passer les url relatives a la css d'origine en url absolues
3027
	$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...
3028
3029
	// ecrire la css
3030
	if (!ecrire_fichier($f, $contenu)) {
3031
		return $css;
3032
	}
3033
3034
	return $f;
3035
}
3036
3037
3038
/**
3039
 * Récupère la valeur d'une clé donnée
3040
 * dans un tableau (ou un objet).
3041
 *
3042
 * @filtre
3043
 * @link https://www.spip.net/4572
3044
 * @example
3045
 *     ```
3046
 *     [(#VALEUR|table_valeur{cle/sous/element})]
3047
 *     ```
3048
 *
3049
 * @param mixed $table
3050
 *     Tableau ou objet PHP
3051
 *     (ou chaîne serialisée de tableau, ce qui permet d'enchaîner le filtre)
3052
 * @param string $cle
3053
 *     Clé du tableau (ou paramètre public de l'objet)
3054
 *     Cette clé peut contenir des caractères / pour sélectionner
3055
 *     des sous éléments dans le tableau, tel que `sous/element/ici`
3056
 *     pour obtenir la valeur de `$tableau['sous']['element']['ici']`
3057
 * @param mixed $defaut
3058
 *     Valeur par defaut retournée si la clé demandée n'existe pas
3059
 * @param bool  $conserver_null
3060
 *     Permet de forcer la fonction à renvoyer la valeur null d'un index
3061
 *     et non pas $defaut comme cela est fait naturellement par la fonction
3062
 *     isset. On utilise alors array_key_exists() à la place de isset().
3063
 * 
3064
 * @return mixed
3065
 *     Valeur trouvée ou valeur par défaut.
3066
 **/
3067
function table_valeur($table, $cle, $defaut = '', $conserver_null = false) {
3068
	foreach (explode('/', $cle) as $k) {
3069
3070
		$table = is_string($table) ? @unserialize($table) : $table;
3071
3072
		if (is_object($table)) {
3073
			$table = (($k !== "") and isset($table->$k)) ? $table->$k : $defaut;
3074
		} elseif (is_array($table)) {
3075
			if ($conserver_null) {
3076
				$table = array_key_exists($k, $table) ? $table[$k] : $defaut;
3077
			} else {
3078
				$table = isset($table[$k]) ? $table[$k] : $defaut;
3079
			}
3080
		} else {
3081
			$table = $defaut;
3082
		}
3083
	}
3084
3085
	return $table;
3086
}
3087
3088
/**
3089
 * Retrouve un motif dans un texte à partir d'une expression régulière
3090
 *
3091
 * S'appuie sur la fonction `preg_match()` en PHP
3092
 *
3093
 * @example
3094
 *    - `[(#TITRE|match{toto})]`
3095
 *    - `[(#TEXTE|match{^ceci$,Uims})]`
3096
 *    - `[(#TEXTE|match{truc(...)$, UimsS, 1})]` Capture de la parenthèse indiquée
3097
 *    - `[(#TEXTE|match{truc(...)$, 1})]` Équivalent, sans indiquer les modificateurs
3098
 *
3099
 * @filtre
3100
 * @link https://www.spip.net/4299
3101
 * @link http://php.net/manual/fr/function.preg-match.php Pour des infos sur `preg_match()`
3102
 *
3103
 * @param string $texte
3104
 *     Texte dans lequel chercher
3105
 * @param string|int $expression
3106
 *     Expression régulière de recherche, sans le délimiteur
3107
 * @param string $modif
3108
 *     - string : Modificateurs de l'expression régulière
3109
 *     - int : Numéro de parenthèse capturante
3110
 * @param int $capte
3111
 *     Numéro de parenthèse capturante
3112
 * @return bool|string
3113
 *     - false : l'expression n'a pas été trouvée
3114
 *     - true : expression trouvée, mais pas la parenthèse capturante
3115
 *     - string : expression trouvée.
3116
 **/
3117
function filtre_match_dist($texte, $expression, $modif = "UimsS", $capte = 0) {
3118
	if (intval($modif) and $capte == 0) {
3119
		$capte = $modif;
3120
		$modif = "UimsS";
3121
	}
3122
	$expression = str_replace("\/", "/", $expression);
3123
	$expression = str_replace("/", "\/", $expression);
3124
3125
	if (preg_match('/' . $expression . '/' . $modif, $texte, $r)) {
3126
		if (isset($r[$capte])) {
3127
			return $r[$capte];
3128
		} else {
3129
			return true;
3130
		}
3131
	}
3132
3133
	return false;
3134
}
3135
3136
3137
/**
3138
 * Remplacement de texte à base d'expression régulière
3139
 *
3140
 * @filtre
3141
 * @link https://www.spip.net/4309
3142
 * @see match()
3143
 * @example
3144
 *     ```
3145
 *     [(#TEXTE|replace{^ceci$,cela,UimsS})]
3146
 *     ```
3147
 *
3148
 * @param string $texte
3149
 *     Texte
3150
 * @param string $expression
3151
 *     Expression régulière
3152
 * @param string $replace
3153
 *     Texte de substitution des éléments trouvés
3154
 * @param string $modif
3155
 *     Modificateurs pour l'expression régulière.
3156
 * @return string
3157
 *     Texte
3158
 **/
3159
function replace($texte, $expression, $replace = '', $modif = "UimsS") {
3160
	$expression = str_replace("\/", "/", $expression);
3161
	$expression = str_replace("/", "\/", $expression);
3162
3163
	return preg_replace('/' . $expression . '/' . $modif, $replace, $texte);
3164
}
3165
3166
3167
/**
3168
 * Cherche les documents numerotés dans un texte traite par `propre()`
3169
 *
3170
 * Affecte la liste des doublons['documents']
3171
 *
3172
 * @param array $doublons
3173
 *     Liste des doublons
3174
 * @param string $letexte
3175
 *     Le texte
3176
 * @return string
3177
 *     Le texte
3178
 **/
3179
function traiter_doublons_documents(&$doublons, $letexte) {
3180
3181
	// Verifier dans le texte & les notes (pas beau, helas)
3182
	$t = $letexte . $GLOBALS['les_notes'];
3183
3184
	if (strstr($t, 'spip_document_') // evite le preg_match_all si inutile
3185
		and preg_match_all(
3186
			',<[^>]+\sclass=["\']spip_document_([0-9]+)[\s"\'],imsS',
3187
			$t, $matches, PREG_PATTERN_ORDER)
3188
	) {
3189
		if (!isset($doublons['documents'])) {
3190
			$doublons['documents'] = "";
3191
		}
3192
		$doublons['documents'] .= "," . join(',', $matches[1]);
3193
	}
3194
3195
	return $letexte;
3196
}
3197
3198
/**
3199
 * Filtre vide qui ne renvoie rien
3200
 *
3201
 * @example
3202
 *     `[(#CALCUL|vide)]` n'affichera pas le résultat du calcul
3203
 * @filtre
3204
 *
3205
 * @param mixed $texte
3206
 * @return string Chaîne vide
3207
 **/
3208
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...
3209
	return "";
3210
}
3211
3212
//
3213
// Filtres pour le modele/emb (embed document)
3214
//
3215
3216
/**
3217
 * Écrit des balises HTML `<param...>` à partir d'un tableau de données tel que `#ENV`
3218
 *
3219
 * Permet d'écrire les balises `<param>` à indiquer dans un `<object>`
3220
 * en prenant toutes les valeurs du tableau transmis.
3221
 *
3222
 * Certaines clés spécifiques à SPIP et aux modèles embed sont omises :
3223
 * id, lang, id_document, date, date_redac, align, fond, recurs, emb, dir_racine
3224
 *
3225
 * @example `[(#ENV*|env_to_params)]`
3226
 *
3227
 * @filtre
3228
 * @link https://www.spip.net/4005
3229
 *
3230
 * @param array|string $env
3231
 *      Tableau cle => valeur des paramètres à écrire, ou chaine sérialisée de ce tableau
3232
 * @param array $ignore_params
3233
 *      Permet de compléter les clés ignorées du tableau.
3234
 * @return string
3235
 *      Code HTML résultant
3236
 **/
3237 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...
3238
	$ignore_params = array_merge(
3239
		array('id', 'lang', 'id_document', 'date', 'date_redac', 'align', 'fond', '', 'recurs', 'emb', 'dir_racine'),
3240
		$ignore_params
3241
	);
3242
	if (!is_array($env)) {
3243
		$env = unserialize($env);
3244
	}
3245
	$texte = "";
3246
	if ($env) {
3247
		foreach ($env as $i => $j) {
3248
			if (is_string($j) and !in_array($i, $ignore_params)) {
3249
				$texte .= "<param name='" . attribut_html($i) . "'\n\tvalue='" . attribut_html($j) . "' />";
3250
			}
3251
		}
3252
	}
3253
3254
	return $texte;
3255
}
3256
3257
/**
3258
 * Écrit des attributs HTML à partir d'un tableau de données tel que `#ENV`
3259
 *
3260
 * Permet d'écrire des attributs d'une balise HTML en utilisant les données du tableau transmis.
3261
 * Chaque clé deviendra le nom de l'attribut (et la valeur, sa valeur)
3262
 *
3263
 * Certaines clés spécifiques à SPIP et aux modèles embed sont omises :
3264
 * id, lang, id_document, date, date_redac, align, fond, recurs, emb, dir_racine
3265
 *
3266
 * @example `<embed src='#URL_DOCUMENT' [(#ENV*|env_to_attributs)] width='#GET{largeur}' height='#GET{hauteur}'></embed>`
3267
 * @filtre
3268
 *
3269
 * @param array|string $env
3270
 *      Tableau cle => valeur des attributs à écrire, ou chaine sérialisée de ce tableau
3271
 * @param array $ignore_params
3272
 *      Permet de compléter les clés ignorées du tableau.
3273
 * @return string
3274
 *      Code HTML résultant
3275
 **/
3276 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...
3277
	$ignore_params = array_merge(
3278
		array('id', 'lang', 'id_document', 'date', 'date_redac', 'align', 'fond', '', 'recurs', 'emb', 'dir_racine'),
3279
		$ignore_params
3280
	);
3281
	if (!is_array($env)) {
3282
		$env = unserialize($env);
3283
	}
3284
	$texte = "";
3285
	if ($env) {
3286
		foreach ($env as $i => $j) {
3287
			if (is_string($j) and !in_array($i, $ignore_params)) {
3288
				$texte .= attribut_html($i) . "='" . attribut_html($j) . "' ";
3289
			}
3290
		}
3291
	}
3292
3293
	return $texte;
3294
}
3295
3296
3297
/**
3298
 * Concatène des chaînes
3299
 *
3300
 * @filtre
3301
 * @link https://www.spip.net/4150
3302
 * @example
3303
 *     ```
3304
 *     #TEXTE|concat{texte1,texte2,...}
3305
 *     ```
3306
 *
3307
 * @param array $args
0 ignored issues
show
Documentation introduced by
Consider making the type for parameter $args a bit more specific; maybe use array[].
Loading history...
3308
 * @return string Chaînes concaténés
3309
 **/
3310
function concat(...$args) : string {
3311
	return join('', $args);
3312
}
3313
3314
3315
/**
3316
 * Retourne le contenu d'un ou plusieurs fichiers
3317
 *
3318
 * Les chemins sont cherchés dans le path de SPIP
3319
 *
3320
 * @see balise_INCLURE_dist() La balise `#INCLURE` peut appeler cette fonction
3321
 *
3322
 * @param array|string $files
3323
 *     - array : Liste de fichiers
3324
 *     - string : fichier ou fichiers séparés par `|`
3325
 * @param bool $script
3326
 *     - si true, considère que c'est un fichier js à chercher `javascript/`
3327
 * @return string
3328
 *     Contenu du ou des fichiers, concaténé
3329
 **/
3330
function charge_scripts($files, $script = true) {
3331
	$flux = "";
3332
	foreach (is_array($files) ? $files : explode("|", $files) as $file) {
3333
		if (!is_string($file)) {
3334
			continue;
3335
		}
3336
		if ($script) {
3337
			$file = preg_match(",^\w+$,", $file) ? "javascript/$file.js" : '';
3338
		}
3339
		if ($file) {
3340
			$path = find_in_path($file);
3341
			if ($path) {
3342
				$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 3340 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...
3343
			}
3344
		}
3345
	}
3346
3347
	return $flux;
3348
}
3349
3350
3351
/**
3352
 * Produit une balise img avec un champ alt d'office si vide
3353
 *
3354
 * Attention le htmlentities et la traduction doivent être appliqués avant.
3355
 *
3356
 * @param string $img
3357
 * @param string $alt
3358
 * @param string $atts
3359
 * @param string $title
3360
 * @param array $options
3361
 *   chemin_image : utiliser chemin_image sur $img fourni, ou non (oui par dafaut)
3362
 *   utiliser_suffixe_size : utiliser ou non le suffixe de taille dans le nom de fichier de l'image
3363
 *   sous forme -xx.png (pour les icones essentiellement) (oui par defaut)
3364
 *   variante_svg_si_possible: utiliser l'image -xx.svg au lieu de -32.png par exemple (si la variante svg est disponible)
3365
 * @return string
3366
 */
3367
function http_img_pack($img, $alt, $atts = '', $title = '', $options = array()) {
3368
3369
	$img_file = $img;
3370
	if ($p = strpos($img_file, '?')) {
3371
		$img_file = substr($img_file,0, $p);
3372
	}
3373
	if (!isset($options['chemin_image']) or $options['chemin_image'] == true) {
3374
		$img_file = chemin_image($img);
3375
	}
3376
	else {
3377
		if (!isset($options['variante_svg_si_possible']) or $options['variante_svg_si_possible'] == true){
3378
			// on peut fournir une icone generique -xx.svg qui fera le job dans toutes les tailles, et qui est prioritaire sur le png
3379
			// si il y a un .svg a la bonne taille (-16.svg) a cote, on l'utilise en remplacement du -16.png
3380
			if (preg_match(',-(\d+)[.](png|gif|svg)$,', $img_file, $m)
3381
			  and $variante_svg_generique = substr($img_file, 0, -strlen($m[0])) . "-xx.svg"
3382
			  and file_exists($variante_svg_generique)) {
3383
				if ($variante_svg_size = substr($variante_svg_generique,0,-6) . $m[1] . ".svg" and file_exists($variante_svg_size)) {
3384
					$img_file = $variante_svg_size;
3385
				}
3386
				else {
3387
					$img_file = $variante_svg_generique;
3388
				}
3389
			}
3390
		}
3391
	}
3392
	if (stripos($atts, 'width') === false) {
3393
		// utiliser directement l'info de taille presente dans le nom
3394
		if ((!isset($options['utiliser_suffixe_size'])
3395
				or $options['utiliser_suffixe_size'] == true
3396
			  or strpos($img_file, '-xx.svg') !== false)
3397
			and (preg_match(',-([0-9]+)[.](png|gif|svg)$,', $img, $regs)
3398
					 or preg_match(',\?([0-9]+)px$,', $img, $regs))
3399
		) {
3400
			$largeur = $hauteur = intval($regs[1]);
3401
		} else {
3402
			$taille = taille_image($img_file);
3403
			list($hauteur, $largeur) = $taille;
3404
			if (!$hauteur or !$largeur) {
3405
				return "";
3406
			}
3407
		}
3408
		$atts .= " width='" . $largeur . "' height='" . $hauteur . "'";
3409
	}
3410
3411
	if (file_exists($img_file)) {
3412
		$img_file = timestamp($img_file);
3413
	}
3414
	if ($alt === false) {
3415
		$alt = '';
3416
	}
3417
	elseif($alt or $alt==='') {
3418
		$alt = " alt='".attribut_html($alt)."'";
3419
	}
3420
	else {
3421
		$alt = " alt='".attribut_html($title)."'";
3422
	}
3423
	return "<img src='$img_file'$alt"
3424
	. ($title ? ' title="' . attribut_html($title) . '"' : '')
3425
	. " " . ltrim($atts)
3426
	. " />";
3427
}
3428
3429
/**
3430
 * Générer une directive `style='background:url()'` à partir d'un fichier image
3431
 *
3432
 * @param string $img
3433
 * @param string $att
3434
 * @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...
3435
 * @return string
3436
 */
3437
function http_style_background($img, $att = '', $size=null) {
3438
	if ($size and is_numeric($size)){
3439
		$size = trim($size) . "px";
3440
	}
3441
	return " style='background" .
3442
		($att ? "" : "-image") . ": url(\"" . chemin_image($img) . "\")" . ($att ? (' ' . $att) : '') . ";"
3443
		. ($size ? "background-size:{$size};" : '')
3444
		. "'";
3445
}
3446
3447
3448
function helper_filtre_balise_img_svg_arguments($alt_or_size, $class_or_size, $size) {
3449
	$args = [$alt_or_size, $class_or_size, $size];
3450
	while (is_null(end($args)) and count($args)) {
3451
		array_pop($args);
3452
	}
3453
	if (!count($args)) {
3454
		return [null, null, null];
3455
	}
3456
	if (count($args) < 3) {
3457
		$maybe_size = array_pop($args);
3458
		// @2x
3459
		// @1.5x
3460
		// 512
3461
		// 512x*
3462
		// 512x300
3463
		if (!strlen($maybe_size)
3464
			or !preg_match(',^(@\d+(\.\d+)?x|\d+(x\*)?|\d+x\d+)$,', trim($maybe_size))) {
3465
			$args[] = $maybe_size;
3466
			$maybe_size = null;
3467
		}
3468
		while (count($args)<2) {
3469
			$args[] = null; // default alt or class
3470
		}
3471
		$args[] = $maybe_size;
3472
	}
3473
	return $args;
3474
}
3475
3476
function helper_filtre_balise_img_svg_size($img, $size) {
3477
	// si size est de la forme '@2x' c'est un coeff multiplicateur sur la densite
3478
	if (strpos($size, '@') === 0 and substr($size,-1) === 'x') {
3479
		$coef = floatval(substr($size, 1, -1));
3480
		list($h, $w) = taille_image($img);
3481
		$height = intval(round($h / $coef));
3482
		$width = intval(round($w / $coef));
3483
	}
3484
	// sinon c'est une valeur seule si image caree ou largeurxhauteur
3485
	else {
3486
		$size = explode('x', $size, 2);
3487
		$size = array_map('trim', $size);
3488
		$height = $width = intval(array_shift($size));
3489
3490
		if (count($size) and reset($size)) {
3491
			$height = array_shift($size);
3492
			if ($height === '*') {
3493
				list($h, $w) = taille_image($img);
3494
				$height = intval(round($h * $width / $w));
3495
			}
3496
		}
3497
	}
3498
3499
	return [$width, $height];
3500
}
3501
3502
/**
3503
 * Générer une balise HTML `img` à partir d'un nom de fichier et/ou renseigne son alt/class/width/height
3504
 * selon les arguments passés
3505
 *
3506
 * Le class et le alt peuvent etre omis et dans ce cas size peut-être renseigné comme dernier argument :
3507
 * [(#FICHIER|balise_img{@2x})]
3508
 * [(#FICHIER|balise_img{1024})]
3509
 * [(#FICHIER|balise_img{1024x*})]
3510
 * [(#FICHIER|balise_img{1024x640})]
3511
 * [(#FICHIER|balise_img{'un nuage',1024x640})]
3512
 * [(#FICHIER|balise_img{'un nuage','spip_logo',1024x640})]
3513
 * 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
3514
 * [(#FICHIER|balise_img{'@2x','',''})]
3515
 *
3516
 * @uses http_img_pack()
3517
 *
3518
 * @param string $img
3519
 *   chemin vers un fichier ou balise `<img src='...' />` (generee par un filtre image par exemple)
3520
 * @param string $alt
3521
 *   texte alternatif ; une valeur nulle pour explicitement ne pas avoir de balise alt sur l'image (au lieu d'un alt vide)
3522
 * @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...
3523
 *   attribut class ; null par defaut (ie si img est une balise, son attribut class sera inchange. pas de class inseree
3524
 * @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...
3525
 *   taille imposee
3526
 *     @2x : pour imposer une densite x2 (widht et height seront divisees par 2)
3527
 *     largeur : pour une image carree
3528
 *     largeurx* : pour imposer uniquement la largeur (un attribut height sera aussi ajoute, mais calcule automatiquement pour respecter le ratio initial de l'image)
3529
 *     largeurxhauteur pour fixer les 2 dimensions
3530
 * @return string
3531
 *     Code HTML de la balise IMG
3532
 */
3533
function filtre_balise_img_dist($img, $alt = '', $class = null, $size=null) {
3534
3535
	list($alt, $class, $size) = helper_filtre_balise_img_svg_arguments($alt, $class, $size);
3536
3537
	$img = trim($img);
3538
	if (strpos($img, '<img') === 0) {
3539
		if (!is_null($alt)) {
3540
			$img = inserer_attribut($img, 'alt', $alt);
3541
		}
3542 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...
3543
			if (strlen($class)) {
3544
				$img = inserer_attribut($img, 'class', $class);
3545
			}
3546
			else {
3547
				$img = vider_attribut($img, 'class');
3548
			}
3549
		}
3550
	}
3551
	else {
3552
		$img = http_img_pack($img, $alt, $class ? " class='" . attribut_html($class) . "'" : '', '',
3553
				array('chemin_image' => false, 'utiliser_suffixe_size' => false));
3554
		if (is_null($alt)) {
3555
			$img = vider_attribut($img, 'alt');
3556
		}
3557
	}
3558
3559
	if ($img and !is_null($size) and strlen($size = trim($size))) {
3560
		list($width, $height) = helper_filtre_balise_img_svg_size($img, $size);
3561
3562
		$img = inserer_attribut($img, 'width', $width);
3563
		$img = inserer_attribut($img, 'height', $height);
3564
	}
3565
3566
	return $img;
3567
}
3568
3569
3570
/**
3571
 * Inserer un svg inline
3572
 * http://www.accede-web.com/notices/html-css-javascript/6-images-icones/6-2-svg-images-vectorielles/
3573
 *
3574
 * pour l'inserer avec une balise <img>, utiliser le filtre |balise_img
3575
 *
3576
 * Le class et le alt peuvent etre omis et dans ce cas size peut-être renseigné comme dernier argument :
3577
 * [(#FICHIER|balise_svg{1024x640})]
3578
 * [(#FICHIER|balise_svg{'un nuage',1024x640})]
3579
 * [(#FICHIER|balise_svg{'un nuage','spip_logo',1024x640})]
3580
 * 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
3581
 * [(#FICHIER|balise_svg{'un nuage','@2x',''})]
3582
 *
3583
 * @param string $img
3584
 *   chemin vers un fichier ou balise `<svg ... >... </svg>` deja preparee
3585
 * @param string $alt
3586
 *   texte alternatif ; une valeur nulle pour explicitement ne pas avoir de balise alt sur l'image (au lieu d'un alt vide)
3587
 * @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...
3588
 *   attribut class ; null par defaut (ie si img est une balise, son attribut class sera inchange. pas de class inseree
3589
 * @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...
3590
 *   taille imposee
3591
 *     @2x : pour imposer une densite x2 (widht et height seront divisees par 2)
3592
 *     largeur : pour une image carree
3593
 *     largeurx* : pour imposer uniquement la largeur (un attribut height sera aussi ajoute, mais calcule automatiquement pour respecter le ratio initial de l'image)
3594
 *     largeurxhauteur pour fixer les 2 dimensions
3595
 * @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...
3596
 *     Code HTML de la balise SVG
3597
 */
3598
function filtre_balise_svg_dist($img, $alt = '', $class = null, $size=null) {
3599
3600
	$img = trim($img);
3601
	$img_file = $img;
3602
	if (strpos($img, '<svg') === false){
3603
		if ($p = strpos($img_file, '?')){
3604
			$img_file = substr($img_file, 0, $p);
3605
		}
3606
3607
		if (!$img_file or !$svg = file_get_contents($img_file)){
3608
			return '';
3609
		}
3610
	}
3611
3612
	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...
3613
		return '';
3614
	}
3615
3616
	list($alt, $class, $size) = helper_filtre_balise_img_svg_arguments($alt, $class, $size);
3617
3618
	$balise_svg = $match[0];
3619
	$balise_svg_source = $balise_svg;
3620
3621
	// entete XML à supprimer
3622
	$svg = preg_replace(',^\s*<\?xml[^>]*\?' . '>,', '', $svg);
3623
3624
	// IE est toujours mon ami
3625
	$balise_svg = inserer_attribut($balise_svg, 'focusable', 'false');
3626
3627
	// regler la classe
3628 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...
3629
		if (strlen($class)) {
3630
			$balise_svg = inserer_attribut($balise_svg, 'class', $class);
3631
		}
3632
		else {
3633
			$balise_svg = vider_attribut($balise_svg, 'class');
3634
		}
3635
	}
3636
3637
	// regler le alt
3638
	if ($alt){
3639
		$balise_svg = inserer_attribut($balise_svg, 'role', 'img');
3640
		$id = "img-svg-title-" . substr(md5("$img_file:$svg:$alt"),0,4);
3641
		$balise_svg = inserer_attribut($balise_svg, 'aria-labelledby', $id);
3642
		$title = "<title id=\"$id\">" . entites_html($alt)."</title>\n";
3643
		$balise_svg .= $title;
3644
	}
3645
	else {
3646
		$balise_svg = inserer_attribut($balise_svg, 'aria-hidden', 'true');
3647
	}
3648
3649
	$svg = str_replace($balise_svg_source, $balise_svg, $svg);
3650
3651
	if (!is_null($size) and strlen($size = trim($size))) {
3652
		list($width, $height) = helper_filtre_balise_img_svg_size($svg, $size);
3653
3654
		if (!function_exists('svg_redimensionner')) {
3655
			include_spip('inc/svg');
3656
		}
3657
		$svg = svg_redimensionner($svg, $width, $height);
3658
	}
3659
3660
	return $svg;
3661
}
3662
3663
3664
3665
/**
3666
 * Affiche chaque valeur d'un tableau associatif en utilisant un modèle
3667
 *
3668
 * @example
3669
 *     - `[(#ENV*|unserialize|foreach)]`
3670
 *     - `[(#ARRAY{a,un,b,deux}|foreach)]`
3671
 *
3672
 * @filtre
3673
 * @link https://www.spip.net/4248
3674
 *
3675
 * @param array $tableau
3676
 *     Tableau de données à afficher
3677
 * @param string $modele
3678
 *     Nom du modèle à utiliser
3679
 * @return string
3680
 *     Code HTML résultant
3681
 **/
3682
function filtre_foreach_dist($tableau, $modele = 'foreach') {
3683
	$texte = '';
3684
	if (is_array($tableau)) {
3685
		foreach ($tableau as $k => $v) {
3686
			$res = recuperer_fond('modeles/' . $modele,
3687
				array_merge(array('cle' => $k), (is_array($v) ? $v : array('valeur' => $v)))
3688
			);
3689
			$texte .= $res;
3690
		}
3691
	}
3692
3693
	return $texte;
3694
}
3695
3696
3697
/**
3698
 * Obtient des informations sur les plugins actifs
3699
 *
3700
 * @filtre
3701
 * @uses liste_plugin_actifs() pour connaître les informations affichables
3702
 *
3703
 * @param string $plugin
3704
 *     Préfixe du plugin ou chaîne vide
3705
 * @param string $type_info
3706
 *     Type d'info demandée
3707
 * @param bool $reload
3708
 *     true (à éviter) pour forcer le recalcul du cache des informations des plugins.
3709
 * @return array|string|bool
3710
 *
3711
 *     - Liste sérialisée des préfixes de plugins actifs (si $plugin = '')
3712
 *     - Suivant $type_info, avec $plugin un préfixe
3713
 *         - est_actif : renvoie true s'il est actif, false sinon
3714
 *         - x : retourne l'information x du plugin si présente (et plugin actif)
3715
 *         - tout : retourne toutes les informations du plugin actif
3716
 **/
3717
function filtre_info_plugin_dist($plugin, $type_info, $reload = false) {
3718
	include_spip('inc/plugin');
3719
	$plugin = strtoupper($plugin);
3720
	$plugins_actifs = liste_plugin_actifs();
3721
3722
	if (!$plugin) {
3723
		return serialize(array_keys($plugins_actifs));
3724
	} elseif (empty($plugins_actifs[$plugin]) and !$reload) {
3725
		return '';
3726
	} elseif (($type_info == 'est_actif') and !$reload) {
3727
		return $plugins_actifs[$plugin] ? 1 : 0;
3728
	} elseif (isset($plugins_actifs[$plugin][$type_info]) and !$reload) {
3729
		return $plugins_actifs[$plugin][$type_info];
3730
	} else {
3731
		$get_infos = charger_fonction('get_infos', 'plugins');
3732
		// On prend en compte les extensions
3733
		if (!is_dir($plugins_actifs[$plugin]['dir_type'])) {
3734
			$dir_plugins = constant($plugins_actifs[$plugin]['dir_type']);
3735
		} else {
3736
			$dir_plugins = $plugins_actifs[$plugin]['dir_type'];
3737
		}
3738
		if (!$infos = $get_infos($plugins_actifs[$plugin]['dir'], $reload, $dir_plugins)) {
3739
			return '';
3740
		}
3741
		if ($type_info == 'tout') {
3742
			return $infos;
3743
		} elseif ($type_info == 'est_actif') {
3744
			return $infos ? 1 : 0;
3745
		} else {
3746
			return strval($infos[$type_info]);
3747
		}
3748
	}
3749
}
3750
3751
3752
/**
3753
 * Affiche la puce statut d'un objet, avec un menu rapide pour changer
3754
 * de statut si possibilité de l'avoir
3755
 *
3756
 * @see inc_puce_statut_dist()
3757
 *
3758
 * @filtre
3759
 *
3760
 * @param int $id_objet
3761
 *     Identifiant de l'objet
3762
 * @param string $statut
3763
 *     Statut actuel de l'objet
3764
 * @param int $id_rubrique
3765
 *     Identifiant du parent
3766
 * @param string $type
3767
 *     Type d'objet
3768
 * @param bool $ajax
3769
 *     Indique s'il ne faut renvoyer que le coeur du menu car on est
3770
 *     dans une requete ajax suite à un post de changement rapide
3771
 * @return string
3772
 *     Code HTML de l'image de puce de statut à insérer (et du menu de changement si présent)
3773
 */
3774
function puce_changement_statut($id_objet, $statut, $id_rubrique, $type, $ajax = false) {
3775
	$puce_statut = charger_fonction('puce_statut', 'inc');
3776
3777
	return $puce_statut($id_objet, $statut, $id_rubrique, $type, $ajax);
3778
}
3779
3780
3781
/**
3782
 * Affiche la puce statut d'un objet, avec un menu rapide pour changer
3783
 * de statut si possibilité de l'avoir
3784
 *
3785
 * Utilisable sur tout objet qui a declaré ses statuts
3786
 *
3787
 * @example
3788
 *     [(#STATUT|puce_statut{article})] affiche une puce passive
3789
 *     [(#STATUT|puce_statut{article,#ID_ARTICLE,#ID_RUBRIQUE})] affiche une puce avec changement rapide
3790
 *
3791
 * @see inc_puce_statut_dist()
3792
 *
3793
 * @filtre
3794
 *
3795
 * @param string $statut
3796
 *     Statut actuel de l'objet
3797
 * @param string $objet
3798
 *     Type d'objet
3799
 * @param int $id_objet
3800
 *     Identifiant de l'objet
3801
 * @param int $id_parent
3802
 *     Identifiant du parent
3803
 * @return string
3804
 *     Code HTML de l'image de puce de statut à insérer (et du menu de changement si présent)
3805
 */
3806
function filtre_puce_statut_dist($statut, $objet, $id_objet = 0, $id_parent = 0) {
3807
	static $puce_statut = null;
3808
	if (!$puce_statut) {
3809
		$puce_statut = charger_fonction('puce_statut', 'inc');
3810
	}
3811
3812
	return $puce_statut($id_objet, $statut, $id_parent, $objet, false,
3813
		objet_info($objet, 'editable') ? _ACTIVER_PUCE_RAPIDE : false);
3814
}
3815
3816
3817
/**
3818
 * Encoder un contexte pour l'ajax
3819
 *
3820
 * Encoder le contexte, le signer avec une clé, le crypter
3821
 * avec le secret du site, le gziper si possible.
3822
 *
3823
 * L'entrée peut-être sérialisée (le `#ENV**` des fonds ajax et ajax_stat)
3824
 *
3825
 * @see  decoder_contexte_ajax()
3826
 * @uses calculer_cle_action()
3827
 *
3828
 * @param string|array $c
3829
 *   contexte, peut etre un tableau serialize
3830
 * @param string $form
3831
 *   nom du formulaire eventuel
3832
 * @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...
3833
 *   contenu a emboiter dans le conteneur ajax
3834
 * @param string $ajaxid
3835
 *   ajaxid pour cibler le bloc et forcer sa mise a jour
3836
 * @return string
3837
 *   hash du contexte
3838
 */
3839
function encoder_contexte_ajax($c, $form = '', $emboite = null, $ajaxid = '') {
3840
	if (is_string($c)
3841
		and @unserialize($c) !== false
3842
	) {
3843
		$c = unserialize($c);
3844
	}
3845
3846
	// supprimer les parametres debut_x
3847
	// pour que la pagination ajax ne soit pas plantee
3848
	// si on charge la page &debut_x=1 : car alors en cliquant sur l'item 0,
3849
	// le debut_x=0 n'existe pas, et on resterait sur 1
3850
	if (is_array($c)) {
3851
		foreach ($c as $k => $v) {
3852
			if (strpos($k, 'debut_') === 0) {
3853
				unset($c[$k]);
3854
			}
3855
		}
3856
	}
3857
3858
	if (!function_exists('calculer_cle_action')) {
3859
		include_spip("inc/securiser_action");
3860
	}
3861
3862
	$c = serialize($c);
3863
	$cle = calculer_cle_action($form . $c);
3864
	$c = "$cle:$c";
3865
3866
	// on ne stocke pas les contextes dans des fichiers en cache
3867
	// par defaut, sauf si cette configuration a été forcée
3868
	// OU que la longueur de l’argument géneré est plus long
3869
	// que ce qui est toléré.
3870
	$cache_contextes_ajax = (defined('_CACHE_CONTEXTES_AJAX') and _CACHE_CONTEXTES_AJAX);
3871
	if (!$cache_contextes_ajax) {
3872
		$env = $c;
3873
		if (function_exists('gzdeflate') && function_exists('gzinflate')) {
3874
			$env = gzdeflate($env);
3875
		}
3876
		$env = _xor($env);
3877
		$env = base64_encode($env);
3878
		$len = strlen($env);
3879
		// Si l’url est trop longue pour le navigateur
3880
		$max_len = _CACHE_CONTEXTES_AJAX_SUR_LONGUEUR;
3881
		if ($len > $max_len) {
3882
			$cache_contextes_ajax = true;
3883
			spip_log("Contextes AJAX forces en fichiers !"
3884
				. " Cela arrive lorsque la valeur du contexte" 
3885
				. " depasse la longueur maximale autorisee ($max_len). Ici : $len."
3886
				, _LOG_AVERTISSEMENT);
3887
		}
3888
		// Sinon si Suhosin est actif et a une la valeur maximale des variables en GET...
3889
		elseif (
3890
			$max_len = @ini_get('suhosin.get.max_value_length')
3891
			and $max_len < $len
3892
		) {
3893
			$cache_contextes_ajax = true;
3894
			spip_log("Contextes AJAX forces en fichiers !"
3895
				. " Cela arrive lorsque la valeur du contexte"
3896
				. " depasse la longueur maximale autorisee par Suhosin"
3897
				. " ($max_len) dans 'suhosin.get.max_value_length'. Ici : $len."
3898
				. " Vous devriez modifier les parametres de Suhosin"
3899
				. " pour accepter au moins 1024 caracteres.", _LOG_AVERTISSEMENT);
3900
		} 
3901
3902
	}
3903
3904
	if ($cache_contextes_ajax) {
3905
		$dir = sous_repertoire(_DIR_CACHE, 'contextes');
3906
		// stocker les contextes sur disque et ne passer qu'un hash dans l'url
3907
		$md5 = md5($c);
3908
		ecrire_fichier("$dir/c$md5", $c);
3909
		$env = $md5;
3910
	}
3911
3912
	if ($emboite === null) {
3913
		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...
3914
	}
3915
	if (!trim($emboite)) {
3916
		return "";
3917
	}
3918
	// toujours encoder l'url source dans le bloc ajax
3919
	$r = self();
3920
	$r = ' data-origin="' . $r . '"';
3921
	$class = 'ajaxbloc';
3922
	if ($ajaxid and is_string($ajaxid)) {
3923
		// ajaxid est normalement conforme a un nom de classe css
3924
		// on ne verifie pas la conformite, mais on passe entites_html par dessus par precaution
3925
		$class .= ' ajax-id-' . entites_html($ajaxid);
3926
	}
3927
3928
	return "<div class='$class' " . "data-ajax-env='$env'$r>\n$emboite</div><!--ajaxbloc-->\n";
3929
}
3930
3931
/**
3932
 * Décoder un hash de contexte pour l'ajax
3933
 *
3934
 * Précude inverse de `encoder_contexte_ajax()`
3935
 *
3936
 * @see  encoder_contexte_ajax()
3937
 * @uses calculer_cle_action()
3938
 *
3939
 * @param string $c
3940
 *   hash du contexte
3941
 * @param string $form
3942
 *   nom du formulaire eventuel
3943
 * @return array|string|bool
3944
 *   - array|string : contexte d'environnement, possiblement sérialisé
3945
 *   - false : erreur de décodage
3946
 */
3947
function decoder_contexte_ajax($c, $form = '') {
3948
	if (!function_exists('calculer_cle_action')) {
3949
		include_spip("inc/securiser_action");
3950
	}
3951
	if (((defined('_CACHE_CONTEXTES_AJAX') and _CACHE_CONTEXTES_AJAX) or strlen($c) == 32)
3952
		and $dir = sous_repertoire(_DIR_CACHE, 'contextes')
3953
		and lire_fichier("$dir/c$c", $contexte)
3954
	) {
3955
		$c = $contexte;
3956
	} else {
3957
		$c = @base64_decode($c);
3958
		$c = _xor($c);
3959
		if (function_exists('gzdeflate') && function_exists('gzinflate')) {
3960
			$c = @gzinflate($c);
3961
		}
3962
	}
3963
3964
	// extraire la signature en debut de contexte
3965
	// et la verifier avant de deserializer
3966
	// format : signature:donneesserializees
3967 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...
3968
		$cle = substr($c,0,$p);
3969
		$c = substr($c,$p+1);
3970
3971
		if ($cle == calculer_cle_action($form . $c)) {
3972
			$env = @unserialize($c);
3973
			return $env;
3974
		}
3975
	}
3976
3977
	return false;
3978
}
3979
3980
3981
/**
3982
 * Encrypte ou décrypte un message
3983
 *
3984
 * @link http://www.php.net/manual/fr/language.operators.bitwise.php#81358
3985
 *
3986
 * @param string $message
3987
 *    Message à encrypter ou décrypter
3988
 * @param null|string $key
3989
 *    Clé de cryptage / décryptage.
3990
 *    Une clé sera calculée si non transmise
3991
 * @return string
3992
 *    Message décrypté ou encrypté
3993
 **/
3994
function _xor($message, $key = null) {
3995
	if (is_null($key)) {
3996
		if (!function_exists('calculer_cle_action')) {
3997
			include_spip("inc/securiser_action");
3998
		}
3999
		$key = pack("H*", calculer_cle_action('_xor'));
4000
	}
4001
4002
	$keylen = strlen($key);
4003
	$messagelen = strlen($message);
4004
	for ($i = 0; $i < $messagelen; $i++) {
4005
		$message[$i] = ~($message[$i] ^ $key[$i % $keylen]);
4006
	}
4007
4008
	return $message;
4009
}
4010
4011
/**
4012
 * Retourne une URL de réponse de forum (aucune action ici)
4013
 *
4014
 * @see filtre_url_reponse_forum() du plugin forum (prioritaire)
4015
 * @note
4016
 *   La vraie fonction est dans le plugin forum,
4017
 *   mais on évite ici une erreur du compilateur en absence du plugin
4018
 * @param string $texte
4019
 * @return string
4020
 */
4021
function url_reponse_forum($texte) { return $texte; }
4022
4023
/**
4024
 * retourne une URL de suivi rss d'un forum (aucune action ici)
4025
 *
4026
 * @see filtre_url_rss_forum() du plugin forum (prioritaire)
4027
 * @note
4028
 *   La vraie fonction est dans le plugin forum,
4029
 *   mais on évite ici une erreur du compilateur en absence du plugin
4030
 * @param string $texte
4031
 * @return string
4032
 */
4033
function url_rss_forum($texte) { return $texte; }
4034
4035
4036
/**
4037
 * Génère des menus avec liens ou `<strong class='on'>` non clicable lorsque
4038
 * l'item est sélectionné
4039
 * Le parametre $on peut recevoir un selecteur simplifié de type 'a.active' 'strong.active.expose'
4040
 * pour préciser la balise (a, span ou strong uniquement) et la/les classes à utiliser quand on est expose
4041
 *
4042
 * @filtre
4043
 * @link https://www.spip.net/4004
4044
 * @example
4045
 *   ```
4046
 *   [(#URL_RUBRIQUE|lien_ou_expose{#TITRE, #ENV{test}|=={en_cours}})]
4047
 *   [(#URL_RUBRIQUE|lien_ou_expose{#TITRE, #ENV{test}|=={en_cours}|?{a.monlien.active}, 'monlien'})]
4048
 *   ```
4049
 *
4050
 *
4051
 * @param string $url
4052
 *   URL du lien
4053
 * @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...
4054
 *   Texte du lien
4055
 * @param bool $on
4056
 *   É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>
4057
 *   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
4058
 * @param string $class
4059
 *   Classes CSS ajoutées au lien
4060
 * @param string $title
4061
 *   Title ajouté au lien
4062
 * @param string $rel
4063
 *   Attribut `rel` ajouté au lien
4064
 * @param string $evt
4065
 *   Complement à la balise `a` pour gérer un événement javascript,
4066
 *   de la forme ` onclick='...'`
4067
 * @return string
4068
 *   Code HTML
4069
 */
4070
function lien_ou_expose($url, $libelle = null, $on = false, $class = "", $title = "", $rel = "", $evt = '') {
4071
	if ($on) {
4072
		$bal = 'strong';
4073
		$class = "";
4074
		$att = "";
4075
		// si $on passe la balise et optionnelement une ou ++classe
4076
		// a.active span.selected.active etc....
4077
		if (is_string($on) and (strncmp($on, 'a', 1)==0 or strncmp($on, 'span', 4)==0 or strncmp($on, 'strong', 6)==0)){
4078
			$on = explode(".", $on);
4079
			// on verifie que c'est exactement une des 3 balises a, span ou strong
4080
			if (in_array(reset($on), array('a', 'span', 'strong'))){
4081
				$bal = array_shift($on);
4082
				$class = implode(" ", $on);
4083
				if ($bal=="a"){
4084
					$att = 'href="#" ';
4085
				}
4086
			}
4087
		}
4088
		$att .= 'class="' . ($class ? attribut_html($class) . ' ' : '') . (defined('_LIEN_OU_EXPOSE_CLASS_ON') ? _LIEN_OU_EXPOSE_CLASS_ON : 'on') . '"';
4089
	} else {
4090
		$bal = 'a';
4091
		$att = "href='$url'"
4092
			. ($title ? " title='" . attribut_html($title) . "'" : '')
4093
			. ($class ? " class='" . attribut_html($class) . "'" : '')
4094
			. ($rel ? " rel='" . attribut_html($rel) . "'" : '')
4095
			. $evt;
4096
	}
4097
	if ($libelle === null) {
4098
		$libelle = $url;
4099
	}
4100
4101
	return "<$bal $att>$libelle</$bal>";
4102
}
4103
4104
4105
/**
4106
 * Afficher un message "un truc"/"N trucs"
4107
 * Les items sont à indiquer comme pour la fonction _T() sous la forme :
4108
 * "module:chaine"
4109
 *
4110
 * @param int $nb : le nombre
4111
 * @param string $chaine_un : l'item de langue si $nb vaut un
4112
 * @param string $chaine_plusieurs : l'item de lanque si $nb >= 2
4113
 * @param string $var : La variable à remplacer par $nb dans l'item de langue (facultatif, défaut "nb")
4114
 * @param array $vars : Les autres variables nécessaires aux chaines de langues (facultatif)
4115
 * @return string : la chaine de langue finale en utilisant la fonction _T()
4116
 */
4117
function singulier_ou_pluriel($nb, $chaine_un, $chaine_plusieurs, $var = 'nb', $vars = array()) {
4118
	static $local_singulier_ou_pluriel = array();
4119
4120
	// si nb=0 ou pas de $vars valide on retourne une chaine vide, a traiter par un |sinon
4121
	if (!is_numeric($nb) or $nb == 0) {
4122
		return "";
4123
	}
4124
	if (!is_array($vars)) {
4125
		return "";
4126
	}
4127
4128
	$langue = $GLOBALS['spip_lang'];
4129
	if (!isset($local_singulier_ou_pluriel[$langue])) {
4130
		$local_singulier_ou_pluriel[$langue] = false;
4131
		if ($f = charger_fonction("singulier_ou_pluriel_${langue}", 'inc', true)
4132
		  or $f = charger_fonction("singulier_ou_pluriel", 'inc', true)) {
4133
			$local_singulier_ou_pluriel[$langue] = $f;
4134
		}
4135
	}
4136
4137
	// si on a une surcharge on l'utilise
4138
	if ($local_singulier_ou_pluriel[$langue]) {
4139
		return ($local_singulier_ou_pluriel[$langue])($nb, $chaine_un, $chaine_plusieurs, $var, $vars);
4140
	}
4141
4142
	// sinon traitement par defaut
4143
	$vars[$var] = $nb;
4144
	if ($nb >= 2) {
4145
		return _T($chaine_plusieurs, $vars);
4146
	} else {
4147
		return _T($chaine_un, $vars);
4148
	}
4149
}
4150
4151
4152
/**
4153
 * Fonction de base pour une icone dans un squelette.
4154
 * Il peut s'agir soit d'un simple lien, structure html : `<span><a><img><b>texte</b></span>`,
4155
 * soit d'un bouton d'action, structure identique au retour de `#BOUTON_ACTION`.
4156
 *
4157
 * @param string $type
4158
 *  'lien' ou 'bouton'
4159
 * @param string $lien
4160
 *  url
4161
 * @param string $texte
4162
 *  texte du lien / alt de l'image
4163
 * @param string $fond
4164
 *  objet avec ou sans son extension et sa taille (article, article-24, article-24.png)
4165
 * @param string $fonction
4166
 *  new/del/edit
4167
 * @param string $class
4168
 *  Classes supplémentaires (horizontale, verticale, ajax…)
4169
 * @param string $javascript
4170
 *  code javascript tel que "onclick='...'" par exemple
4171
 *  ou texte du message de confirmation s'il s'agit d'un bouton
4172
 * @return string
4173
 */
4174
function prepare_icone_base($type, $lien, $texte, $fond, $fonction = '', $class = '', $javascript = '') {
4175
4176
	$class_lien = $class_bouton = $class;
4177
4178
	// Normaliser la fonction et compléter la classe en fonction
4179
	if (in_array($fonction, ['del', 'supprimer.gif'])) {
4180
		$class_lien .= ' danger';
4181
		$class_bouton .= ' btn_danger';
4182
	} elseif ($fonction == 'rien.gif') {
4183
		$fonction = '';
4184
	} elseif ($fonction == 'delsafe') {
4185
		$fonction = 'del';
4186
	}
4187
4188
	$fond_origine = $fond;
4189
	// Remappage des icone : article-24.png+new => article-new-24.png
4190 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...
4191
		list($fond, $fonction) = $icone_renommer($fond, $fonction);
4192
	}
4193
4194
	// Ajouter le type d'objet dans la classe
4195
	$objet_type = substr(basename($fond), 0, -4);
4196
	$class_lien .= " $objet_type";
4197
	$class_bouton .= " $objet_type";
4198
4199
	// Texte
4200
	$alt = attribut_html($texte);
4201
	$title = " title=\"$alt\""; // est-ce pertinent de doubler le alt par un title ?
4202
4203
	// Liens : préparer les classes ajax
4204
	$ajax = '';
4205
	if ($type === 'lien') {
4206
		if (strpos($class_lien, 'ajax') !== false) {
4207
			$ajax = 'ajax';
4208
			if (strpos($class_lien, 'preload') !== false) {
4209
				$ajax .= ' preload';
4210
			}
4211
			if (strpos($class_lien, 'nocache') !== false) {
4212
				$ajax .= ' nocache';
4213
			}
4214
			$ajax = " class='$ajax'";
4215
		}
4216
	}
4217
4218
	// Repérer la taille et l'ajouter dans la classe
4219
	$size = 24;
4220
	if (preg_match("/-([0-9]{1,3})[.](gif|png|svg)$/i", $fond, $match)
4221
	  or preg_match("/-([0-9]{1,3})([.](gif|png|svg))?$/i", $fond_origine, $match)) {
4222
		$size = $match[1];
4223
	}
4224
	$class_lien .= " s$size";
4225
	$class_bouton .= " s$size";
4226
4227
	// Icône
4228
	$icone = http_img_pack($fond, $alt, "width='$size' height='$size'");
4229
	$icone = "<span class=\"icone-image".($fonction ? " icone-fonction icone-fonction-$fonction" : "") . "\">$icone</span>";
4230
4231
	// Markup final
4232
	if ($type == 'lien') {
4233
		return "<span class='icone $class_lien'>"
4234
		. "<a href='$lien'$title$ajax$javascript>"
4235
		. $icone
4236
		. "<b>$texte</b>"
4237
		. "</a></span>\n";
4238
	} else {
4239
		return bouton_action("$icone $texte", $lien, $class_bouton, $javascript, $alt);
4240
	}
4241
}
4242
4243
/**
4244
 * Crée un lien ayant une icone
4245
 *
4246
 * @uses prepare_icone_base()
4247
 *
4248
 * @param string $lien
4249
 *     URL du lien
4250
 * @param string $texte
4251
 *     Texte du lien
4252
 * @param string $fond
4253
 *     Objet avec ou sans son extension et sa taille (article, article-24, article-24.png)
4254
 * @param string $fonction
4255
 *     Fonction du lien (`edit`, `new`, `del`)
4256
 * @param string $class
4257
 *     Classe CSS, tel que `left`, `right` pour définir un alignement
4258
 * @param string $javascript
4259
 *     Javascript ajouté sur le lien
4260
 * @return string
4261
 *     Code HTML du lien
4262
 **/
4263
function icone_base($lien, $texte, $fond, $fonction = "", $class = "", $javascript = "") {
4264
	return prepare_icone_base('lien', $lien, $texte, $fond, $fonction, $class, $javascript);
4265
}
4266
4267
/**
4268
 * Crée un lien précédé d'une icone au dessus du texte
4269
 *
4270
 * @uses icone_base()
4271
 * @see  icone_verticale() Pour un usage dans un code PHP.
4272
 *
4273
 * @filtre
4274
 * @example
4275
 *     ```
4276
 *     [(#AUTORISER{voir,groupemots,#ID_GROUPE})
4277
 *         [(#URL_ECRIRE{groupe_mots,id_groupe=#ID_GROUPE}
4278
 *            |icone_verticale{<:mots:icone_voir_groupe_mots:>,groupe_mots-24.png,'',left})]
4279
 *    ]
4280
 *     ```
4281
 *
4282
 * @param string $lien
4283
 *     URL du lien
4284
 * @param string $texte
4285
 *     Texte du lien
4286
 * @param string $fond
4287
 *     Objet avec ou sans son extension et sa taille (article, article-24, article-24.png)
4288
 * @param string $fonction
4289
 *     Fonction du lien (`edit`, `new`, `del`)
4290
 * @param string $class
4291
 *     Classe CSS à ajouter, tel que `left`, `right`, `center` pour définir un alignement.
4292
 *     Il peut y en avoir plusieurs : `left ajax`
4293
 * @param string $javascript
4294
 *     Javascript ajouté sur le lien
4295
 * @return string
4296
 *     Code HTML du lien
4297
 **/
4298
function filtre_icone_verticale_dist($lien, $texte, $fond, $fonction = "", $class = "", $javascript = "") {
4299
	return icone_base($lien, $texte, $fond, $fonction, "verticale $class", $javascript);
4300
}
4301
4302
/**
4303
 * Crée un lien précédé d'une icone horizontale
4304
 *
4305
 * @uses icone_base()
4306
 * @see  icone_horizontale() Pour un usage dans un code PHP.
4307
 *
4308
 * @filtre
4309
 * @example
4310
 *     En tant que filtre dans un squelettes :
4311
 *     ```
4312
 *     [(#URL_ECRIRE{sites}|icone_horizontale{<:sites:icone_voir_sites_references:>,site-24.png})]
4313
 *
4314
 *     [(#AUTORISER{supprimer,groupemots,#ID_GROUPE}|oui)
4315
 *         [(#URL_ACTION_AUTEUR{supprimer_groupe_mots,#ID_GROUPE,#URL_ECRIRE{mots}}
4316
 *             |icone_horizontale{<:mots:icone_supprimer_groupe_mots:>,groupe_mots,del})]
4317
 *     ]
4318
 *     ```
4319
 *
4320
 *     En tant que filtre dans un code php :
4321
 *     ```
4322
 *     $icone_horizontale=chercher_filtre('icone_horizontale');
4323
 *     $icone = $icone_horizontale(generer_url_ecrire("stats_visites","id_article=$id_article"),
4324
 *         _T('statistiques:icone_evolution_visites', array('visites' => $visites)),
4325
 *         "statistique-24.png");
4326
 *     ```
4327
 *
4328
 * @param string $lien
4329
 *     URL du lien
4330
 * @param string $texte
4331
 *     Texte du lien
4332
 * @param string $fond
4333
 *     Objet avec ou sans son extension et sa taille (article, article-24, article-24.png)
4334
 * @param string $fonction
4335
 *     Fonction du lien (`edit`, `new`, `del`)
4336
 * @param string $class
4337
 *     Classe CSS à ajouter
4338
 * @param string $javascript
4339
 *     Javascript ajouté sur le lien
4340
 * @return string
4341
 *     Code HTML du lien
4342
 **/
4343
function filtre_icone_horizontale_dist($lien, $texte, $fond, $fonction = "", $class = "", $javascript = "") {
4344
	return icone_base($lien, $texte, $fond, $fonction, "horizontale $class", $javascript);
4345
}
4346
4347
/**
4348
 * Crée un bouton d'action intégrant une icone horizontale
4349
 *
4350
 * @uses prepare_icone_base()
4351
 *
4352
 * @filtre
4353
 * @example
4354
 *     ```
4355
 *     [(#URL_ACTION_AUTEUR{supprimer_mot, #ID_MOT, #URL_ECRIRE{groupe_mots,id_groupe=#ID_GROUPE}}
4356
 *         |bouton_action_horizontal{<:mots:info_supprimer_mot:>,mot-24.png,del,#ARRAY{bouton,btn_large}})]
4357
 *     ```
4358
 *
4359
 * @param string $lien
4360
 *     URL de l'action
4361
 * @param string $texte
4362
 *     Texte du bouton
4363
 * @param string $fond
4364
 *     Objet avec ou sans son extension et sa taille (article, article-24, article-24.png)
4365
 * @param string $fonction
4366
 *     Fonction du bouton (`edit`, `new`, `del`)
4367
 * @param string $class
4368
 *     Classes à ajouter au bouton, à l'exception de `ajax` qui est placé sur le formulaire.
4369
 * @param string $confirm
4370
 *     Message de confirmation à ajouter en javascript sur le bouton
4371
 * @return string
4372
 *     Code HTML du lien
4373
 **/
4374
function filtre_bouton_action_horizontal_dist($lien, $texte, $fond, $fonction = "", $class = "", $confirm = "") {
4375
	return prepare_icone_base('bouton', $lien, $texte, $fond, $fonction, $class, $confirm);
4376
}
4377
4378
/**
4379
 * Filtre `icone` pour compatibilité mappé sur `icone_base`
4380
 *
4381
 * @uses icone_base()
4382
 * @see  filtre_icone_verticale_dist()
4383
 *
4384
 * @filtre
4385
 * @deprecated Utiliser le filtre `icone_verticale`
4386
 *
4387
 * @param string $lien
4388
 *     URL du lien
4389
 * @param string $texte
4390
 *     Texte du lien
4391
 * @param string $fond
4392
 *     Nom de l'image utilisée
4393
 * @param string $align
4394
 *     Classe CSS d'alignement (`left`, `right`, `center`)
4395
 * @param string $fonction
4396
 *     Fonction du lien (`edit`, `new`, `del`)
4397
 * @param string $class
4398
 *     Classe CSS à ajouter
4399
 * @param string $javascript
4400
 *     Javascript ajouté sur le lien
4401
 * @return string
4402
 *     Code HTML du lien
4403
 */
4404
function filtre_icone_dist($lien, $texte, $fond, $align = "", $fonction = "", $class = "", $javascript = "") {
4405
	return icone_base($lien, $texte, $fond, $fonction, "verticale $align $class", $javascript);
4406
}
4407
4408
4409
/**
4410
 * Explose un texte en tableau suivant un séparateur
4411
 *
4412
 * @note
4413
 *     Inverse l'écriture de la fonction PHP de même nom
4414
 *     pour que le filtre soit plus pratique dans les squelettes
4415
 *
4416
 * @filtre
4417
 * @example
4418
 *     ```
4419
 *     [(#GET{truc}|explode{-})]
4420
 *     ```
4421
 *
4422
 * @param string $a Texte
4423
 * @param string $b Séparateur
4424
 * @return array Liste des éléments
4425
 */
4426
function filtre_explode_dist($a, $b) { return explode($b, $a); }
4427
4428
/**
4429
 * Implose un tableau en chaine en liant avec un séparateur
4430
 *
4431
 * @note
4432
 *     Inverse l'écriture de la fonction PHP de même nom
4433
 *     pour que le filtre soit plus pratique dans les squelettes
4434
 *
4435
 * @filtre
4436
 * @example
4437
 *     ```
4438
 *     [(#GET{truc}|implode{-})]
4439
 *     ```
4440
 *
4441
 * @param array $a Tableau
4442
 * @param string $b Séparateur
4443
 * @return string Texte
4444
 */
4445
function filtre_implode_dist($a, $b) { return is_array($a) ? implode($b, $a) : $a; }
4446
4447
/**
4448
 * Produire les styles privés qui associent item de menu avec icone en background
4449
 *
4450
 * @return string Code CSS
4451
 */
4452
function bando_images_background() {
4453
	include_spip('inc/bandeau');
4454
	// recuperer tous les boutons et leurs images
4455
	$boutons = definir_barre_boutons(definir_barre_contexte(), true, false);
4456
4457
	$res = "";
4458
	foreach ($boutons as $page => $detail) {
4459
		$selecteur = (in_array($page, array('outils_rapides', 'outils_collaboratifs')) ? "" : ".navigation_avec_icones ");
4460
		if (is_array($detail->sousmenu)) {
4461
			foreach ($detail->sousmenu as $souspage => $sousdetail) {
4462
				if ($sousdetail->icone and strlen(trim($sousdetail->icone))) {
4463
					$res .= "\n$selecteur.bando2_$souspage {background-image:url(" . $sousdetail->icone . ");}";
4464
				}
4465
			}
4466
		}
4467
	}
4468
4469
	return $res;
4470
}
4471
4472
/**
4473
 * Générer un bouton_action
4474
 * utilisé par #BOUTON_ACTION
4475
 *
4476
 * @param string $libelle
4477
 *   Libellé du bouton
4478
 * @param string $url
4479
 *   URL d'action sécurisée, généralement obtenue avec generer_action_auteur()
4480
 * @param string $class
4481
 *   Classes à ajouter au bouton, à l'exception de `ajax` qui est placé sur le formulaire.
4482
 * @param string $confirm
4483
 *   Message de confirmation oui/non avant l'action
4484
 * @param string $title
4485
 *   Attribut title à ajouter au bouton
4486
 * @param string $callback
4487
 *   Callback js a appeler lors de l'évènement action et avant execution de l'action
4488
 *   (ou après confirmation éventuelle si $confirm est non vide).
4489
 *   Si la callback renvoie false, elle annule le déclenchement de l'action.
4490
 * @return string
4491
 */
4492
function bouton_action($libelle, $url, $class = '', $confirm = '', $title = '', $callback = '') {
4493
4494
	// Classes : dispatcher `ajax` sur le formulaire
4495
	$class_form = '';
4496
	if (strpos($class, 'ajax') !== false) {
4497
		$class_form = 'ajax';
4498
		$class = str_replace('ajax', '', $class);
4499
	}
4500
	$class_btn = 'submit ' . trim($class);
4501
4502
	if ($confirm) {
4503
		$confirm = "confirm(\"" . attribut_html($confirm) . "\")";
4504
		if ($callback) {
4505
			$callback = "$confirm?($callback):false";
4506
		} else {
4507
			$callback = $confirm;
4508
		}
4509
	}
4510
	$onclick = $callback ? " onclick='return " . addcslashes($callback, "'") . "'" : "";
4511
	$title = $title ? " title='$title'" : '';
4512
4513
	return "<form class='bouton_action_post $class_form' method='post' action='$url'><div>" . form_hidden($url)
4514
	. "<button type='submit' class='$class_btn'$title$onclick>$libelle</button></div></form>";
4515
}
4516
4517
/**
4518
 * Donner n'importe quelle information sur un objet de maniere generique.
4519
 *
4520
 * La fonction va gerer en interne deux cas particuliers les plus utilises :
4521
 * l'URL et le titre (qui n'est pas forcemment le champ SQL "titre").
4522
 *
4523
 * On peut ensuite personnaliser les autres infos en creant une fonction
4524
 * generer_<nom_info>_entite($id_objet, $type_objet, $ligne).
4525
 * $ligne correspond a la ligne SQL de tous les champs de l'objet, les fonctions
4526
 * de personnalisation n'ont donc pas a refaire de requete.
4527
 *
4528
 * @param int $id_objet
4529
 * @param string $type_objet
4530
 * @param string $info
4531
 * @param string $etoile
4532
 * @return string
4533
 */
4534
function generer_info_entite($id_objet, $type_objet, $info, $etoile = "") {
4535
	static $trouver_table = null;
4536
	static $objets;
4537
4538
	// On verifie qu'on a tout ce qu'il faut
4539
	$id_objet = intval($id_objet);
4540
	if (!($id_objet and $type_objet and $info)) {
4541
		return '';
4542
	}
4543
4544
	// si on a deja note que l'objet n'existe pas, ne pas aller plus loin
4545
	if (isset($objets[$type_objet]) and $objets[$type_objet] === false) {
4546
		return '';
4547
	}
4548
4549
	// Si on demande l'url, on retourne direct la fonction
4550
	if ($info == 'url') {
4551
		return generer_url_entite($id_objet, $type_objet);
4552
	}
4553
4554
	// Sinon on va tout chercher dans la table et on garde en memoire
4555
	$demande_titre = ($info == 'titre');
4556
4557
	// 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
4558
	if (!isset($objets[$type_objet][$id_objet])
4559
		or
4560
		($demande_titre and !isset($objets[$type_objet][$id_objet]['titre']))
4561
	) {
4562
		if (!$trouver_table) {
4563
			$trouver_table = charger_fonction('trouver_table', 'base');
4564
		}
4565
		$desc = $trouver_table(table_objet_sql($type_objet));
4566
		if (!$desc) {
4567
			return $objets[$type_objet] = false;
4568
		}
4569
4570
		// Si on demande le titre, on le gere en interne
4571
		$champ_titre = "";
4572
		if ($demande_titre) {
4573
			// si pas de titre declare mais champ titre, il sera peuple par le select *
4574
			$champ_titre = (!empty($desc['titre'])) ? ', ' . $desc['titre'] : '';
4575
		}
4576
		include_spip('base/abstract_sql');
4577
		include_spip('base/connect_sql');
4578
		$objets[$type_objet][$id_objet] = sql_fetsel(
4579
			'*' . $champ_titre,
4580
			$desc['table_sql'],
4581
			id_table_objet($type_objet) . ' = ' . intval($id_objet)
4582
		);
4583
	}
4584
4585
	// Si la fonction generer_TRUC_TYPE existe, on l'utilise pour formater $info_generee
4586
	if ($generer = charger_fonction("generer_${info}_${type_objet}", '', true)) {
4587
		$info_generee = $generer($id_objet, $objets[$type_objet][$id_objet]);
4588
	} // Si la fonction generer_TRUC_entite existe, on l'utilise pour formater $info_generee
4589
	else {
4590
		if ($generer = charger_fonction("generer_${info}_entite", '', true)) {
4591
			$info_generee = $generer($id_objet, $type_objet, $objets[$type_objet][$id_objet]);
4592
		} // Sinon on prend directement le champ SQL tel quel
4593
		else {
4594
			$info_generee = (isset($objets[$type_objet][$id_objet][$info]) ? $objets[$type_objet][$id_objet][$info] : '');
4595
		}
4596
	}
4597
4598
	// On va ensuite appliquer les traitements automatiques si besoin
4599
	if (!$etoile) {
4600
		// FIXME: on fournit un ENV minimum avec id et type et connect=''
4601
		// mais ce fonctionnement est a ameliorer !
4602
		$info_generee = appliquer_traitement_champ($info_generee, $info, table_objet($type_objet),
4603
			array('id_objet' => $id_objet, 'objet' => $type_objet, ''));
4604
	}
4605
4606
	return $info_generee;
4607
}
4608
4609
/**
4610
 * Appliquer a un champ SQL le traitement qui est configure pour la balise homonyme dans les squelettes
4611
 *
4612
 * @param string $texte
4613
 * @param string $champ
4614
 * @param string $table_objet
4615
 * @param array $env
4616
 * @param string $connect
4617
 * @return string
4618
 */
4619
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...
4620
	if (!$champ) {
4621
		return $texte;
4622
	}
4623
	
4624
	// On charge toujours les filtres de texte car la majorité des traitements les utilisent
4625
	// et il ne faut pas partir du principe que c'est déjà chargé (form ajax, etc)
4626
	include_spip('inc/texte');
4627
	
4628
	$champ = strtoupper($champ);
4629
	$traitements = isset($GLOBALS['table_des_traitements'][$champ]) ? $GLOBALS['table_des_traitements'][$champ] : false;
4630
	if (!$traitements or !is_array($traitements)) {
4631
		return $texte;
4632
	}
4633
4634
	$traitement = '';
4635
	if ($table_objet and (!isset($traitements[0]) or count($traitements) > 1)) {
4636
		// necessaire pour prendre en charge les vieux appels avec un table_objet_sql en 3e arg
4637
		$table_objet = table_objet($table_objet);
4638
		if (isset($traitements[$table_objet])) {
4639
			$traitement = $traitements[$table_objet];
4640
		}
4641
	}
4642
	if (!$traitement and isset($traitements[0])) {
4643
		$traitement = $traitements[0];
4644
	}
4645
	// (sinon prendre le premier de la liste par defaut ?)
4646
4647
	if (!$traitement) {
4648
		return $texte;
4649
	}
4650
4651
	$traitement = str_replace('%s', "'" . texte_script($texte) . "'", $traitement);
4652
4653
	// Fournir $connect et $Pile[0] au traitement si besoin
4654
	$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...
4655
	eval("\$texte = $traitement;");
4656
4657
	return $texte;
4658
}
4659
4660
4661
/**
4662
 * Generer un lien (titre clicable vers url) vers un objet
4663
 *
4664
 * @param int $id_objet
4665
 * @param $objet
4666
 * @param int $longueur
4667
 * @param null|string $connect
4668
 * @return string
4669
 */
4670
function generer_lien_entite($id_objet, $objet, $longueur = 80, $connect = null) {
4671
	include_spip('inc/liens');
4672
	$titre = traiter_raccourci_titre($id_objet, $objet, $connect);
4673
	// lorsque l'objet n'est plus declare (plugin desactive par exemple)
4674
	// le raccourcis n'est plus valide
4675
	$titre = isset($titre['titre']) ? typo($titre['titre']) : '';
4676
	// on essaye avec generer_info_entite ?
4677
	if (!strlen($titre) and !$connect) {
4678
		$titre = generer_info_entite($id_objet, $objet, 'titre');
4679
	}
4680
	if (!strlen($titre)) {
4681
		$titre = _T('info_sans_titre');
4682
	}
4683
	$url = generer_url_entite($id_objet, $objet, '', '', $connect);
4684
4685
	return "<a href='$url' class='$objet'>" . couper($titre, $longueur) . "</a>";
4686
}
4687
4688
4689
/**
4690
 * Englobe (Wrap) un texte avec des balises
4691
 *
4692
 * @example `wrap('mot','<b>')` donne `<b>mot</b>'`
4693
 *
4694
 * @filtre
4695
 * @uses extraire_balises()
4696
 *
4697
 * @param string $texte
4698
 * @param string $wrap
4699
 * @return string
4700
 */
4701
function wrap($texte, $wrap) {
4702
	$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...
4703
	if (preg_match_all(",<([a-z]\w*)\b[^>]*>,UimsS", $wrap, $regs, PREG_PATTERN_ORDER)) {
4704
		$texte = $wrap . $texte;
4705
		$regs = array_reverse($regs[1]);
4706
		$wrap = "</" . implode("></", $regs) . ">";
4707
		$texte = $texte . $wrap;
4708
	}
4709
4710
	return $texte;
4711
}
4712
4713
4714
/**
4715
 * afficher proprement n'importe quoi
4716
 * On affiche in fine un pseudo-yaml qui premet de lire humainement les tableaux et de s'y reperer
4717
 *
4718
 * Les textes sont retournes avec simplement mise en forme typo
4719
 *
4720
 * 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
4721
 * les tableaux-listes (qui n'ont que des cles numeriques), sont affiches sous forme de liste separee par des virgules :
4722
 * c'est VOULU !
4723
 *
4724
 * @param $u
4725
 * @param string $join
4726
 * @param int $indent
4727
 * @return array|mixed|string
4728
 */
4729
function filtre_print_dist($u, $join = "<br />", $indent = 0) {
4730
	if (is_string($u)) {
4731
		$u = typo($u);
4732
4733
		return $u;
4734
	}
4735
4736
	// caster $u en array si besoin
4737
	if (is_object($u)) {
4738
		$u = (array)$u;
4739
	}
4740
4741
	if (is_array($u)) {
4742
		$out = "";
4743
		// toutes les cles sont numeriques ?
4744
		// et aucun enfant n'est un tableau
4745
		// liste simple separee par des virgules
4746
		$numeric_keys = array_map('is_numeric', array_keys($u));
4747
		$array_values = array_map('is_array', $u);
4748
		$object_values = array_map('is_object', $u);
4749
		if (array_sum($numeric_keys) == count($numeric_keys)
4750
			and !array_sum($array_values)
4751
			and !array_sum($object_values)
4752
		) {
4753
			return join(", ", array_map('filtre_print_dist', $u));
4754
		}
4755
4756
		// sinon on passe a la ligne et on indente
4757
		$i_str = str_pad("", $indent, " ");
4758
		foreach ($u as $k => $v) {
4759
			$out .= $join . $i_str . "$k: " . filtre_print_dist($v, $join, $indent + 2);
4760
		}
4761
4762
		return $out;
4763
	}
4764
4765
	// on sait pas quoi faire...
4766
	return $u;
4767
}
4768
4769
4770
/**
4771
 * Renvoyer l'info d'un objet
4772
 * telles que definies dans declarer_tables_objets_sql
4773
 *
4774
 * @param string $objet
4775
 * @param string $info
4776
 * @return string|array
4777
 */
4778
function objet_info($objet, $info) {
4779
	$table = table_objet_sql($objet);
4780
	$infos = lister_tables_objets_sql($table);
4781
4782
	return (isset($infos[$info]) ? $infos[$info] : '');
4783
}
4784
4785
/**
4786
 * Filtre pour afficher 'Aucun truc' ou '1 truc' ou 'N trucs'
4787
 * avec la bonne chaîne de langue en fonction de l'objet utilisé
4788
 *
4789
 * @param int $nb
4790
 *     Nombre d'éléments
4791
 * @param string $objet
4792
 *     Objet
4793
 * @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...
4794
 *     Texte traduit du comptage, tel que '3 articles'
4795
 */
4796
function objet_afficher_nb($nb, $objet) {
4797
	if (!$nb) {
4798
		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...
4799
	} else {
4800
		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...
4801
	}
4802
}
4803
4804
/**
4805
 * Filtre pour afficher l'img icone d'un objet
4806
 *
4807
 * @param string $objet
4808
 * @param int $taille
4809
 * @param string $class
4810
 * @return string
4811
 */
4812
function objet_icone($objet, $taille = 24, $class='') {
4813
	$icone = objet_info($objet, 'icone_objet') . "-" . $taille . ".png";
4814
	$icone = chemin_image($icone);
4815
	$balise_img = charger_filtre('balise_img');
4816
4817
	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...
4818
}
4819
4820
/**
4821
 * Renvoyer une traduction d'une chaine de langue contextuelle à un objet si elle existe,
4822
 * la traduction de la chaine generique
4823
 *
4824
 * Ex : [(#ENV{objet}|objet_label{trad_reference})]
4825
 * va chercher si une chaine objet:trad_reference existe et renvoyer sa trad le cas echeant
4826
 * sinon renvoie la trad de la chaine trad_reference
4827
 * Si la chaine fournie contient un prefixe il est remplacé par celui de l'objet pour chercher la chaine contextuelle
4828
 *
4829
 * Les arguments $args et $options sont ceux de la fonction _T
4830
 *
4831
 * @param string $objet
4832
 * @param string $chaine
4833
 * @param array $args
4834
 * @param array $options
4835
 * @return string
4836
 */
4837
function objet_T($objet, $chaine, $args = array(), $options = array()){
4838
	$chaine = explode(':',$chaine);
4839
	if ($t = _T($objet . ':' . end($chaine), $args, array_merge($options, array('force'=>false)))) {
4840
		return $t;
4841
	}
4842
	$chaine = implode(':',$chaine);
4843
	return _T($chaine, $args, $options);
4844
}
4845
4846
/**
4847
 * Fonction de secours pour inserer le head_css de facon conditionnelle
4848
 *
4849
 * Appelée en filtre sur le squelette qui contient #INSERT_HEAD,
4850
 * elle vérifie l'absence éventuelle de #INSERT_HEAD_CSS et y suplée si besoin
4851
 * pour assurer la compat avec les squelettes qui n'utilisent pas.
4852
 *
4853
 * @param string $flux Code HTML
4854
 * @return string      Code HTML
4855
 */
4856
function insert_head_css_conditionnel($flux) {
4857
	if (strpos($flux, '<!-- insert_head_css -->') === false
4858
		and $p = strpos($flux, '<!-- insert_head -->')
4859
	) {
4860
		// plutot avant le premier js externe (jquery) pour etre non bloquant
4861
		if ($p1 = stripos($flux, '<script src=') and $p1 < $p) {
4862
			$p = $p1;
4863
		}
4864
		$flux = substr_replace($flux, pipeline('insert_head_css', '<!-- insert_head_css -->'), $p, 0);
4865
	}
4866
4867
	return $flux;
4868
}
4869
4870
/**
4871
 * Produire un fichier statique à partir d'un squelette dynamique
4872
 *
4873
 * Permet ensuite à Apache de le servir en statique sans repasser
4874
 * par spip.php à chaque hit sur le fichier.
4875
 *
4876
 * Si le format (css ou js) est passe dans `contexte['format']`, on l'utilise
4877
 * sinon on regarde si le fond finit par .css ou .js, sinon on utilie "html"
4878
 *
4879
 * @uses urls_absolues_css()
4880
 *
4881
 * @param string $fond
4882
 * @param array $contexte
4883
 * @param array $options
4884
 * @param string $connect
4885
 * @return string
4886
 */
4887
function produire_fond_statique($fond, $contexte = array(), $options = array(), $connect = '') {
4888
	if (isset($contexte['format'])) {
4889
		$extension = $contexte['format'];
4890
		unset($contexte['format']);
4891
	} else {
4892
		$extension = "html";
4893
		if (preg_match(',[.](css|js|json)$,', $fond, $m)) {
4894
			$extension = $m[1];
4895
		}
4896
	}
4897
	// recuperer le contenu produit par le squelette
4898
	$options['raw'] = true;
4899
	$cache = recuperer_fond($fond, $contexte, $options, $connect);
4900
4901
	// calculer le nom de la css
4902
	$dir_var = sous_repertoire(_DIR_VAR, 'cache-' . $extension);
4903
	$nom_safe = preg_replace(",\W,", '_', str_replace('.', '_', $fond));
4904
	$contexte_implicite = calculer_contexte_implicite();
4905
4906
	// par defaut on hash selon les contextes qui sont a priori moins variables
4907
	// mais on peut hasher selon le contenu a la demande, si plusieurs contextes produisent un meme contenu
4908
	// reduit la variabilite du nom et donc le nombre de css concatenees possibles in fine
4909
	if (isset($options['hash_on_content']) and $options['hash_on_content']) {
4910
		$hash = md5($contexte_implicite['host'] . '::'. $cache);
4911
	}
4912
	else {
4913
		unset($contexte_implicite['notes']); // pas pertinent pour signaler un changeemnt de contenu pour des css/js
4914
		ksort($contexte);
4915
		$hash = md5($fond . json_encode($contexte_implicite) . json_encode($contexte) . $connect);
4916
	}
4917
	$filename = $dir_var . $extension . "dyn-$nom_safe-" . substr($hash, 0, 8) . ".$extension";
4918
4919
	// mettre a jour le fichier si il n'existe pas
4920
	// ou trop ancien
4921
	// le dernier fichier produit est toujours suffixe par .last
4922
	// et recopie sur le fichier cible uniquement si il change
4923
	if (!file_exists($filename)
4924
		or !file_exists($filename . ".last")
4925
		or (isset($cache['lastmodified']) and $cache['lastmodified'] and filemtime($filename . ".last") < $cache['lastmodified'])
4926
		or (defined('_VAR_MODE') and _VAR_MODE == 'recalcul')
4927
	) {
4928
		$contenu = $cache['texte'];
4929
		// passer les urls en absolu si c'est une css
4930
		if ($extension == "css") {
4931
			$contenu = urls_absolues_css($contenu,
4932
				test_espace_prive() ? generer_url_ecrire('accueil') : generer_url_public($fond));
4933
		}
4934
4935
		$comment = '';
4936
		// ne pas insérer de commentaire si c'est du json
4937
		if ($extension != "json") {
4938
			$comment = "/* #PRODUIRE{fond=$fond";
4939
			foreach ($contexte as $k => $v) {
4940
				$comment .= ",$k=$v";
4941
			}
4942
			// pas de date dans le commentaire car sinon ca invalide le md5 et force la maj
4943
			// mais on peut mettre un md5 du contenu, ce qui donne un aperu rapide si la feuille a change ou non
4944
			$comment .= "}\n   md5:" . md5($contenu) . " */\n";
4945
		}
4946
		// et ecrire le fichier si il change
4947
		ecrire_fichier_calcule_si_modifie($filename, $comment . $contenu, false, true);
4948
	}
4949
4950
	return timestamp($filename);
4951
}
4952
4953
/**
4954
 * Ajouter un timestamp a une url de fichier
4955
 * [(#CHEMIN{monfichier}|timestamp)]
4956
 *
4957
 * @param string $fichier
4958
 *    Le chemin du fichier sur lequel on souhaite ajouter le timestamp
4959
 * @return string
4960
 *    $fichier auquel on a ajouté le timestamp
4961
 */
4962
function timestamp($fichier) {
4963
	if (!$fichier
4964
		or !file_exists($fichier)
4965
		or !$m = filemtime($fichier)
4966
	) {
4967
		return $fichier;
4968
	}
4969
4970
	return "$fichier?$m";
4971
}
4972
4973
/**
4974
 * Supprimer le timestamp d'une url
4975
 *
4976
 * @param string $url
4977
 * @return string
4978
 */
4979
function supprimer_timestamp($url) {
4980
	if (strpos($url, "?") === false) {
4981
		return $url;
4982
	}
4983
4984
	return preg_replace(",\?[[:digit:]]+$,", "", $url);
4985
}
4986
4987
/**
4988
 * Nettoyer le titre d'un email
4989
 *
4990
 * Éviter une erreur lorsqu'on utilise `|nettoyer_titre_email` dans un squelette de mail
4991
 *
4992
 * @filtre
4993
 * @uses nettoyer_titre_email()
4994
 *
4995
 * @param string $titre
4996
 * @return string
4997
 */
4998
function filtre_nettoyer_titre_email_dist($titre) {
4999
	include_spip('inc/envoyer_mail');
5000
5001
	return nettoyer_titre_email($titre);
5002
}
5003
5004
/**
5005
 * Afficher le sélecteur de rubrique
5006
 *
5007
 * Il permet de placer un objet dans la hiérarchie des rubriques de SPIP
5008
 *
5009
 * @uses chercher_rubrique()
5010
 *
5011
 * @param string $titre
5012
 * @param int $id_objet
5013
 * @param int $id_parent
5014
 * @param string $objet
5015
 * @param int $id_secteur
5016
 * @param bool $restreint
5017
 * @param bool $actionable
5018
 *   true : fournit le selecteur dans un form directement postable
5019
 * @param bool $retour_sans_cadre
5020
 * @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...
5021
 */
5022
function filtre_chercher_rubrique_dist(
5023
	$titre,
5024
	$id_objet,
5025
	$id_parent,
5026
	$objet,
5027
	$id_secteur,
5028
	$restreint,
5029
	$actionable = false,
5030
	$retour_sans_cadre = false
5031
) {
5032
	include_spip('inc/filtres_ecrire');
5033
5034
	return chercher_rubrique($titre, $id_objet, $id_parent, $objet, $id_secteur, $restreint, $actionable,
5035
		$retour_sans_cadre);
5036
}
5037
5038
/**
5039
 * Rediriger une page suivant une autorisation,
5040
 * et ce, n'importe où dans un squelette, même dans les inclusions.
5041
 *
5042
 * En l'absence de redirection indiquée, la fonction redirige par défaut
5043
 * sur une 403 dans l'espace privé et 404 dans l'espace public.
5044
 *
5045
 * @example
5046
 *     ```
5047
 *     [(#AUTORISER{non}|sinon_interdire_acces)]
5048
 *     [(#AUTORISER{non}|sinon_interdire_acces{#URL_PAGE{login}, 401})]
5049
 *     ```
5050
 *
5051
 * @filtre
5052
 * @param bool $ok
5053
 *     Indique si l'on doit rediriger ou pas
5054
 * @param string $url
5055
 *     Adresse eventuelle vers laquelle rediriger
5056
 * @param int $statut
5057
 *     Statut HTML avec lequel on redirigera
5058
 * @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...
5059
 *     message d'erreur
5060
 * @return string|void
5061
 *     Chaîne vide si l'accès est autorisé
5062
 */
5063
function sinon_interdire_acces($ok = false, $url = '', $statut = 0, $message = null) {
5064
	if ($ok) {
5065
		return '';
5066
	}
5067
5068
	// Vider tous les tampons
5069
	$level = @ob_get_level();
5070
	while ($level--) {
5071
		@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...
5072
	}
5073
5074
	include_spip('inc/headers');
5075
5076
	// S'il y a une URL, on redirige (si pas de statut, la fonction mettra 302 par défaut)
5077
	if ($url) {
5078
		redirige_par_entete($url, '', $statut);
5079
	}
5080
5081
	// ecriture simplifiee avec message en 3eme argument (= statut 403)
5082
	if (!is_numeric($statut) and is_null($message)) {
5083
		$message = $statut;
5084
		$statut = 0;
5085
	}
5086
	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...
5087
		$message = '';
5088
	}
5089
	$statut = intval($statut);
5090
5091
	// Si on est dans l'espace privé, on génère du 403 Forbidden par defaut ou du 404
5092
	if (test_espace_prive()) {
5093
		if (!$statut or !in_array($statut, array(404, 403))) {
5094
			$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...
5095
		}
5096
		http_status(403);
5097
		$echec = charger_fonction('403', 'exec');
5098
		$echec($message);
5099
	} else {
5100
		// Sinon dans l'espace public on redirige vers une 404 par défaut, car elle toujours présente normalement
5101
		if (!$statut) {
5102
			$statut = 404;
5103
		}
5104
		// Dans tous les cas on modifie l'entité avec ce qui est demandé
5105
		http_status($statut);
5106
		// Si le statut est une erreur et qu'il n'y a pas de redirection on va chercher le squelette du même nom
5107
		if ($statut >= 400) {
5108
			echo recuperer_fond("$statut", array('erreur' => $message));
5109
		}
5110
	}
5111
5112
5113
	exit;
5114
}
5115
5116
/**
5117
 * Assurer le fonctionnement de |compacte meme sans l'extension compresseur
5118
 *
5119
 * @param string $source
5120
 * @param null|string $format
5121
 * @return string
5122
 */
5123
function filtre_compacte_dist($source, $format = null) {
5124
	if (function_exists('compacte')) {
5125
		return compacte($source, $format);
5126
	}
5127
5128
	return $source;
5129
}
5130
5131
5132
/**
5133
 * Afficher partiellement un mot de passe que l'on ne veut pas rendre lisible par un champ hidden
5134
 * @param string $passe
5135
 * @param bool $afficher_partiellement
5136
 * @param int|null $portion_pourcent
5137
 * @return string
5138
 */
5139
function spip_affiche_mot_de_passe_masque($passe, $afficher_partiellement = false, $portion_pourcent = null) {
5140
	$l = strlen($passe);
5141
5142
	if ($l<=8 or !$afficher_partiellement){
5143
		if (!$l) {
5144
			return ''; // montrer qu'il y a pas de mot de passe si il y en a pas
5145
		}
5146
		return str_pad('',$afficher_partiellement ? $l : 16,'*');
5147
	}
5148
5149
	if (is_null($portion_pourcent)) {
5150
		if (!defined('_SPIP_AFFICHE_MOT_DE_PASSE_MASQUE_PERCENT')) {
5151
			define('_SPIP_AFFICHE_MOT_DE_PASSE_MASQUE_PERCENT', 20); // 20%
5152
		}
5153
		$portion_pourcent = _SPIP_AFFICHE_MOT_DE_PASSE_MASQUE_PERCENT;
5154
	}
5155
	if ($portion_pourcent >= 100) {
5156
		return $passe;
5157
	}
5158
	$e = intval(ceil($l * $portion_pourcent / 100 / 2));
5159
	$e = max($e, 0);
5160
	$mid = str_pad('',$l-2*$e,'*');
5161
	if ($e>0 and strlen($mid)>8){
5162
		$mid = '***...***';
5163
	}
5164
	return substr($passe,0,$e) . $mid . ($e > 0 ? substr($passe,-$e) : '');
5165
}
5166
5167
5168
/**
5169
 * Cette fonction permet de transformer un texte clair en nom court pouvant servir d'identifiant, class, id, url...
5170
 * en ne conservant que des caracteres alphanumeriques et un separateur
5171
 *
5172
 * @param string $texte
5173
 *   Texte à transformer en nom machine
5174
 * @param string $type
5175
 *
5176
 * @param array $options
5177
 *   string separateur : par défaut, un underscore `_`.
5178
 *   int longueur_maxi : par defaut 60
5179
 *   int longueur_mini : par defaut 0
5180
 *
5181
 * @return string
5182
 */
5183
function identifiant_slug($texte, $type = '', $options = array()) {
5184
5185
	$original = $texte;
5186
	$separateur = (isset($options['separateur'])?$options['separateur']:'_');
5187
	$longueur_maxi = (isset($options['longueur_maxi'])?$options['longueur_maxi']:60);
5188
	$longueur_mini = (isset($options['longueur_mini'])?$options['longueur_mini']:0);
5189
5190
	if (!function_exists('translitteration')) {
5191
		include_spip('inc/charsets');
5192
	}
5193
5194
	// pas de balise html
5195
	if (strpos($texte, '<') !== false) {
5196
		$texte = strip_tags($texte);
5197
	}
5198
	if (strpos($texte, '&') !== false) {
5199
		$texte = unicode2charset($texte);
5200
	}
5201
	// On enlève les espaces indésirables
5202
	$texte = trim($texte);
5203
5204
	// On enlève les accents et cie
5205
	$texte = translitteration($texte);
5206
5207
	// On remplace tout ce qui n'est pas un mot par un séparateur
5208
	$texte = preg_replace(',[\W_]+,ms', $separateur, $texte);
5209
5210
	// nettoyer les doubles occurences du separateur si besoin
5211
	while (strpos($texte, "$separateur$separateur") !== false) {
5212
		$texte = str_replace("$separateur$separateur", $separateur, $texte);
5213
	}
5214
5215
	// pas de separateur au debut ni a la fin
5216
	$texte = trim($texte, $separateur);
5217
5218
	// en minuscules
5219
	$texte = strtolower($texte);
5220
5221
	switch ($type) {
5222
		case 'class':
5223
		case 'id':
5224
		case 'anchor':
5225
			if (preg_match(',^\d,', $texte)) {
5226
				$texte = substr($type, 0, 1).$texte;
5227
			}
5228
	}
5229
5230
	if (strlen($texte)>$longueur_maxi) {
5231
		$texte = substr($texte, 0, $longueur_maxi);
5232
	}
5233
5234
	if (strlen($texte) < $longueur_mini and $longueur_mini < $longueur_maxi) {
5235
		if (preg_match(',^\d,', $texte)) {
5236
			$texte = ($type ? substr($type,0,1) : "s") . $texte;
5237
		}
5238
		$texte .= $separateur . md5($original);
5239
		$texte = substr($texte, 0, $longueur_mini);
5240
	}
5241
5242
	return $texte;
5243
}
5244
5245
5246
/**
5247
 * Prépare un texte (issu d'une chaine de langue historique) pour un affichage en label ou titre
5248
 * 
5249
 * Enlève un ':' à la fin d'une chaine de caractère, ainsi que les espaces qui pourraient l'accompagner,
5250
 * Met la première lettre en majuscule (par défaut)
5251
 *
5252
 * Utile afficher dans un contexte de titre des chaines de langues qui contiennent des ':'
5253
 * 
5254
 * @exemple `<:info_maximum|uniformiser_label:>`
5255
 */
5256
function uniformiser_label(string $text, bool $ucfirst = true) : string {
5257
	$label = rtrim($text, " : \t\n\r\0\x0B\xc2\xa0");
5258
	if ($label and $label[-1] === ';') {
5259
		$label = preg_replace("#(\&nbsp;)+$#", "", $label);
5260
	}
5261
	if ($ucfirst) {
5262
		$label = spip_ucfirst($label);
5263
	}
5264
	return $label;
5265
}
5266
5267
5268
/**
5269
 * Helper pour les filtres associés aux fonctions objet_lister_(parents|enfants)(_par_type)?
5270
 *
5271
 * @param string $objet
5272
 * @param int|string $id_objet
5273
 * @return array
5274
 */
5275
function helper_filtre_objet_lister_enfants_ou_parents($objet, $id_objet, $fonction) {
5276
	if (!in_array($fonction, ['objet_lister_parents', 'objet_lister_enfants', 'objet_lister_parents_par_type', 'objet_lister_enfants_par_type'])) {
5277
		return [];
5278
	}
5279
5280
	// compatibilite signature inversee
5281
	if (is_numeric($objet) and !is_numeric($id_objet)) {
5282
		list($objet, $id_objet) = [$id_objet, $objet];
5283
	}
5284
5285
	if (!function_exists($fonction)) {
5286
		include_spip('base/objets');
5287
	}
5288
	return $fonction($objet, $id_objet);
5289
}
5290
5291
5292
/**
5293
 * Cherche les contenus parents d'un objet
5294
 * Supporte l'ecriture inverseee sous la forme
5295
 * [(#ID_OBJET|objet_lister_parents{#OBJET})]
5296
 *
5297
 * @see objet_lister_parents()
5298
 * @param string $objet
5299
 * @param int|string $id_objet
5300
 * @return array
5301
 */
5302
function filtre_objet_lister_parents_dist($objet, $id_objet) {
5303
	return helper_filtre_objet_lister_enfants_ou_parents($objet, $id_objet, 'objet_lister_parents');
5304
}
5305
5306
/**
5307
 * Cherche les contenus parents d'un objet
5308
 * Supporte l'ecriture inverseee sous la forme
5309
 * [(#ID_OBJET|objet_lister_parents_par_type{#OBJET})]
5310
 *
5311
 * @see objet_lister_parents_par_type()
5312
 * @param string $objet
5313
 * @param int|string $id_objet
5314
 * @return array
5315
 */
5316
function filtre_objet_lister_parents_par_type_dist($objet, $id_objet) {
5317
	return helper_filtre_objet_lister_enfants_ou_parents($objet, $id_objet, 'objet_lister_parents_par_type');
5318
}
5319
5320
/**
5321
 * Cherche les contenus enfants d'un objet
5322
 * Supporte l'ecriture inverseee sous la forme
5323
 * [(#ID_OBJET|objet_lister_enfants{#OBJET})]
5324
 *
5325
 * @see objet_lister_enfants()
5326
 * @param string $objet
5327
 * @param int|string $id_objet
5328
 * @return array
5329
 */
5330
function filtre_objet_lister_enfants_dist($objet, $id_objet) {
5331
	return helper_filtre_objet_lister_enfants_ou_parents($objet, $id_objet, 'objet_lister_enfants');
5332
}
5333
5334
/**
5335
 * Cherche les contenus enfants d'un objet
5336
 * Supporte l'ecriture inverseee sous la forme
5337
 * [(#ID_OBJET|objet_lister_enfants_par_type{#OBJET})]
5338
 *
5339
 * @see objet_lister_enfants_par_type()
5340
 * @param string $objet
5341
 * @param int|string $id_objet
5342
 * @return array
5343
 */
5344
function filtre_objet_lister_enfants_par_type_dist($objet, $id_objet) {
5345
	return helper_filtre_objet_lister_enfants_ou_parents($objet, $id_objet, 'objet_lister_enfants_par_type');
5346
}
5347
5348