Completed
Push — master ( c5242e...e18136 )
by cam
04:48
created

filtres.php ➔ uniformiser_label()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
nc 4
nop 2
dl 0
loc 10
rs 9.9332
c 0
b 0
f 0
1
<?php
2
3
/***************************************************************************\
4
 *  SPIP, Système de publication pour l'internet                           *
5
 *                                                                         *
6
 *  Copyright © avec tendresse depuis 2001                                 *
7
 *  Arnaud Martin, Antoine Pitrou, Philippe Rivière, Emmanuel Saint-James  *
8
 *                                                                         *
9
 *  Ce programme est un logiciel libre distribué sous licence GNU/GPL.     *
10
 *  Pour plus de détails voir le fichier COPYING.txt ou l'aide en ligne.   *
11
\***************************************************************************/
12
13
/**
14
 * Déclaration de filtres pour les squelettes
15
 *
16
 * @package SPIP\Core\Filtres
17
 **/
18
if (!defined('_ECRIRE_INC_VERSION')) {
19
	return;
20
}
21
22
include_spip('inc/charsets');
23
include_spip('inc/filtres_mini');
24
include_spip('inc/filtres_dates');
25
include_spip('inc/filtres_selecteur_generique');
26
include_spip('base/objets');
27
include_spip('public/fonctions'); // charger les fichiers fonctions
28
29
/**
30
 * Charger un filtre depuis le php
31
 *
32
 * - on inclue tous les fichiers fonctions des plugins et du skel
33
 * - on appelle chercher_filtre
34
 *
35
 * Pour éviter de perdre le texte si le filtre demandé est introuvable,
36
 * on transmet `filtre_identite_dist` en filtre par défaut.
37
 *
38
 * @uses filtre_identite_dist() Comme fonction par défaut
39
 *
40
 * @param string $fonc Nom du filtre
41
 * @param string $default Filtre par défaut
42
 * @return string Fonction PHP correspondante du filtre
0 ignored issues
show
Documentation introduced by
Should the return type not be null|string?

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

Loading history...
43
 */
44
function charger_filtre($fonc, $default = 'filtre_identite_dist') {
45
	include_fichiers_fonctions(); // inclure les fichiers fonctions
46
	return chercher_filtre($fonc, $default);
47
}
48
49
/**
50
 * Retourne le texte tel quel
51
 *
52
 * @param string $texte Texte
53
 * @return string Texte
54
 **/
55
function filtre_identite_dist($texte) { return $texte; }
56
57
/**
58
 * Cherche un filtre
59
 *
60
 * Pour une filtre `F` retourne la première fonction trouvée parmis :
61
 *
62
 * - filtre_F
63
 * - filtre_F_dist
64
 * - F
65
 *
66
 * Peut gérer des appels par des fonctions statiques de classes tel que `Foo::Bar`
67
 *
68
 * En absence de fonction trouvée, retourne la fonction par défaut indiquée.
69
 *
70
 * @param string $fonc
71
 *     Nom du filtre
72
 * @param null $default
73
 *     Nom du filtre appliqué par défaut si celui demandé n'est pas trouvé
74
 * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be null|string?

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

Loading history...
75
 *     Fonction PHP correspondante du filtre demandé
76
 */
77
function chercher_filtre($fonc, $default = null) {
78
	if (!$fonc) {
79
		return $default;
80
	}
81
	// Cas des types mime, sans confondre avec les appels de fonction de classe
82
	// Foo::Bar
83
	// qui peuvent etre avec un namespace : space\Foo::Bar
84
	if (preg_match(',^[\w]+/,', $fonc)) {
85
		$nom = preg_replace(',\W,', '_', $fonc);
86
		$f = chercher_filtre($nom);
87
		// cas du sous-type MIME sans filtre associe, passer au type:
88
		// si filtre_text_plain pas defini, passe a filtre_text
89
		if (!$f and $nom !== $fonc) {
90
			$f = chercher_filtre(preg_replace(',\W.*$,', '', $fonc));
91
		}
92
93
		return $f;
94
	}
95
96
	include_fichiers_fonctions();
97
	foreach (array('filtre_' . $fonc, 'filtre_' . $fonc . '_dist', $fonc) as $f) {
98
		trouver_filtre_matrice($f); // charge des fichiers spécifiques éventuels
99
		// fonction ou name\space\fonction
100
		if (is_callable($f)) {
101
			return $f;
102
		}
103
		// méthode statique d'une classe Classe::methode ou name\space\Classe::methode
104
		elseif (false === strpos($f, '::') and is_callable(array($f))) {
105
			return $f;
106
		}
107
	}
108
109
	return $default;
110
}
111
112
/**
113
 * Applique un filtre s'il existe, sinon retourne une chaîne vide
114
 *
115
 * Fonction générique qui prend en argument l’objet (texte, etc) à modifier
116
 * et le nom du filtre.
117
 *
118
 * - À la différence de la fonction `filtrer()`, celle-ci ne lève
119
 *   pas d'erreur de squelettes si le filtre n'est pas trouvé.
120
 * - À la différence de la fonction `appliquer_si_filtre()` le contenu
121
 *   d'origine n'est pas retourné si le filtre est absent.
122
 *
123
 * Les arguments supplémentaires transmis à cette fonction sont utilisés
124
 * comme arguments pour le filtre appelé.
125
 *
126
 * @example
127
 *      ```
128
 *      [(#BALISE|appliquer_filtre{nom_du_filtre})]
129
 *      [(#BALISE|appliquer_filtre{nom_du_filtre, arg1, arg2, ...})]
130
 *
131
 *      // Applique le filtre minifier si on le trouve :
132
 *      // - Ne retourne rien si le filtre 'minifier' n'est pas trouvé
133
 *      [(#INCLURE{fichier.js}|appliquer_filtre{minifier, js})]
134
 *
135
 *      // - Retourne le contenu du fichier.js si le filtre n'est pas trouvé.
136
 *      [(#INCLURE{fichier.js}|appliquer_si_filtre{minifier, js})]
137
 *      ```
138
 *
139
 * @filtre
140
 * @see filtrer() Génère une erreur si le filtre est absent
141
 * @see appliquer_si_filtre() Proche : retourne le texte d'origine si le filtre est absent
142
 * @uses appliquer_filtre_sinon()
143
 *
144
 * @param mixed $arg
145
 *     Texte (le plus souvent) sur lequel appliquer le filtre
146
 * @param string $filtre
147
 *     Nom du filtre à appliquer
148
 * @return string
149
 *     Texte traité par le filtre si le filtre existe,
150
 *     Chaîne vide sinon.
151
 **/
152
function appliquer_filtre($arg, $filtre) {
153
	$args = func_get_args();
154
	return appliquer_filtre_sinon($arg, $filtre, $args, '');
155
}
156
157
/**
158
 * Applique un filtre s'il existe, sinon retourne le contenu d'origine sans modification
159
 *
160
 * Se référer à `appliquer_filtre()` pour les détails.
161
 *
162
 * @example
163
 *      ```
164
 *      [(#INCLURE{fichier.js}|appliquer_si_filtre{minifier, js})]
165
 *      ```
166
 * @filtre
167
 * @see appliquer_filtre() Proche : retourne vide si le filtre est absent
168
 * @uses appliquer_filtre_sinon()
169
 *
170
 * @param mixed $arg
171
 *     Texte (le plus souvent) sur lequel appliquer le filtre
172
 * @param string $filtre
173
 *     Nom du filtre à appliquer
174
 * @return string
175
 *     Texte traité par le filtre si le filtre existe,
176
 *     Texte d'origine sinon
177
 **/
178
function appliquer_si_filtre($arg, $filtre) {
179
	$args = func_get_args();
180
	return appliquer_filtre_sinon($arg, $filtre, $args, $arg);
181
}
182
183
/**
184
 * Retourne la version de SPIP
185
 *
186
 * Si l'on retrouve un numéro de révision GIT ou SVN, il est ajouté entre crochets.
187
 * Si effectivement le SPIP est installé par Git ou Svn, 'GIT' ou 'SVN' est ajouté avant sa révision.
188
 *
189
 * @global spip_version_affichee Contient la version de SPIP
190
 * @uses version_vcs_courante() Pour trouver le numéro de révision
191
 *
192
 * @return string
193
 *     Version de SPIP
194
 **/
195
function spip_version() {
196
	$version = $GLOBALS['spip_version_affichee'];
197
	if ($vcs_version = version_vcs_courante(_DIR_RACINE)) {
198
		$version .= " $vcs_version";
199
	}
200
201
	return $version;
202
}
203
204
/**
205
 * Retourne une courte description d’une révision VCS d’un répertoire
206
 *
207
 * @param string $dir Le répertoire à tester
208
 * @param array $raw True pour avoir les données brutes, false pour un texte à afficher
0 ignored issues
show
Documentation introduced by
Should the type for parameter $raw not be false|array? Also, consider making the array more specific, something like array<String>, or String[].

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

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

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

Loading history...
209
 * @retun string|array|null
210
 *    - array|null si $raw = true,
211
 *    - string|null si $raw = false
212
 */
213
function version_vcs_courante($dir, $raw = false) {
214
	$desc = decrire_version_git($dir);
215
	if ($desc === null) {
216
		$desc = decrire_version_svn($dir);
217
	}
218
	if ($desc === null or $raw) {
219
		return $desc;
220
	}
221
	// affichage "GIT [master: abcdef]"
222
	$commit = isset($desc['commit_short']) ? $desc['commit_short'] : $desc['commit'];
223
	if ($desc['branch']) {
224
		$commit = $desc['branch'] . ': ' . $commit;
225
	}
226
	return "{$desc['vcs']} [$commit]";
227
}
228
229
/**
230
 * Retrouve un numéro de révision Git d'un répertoire
231
 *
232
 * @param string $dir Chemin du répertoire
233
 * @return array|null
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array<string,string>|null.

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

Loading history...
234
 *      null si aucune info trouvée
235
 *      array ['branch' => xx, 'commit' => yy] sinon.
236
 **/
237
function decrire_version_git($dir) {
238
	if (!$dir) {
239
		$dir = '.';
240
	}
241
242
	// version installee par GIT
243
	if (lire_fichier($dir . '/.git/HEAD', $c)) {
244
		$currentHead = trim(substr($c, 4));
245
		if (lire_fichier($dir . '/.git/' . $currentHead, $hash)) {
246
			return [
247
				'vcs' => 'GIT',
248
				'branch' => basename($currentHead),
249
				'commit' => trim($hash),
250
				'commit_short' => substr(trim($hash), 0, 8),
251
			];
252
		}
253
	}
254
255
	return null;
256
}
257
258
259
/**
260
 * Retrouve un numéro de révision Svn d'un répertoire
261
 *
262
 * @param string $dir Chemin du répertoire
263
 * @return array|null
264
 *      null si aucune info trouvée
265
 *      array ['commit' => yy, 'date' => xx, 'author' => xx] sinon.
266
 **/
267
function decrire_version_svn($dir) {
268
	if (!$dir) {
269
		$dir = '.';
270
	}
271
	// version installee par SVN
272
	if (file_exists($dir . '/.svn/wc.db') && class_exists('SQLite3')) {
273
		$db = new SQLite3($dir . '/.svn/wc.db');
274
		$result = $db->query('SELECT changed_revision FROM nodes WHERE local_relpath = "" LIMIT 1');
275
		if ($result) {
276
			$row = $result->fetchArray();
277
			if ($row['changed_revision'] != "") {
278
				return [
279
					'vcs' => 'SVN',
280
					'branch' => '',
281
					'commit' => $row['changed_revision'],
282
				];
283
			}
284
		}
285
	}
286
	return null;
287
}
288
289
// 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 3.1
1191
 * @see Utiliser le style CSS `word-wrap:break-word;`
1192
 *
1193
 * @param string $texte Texte
1194
 * @return string Texte encadré du style CSS
1195
 */
1196
function lignes_longues($texte) {
1197
	if (!strlen(trim($texte))) {
1198
		return $texte;
1199
	}
1200
	include_spip('inc/texte');
1201
	$tag = preg_match(',</?(' . _BALISES_BLOCS . ')[>[:space:]],iS', $texte) ?
1202
		'div' : 'span';
1203
1204
	return "<$tag style='word-wrap:break-word;'>$texte</$tag>";
1205
}
1206
1207
/**
1208
 * Passe un texte en majuscules, y compris les accents, en HTML
1209
 *
1210
 * Encadre le texte du style CSS `text-transform: uppercase;`.
1211
 * Le cas spécifique du i turc est géré.
1212
 *
1213
 * @filtre
1214
 * @example
1215
 *     ```
1216
 *     [(#EXTENSION|majuscules)]
1217
 *     ```
1218
 *
1219
 * @param string $texte Texte
1220
 * @return string Texte en majuscule
1221
 */
1222
function majuscules($texte) {
1223
	if (!strlen($texte)) {
1224
		return '';
1225
	}
1226
1227
	// Cas du turc
1228
	if ($GLOBALS['spip_lang'] == 'tr') {
1229
		# remplacer hors des tags et des entites
1230 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...
1231
			foreach ($regs as $n => $match) {
1232
				$texte = str_replace($match[0], "@@SPIP_TURC$n@@", $texte);
1233
			}
1234
		}
1235
1236
		$texte = str_replace('i', '&#304;', $texte);
1237
1238 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...
1239
			foreach ($regs as $n => $match) {
1240
				$texte = str_replace("@@SPIP_TURC$n@@", $match[0], $texte);
1241
			}
1242
		}
1243
	}
1244
1245
	// Cas general
1246
	return "<span style='text-transform: uppercase;'>$texte</span>";
1247
}
1248
1249
/**
1250
 * Retourne une taille en octets humainement lisible
1251
 *
1252
 * Tel que "127.4 ko" ou "3.1 Mo"
1253
 *
1254
 * @example
1255
 *     - `[(#TAILLE|taille_en_octets)]`
1256
 *     - `[(#VAL{123456789}|taille_en_octets)]` affiche `117.7 Mo`
1257
 *
1258
 * @filtre
1259
 * @link https://www.spip.net/4316
1260
 * @param int $taille
1261
 * @return string
1262
 **/
1263
function taille_en_octets($taille) {
1264
	if (!defined('_KILOBYTE')) {
1265
		/**
1266
		 * Définit le nombre d'octets dans un Kilobyte
1267
		 *
1268
		 * @var int
1269
		 **/
1270
		define('_KILOBYTE', 1024);
1271
	}
1272
1273
	if ($taille < 1) {
1274
		return '';
1275
	}
1276
	if ($taille < _KILOBYTE) {
1277
		$taille = _T('taille_octets', array('taille' => $taille));
1278 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...
1279
		$taille = _T('taille_ko', array('taille' => round($taille / _KILOBYTE, 1)));
1280
	} elseif ($taille < _KILOBYTE * _KILOBYTE * _KILOBYTE) {
1281
		$taille = _T('taille_mo', array('taille' => round($taille / _KILOBYTE / _KILOBYTE, 1)));
1282 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...
1283
		$taille = _T('taille_go', array('taille' => round($taille / _KILOBYTE / _KILOBYTE / _KILOBYTE, 2)));
1284
	}
1285
1286
	return $taille;
1287
}
1288
1289
1290
/**
1291
 * Rend une chaine utilisable sans dommage comme attribut HTML
1292
 *
1293
 * @example `<a href="#URL_ARTICLE" title="[(#TITRE|attribut_html)]">#TITRE</a>`
1294
 *
1295
 * @filtre
1296
 * @link https://www.spip.net/4282
1297
 * @uses textebrut()
1298
 * @uses texte_backend()
1299
 *
1300
 * @param string $texte
1301
 *     Texte à mettre en attribut
1302
 * @param bool $textebrut
1303
 *     Passe le texte en texte brut (enlève les balises html) ?
1304
 * @return string
1305
 *     Texte prêt pour être utilisé en attribut HTML
1306
 **/
1307
function attribut_html($texte, $textebrut = true) {
1308
	$u = $GLOBALS['meta']['pcre_u'];
1309
	if ($textebrut) {
1310
		$texte = preg_replace(array(",\n,", ",\s(?=\s),msS" . $u), array(" ", ""), textebrut($texte));
1311
	}
1312
	$texte = texte_backend($texte);
1313
	$texte = str_replace(array("'", '"'), array('&#039;', '&#034;'), $texte);
1314
1315
	return preg_replace(array("/&(amp;|#38;)/", "/&(?![A-Za-z]{0,4}\w{2,3};|#[0-9]{2,5};)/"), array("&", "&#38;"),
1316
		$texte);
1317
}
1318
1319
1320
/**
1321
 * Vider les URL nulles
1322
 *
1323
 * - Vide les URL vides comme `http://` ou `mailto:` (sans rien d'autre)
1324
 * - échappe les entités et gère les `&amp;`
1325
 *
1326
 * @uses entites_html()
1327
 *
1328
 * @param string $url
1329
 *     URL à vérifier et échapper
1330
 * @param bool $entites
1331
 *     `true` pour échapper les entités HTML.
1332
 * @return string
1333
 *     URL ou chaîne vide
1334
 **/
1335
function vider_url($url, $entites = true) {
1336
	# un message pour abs_url
1337
	$GLOBALS['mode_abs_url'] = 'url';
1338
	$url = trim($url);
1339
	$r = ",^(?:" . _PROTOCOLES_STD . '):?/?/?$,iS';
1340
1341
	return preg_match($r, $url) ? '' : ($entites ? entites_html($url) : $url);
1342
}
1343
1344
1345
/**
1346
 * Maquiller une adresse e-mail
1347
 *
1348
 * Remplace `@` par 3 caractères aléatoires.
1349
 *
1350
 * @uses creer_pass_aleatoire()
1351
 *
1352
 * @param string $texte Adresse email
1353
 * @return string Adresse email maquillée
1354
 **/
1355
function antispam($texte) {
1356
	include_spip('inc/acces');
1357
	$masque = creer_pass_aleatoire(3);
1358
1359
	return preg_replace("/@/", " $masque ", $texte);
1360
}
1361
1362
/**
1363
 * Vérifie un accès à faible sécurité
1364
 *
1365
 * Vérifie qu'un visiteur peut accéder à la page demandée,
1366
 * qui est protégée par une clé, calculée à partir du low_sec de l'auteur,
1367
 * et des paramètres le composant l'appel (op, args)
1368
 *
1369
 * @example
1370
 *     `[(#ID_AUTEUR|securiser_acces{#ENV{cle}, rss, #ENV{op}, #ENV{args}}|sinon_interdire_acces)]`
1371
 *
1372
 * @see  bouton_spip_rss() pour générer un lien de faible sécurité pour les RSS privés
1373
 * @see  afficher_low_sec() pour calculer une clé valide
1374
 * @uses verifier_low_sec()
1375
 *
1376
 * @filtre
1377
 * @param int $id_auteur
1378
 *     L'auteur qui demande la page
1379
 * @param string $cle
1380
 *     La clé à tester
1381
 * @param string $dir
1382
 *     Un type d'accès (nom du répertoire dans lequel sont rangés les squelettes demandés, tel que 'rss')
1383
 * @param string $op
1384
 *     Nom de l'opération éventuelle
1385
 * @param string $args
1386
 *     Nom de l'argument calculé
1387
 * @return bool
1388
 *     True si on a le droit d'accès, false sinon.
1389
 **/
1390
function securiser_acces($id_auteur, $cle, $dir, $op = '', $args = '') {
1391
	include_spip('inc/acces');
1392
	if ($op) {
1393
		$dir .= " $op $args";
1394
	}
1395
1396
	return verifier_low_sec($id_auteur, $cle, $dir);
1397
}
1398
1399
/**
1400
 * Retourne le second paramètre lorsque
1401
 * le premier est considere vide, sinon retourne le premier paramètre.
1402
 *
1403
 * En php `sinon($a, 'rien')` retourne `$a`, ou `'rien'` si `$a` est vide.
1404
 * En filtre SPIP `|sinon{#TEXTE, rien}` : affiche `#TEXTE` ou `rien` si `#TEXTE` est vide,
1405
 *
1406
 * @filtre
1407
 * @see filtre_logique() pour la compilation du filtre dans un squelette
1408
 * @link https://www.spip.net/4313
1409
 * @note
1410
 *     L'utilisation de `|sinon` en tant que filtre de squelette
1411
 *     est directement compilé dans `public/references` par la fonction `filtre_logique()`
1412
 *
1413
 * @param mixed $texte
1414
 *     Contenu de reference a tester
1415
 * @param mixed $sinon
1416
 *     Contenu a retourner si le contenu de reference est vide
1417
 * @return mixed
1418
 *     Retourne $texte, sinon $sinon.
1419
 **/
1420
function sinon($texte, $sinon = '') {
1421
	if ($texte or (!is_array($texte) and strlen($texte))) {
1422
		return $texte;
1423
	} else {
1424
		return $sinon;
1425
	}
1426
}
1427
1428
/**
1429
 * Filtre `|choixsivide{vide, pas vide}` alias de `|?{si oui, si non}` avec les arguments inversés
1430
 *
1431
 * @example
1432
 *     `[(#TEXTE|choixsivide{vide, plein})]` affiche vide si le `#TEXTE`
1433
 *     est considéré vide par PHP (chaîne vide, false, 0, tableau vide, etc…).
1434
 *     C'est l'équivalent de `[(#TEXTE|?{plein, vide})]`
1435
 *
1436
 * @filtre
1437
 * @see choixsiegal()
1438
 * @link https://www.spip.net/4189
1439
 *
1440
 * @param mixed $a
1441
 *     La valeur à tester
1442
 * @param mixed $vide
1443
 *     Ce qui est retourné si `$a` est considéré vide
1444
 * @param mixed $pasvide
1445
 *     Ce qui est retourné sinon
1446
 * @return mixed
1447
 **/
1448
function choixsivide($a, $vide, $pasvide) {
1449
	return $a ? $pasvide : $vide;
1450
}
1451
1452
/**
1453
 * Filtre `|choixsiegal{valeur, sioui, sinon}`
1454
 *
1455
 * @example
1456
 *     `#LANG_DIR|choixsiegal{ltr,left,right}` retourne `left` si
1457
 *      `#LANG_DIR` vaut `ltr` et `right` sinon.
1458
 *
1459
 * @filtre
1460
 * @link https://www.spip.net/4148
1461
 *
1462
 * @param mixed $a1
1463
 *     La valeur à tester
1464
 * @param mixed $a2
1465
 *     La valeur de comparaison
1466
 * @param mixed $v
1467
 *     Ce qui est retourné si la comparaison est vraie
1468
 * @param mixed $f
1469
 *     Ce qui est retourné sinon
1470
 * @return mixed
1471
 **/
1472
function choixsiegal($a1, $a2, $v, $f) {
1473
	return ($a1 == $a2) ? $v : $f;
1474
}
1475
1476
//
1477
// Export iCal
1478
//
1479
1480
/**
1481
 * Adapte un texte pour être inséré dans une valeur d'un export ICAL
1482
 *
1483
 * Passe le texte en utf8, enlève les sauts de lignes et échappe les virgules.
1484
 *
1485
 * @example `SUMMARY:[(#TITRE|filtrer_ical)]`
1486
 * @filtre
1487
 *
1488
 * @param string $texte
1489
 * @return string
1490
 **/
1491
function filtrer_ical($texte) {
1492
	#include_spip('inc/charsets');
1493
	$texte = html2unicode($texte);
1494
	$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...
1495
	$texte = preg_replace("/\n/", " ", $texte);
1496
	$texte = preg_replace("/,/", "\,", $texte);
1497
1498
	return $texte;
1499
}
1500
1501
1502
/**
1503
 * Transforme les sauts de ligne simples en sauts forcés avec `_ `
1504
 *
1505
 * Ne modifie pas les sauts de paragraphe (2 sauts consécutifs au moins),
1506
 * ou les retours à l'intérieur de modèles ou de certaines balises html.
1507
 *
1508
 * @note
1509
 *     Cette fonction pouvait être utilisée pour forcer les alinéas,
1510
 *     (retours à la ligne sans saut de paragraphe), mais ce traitement
1511
 *     est maintenant automatique.
1512
 *     Cf. plugin Textwheel et la constante _AUTOBR
1513
 *
1514
 * @uses echappe_html()
1515
 * @uses echappe_retour()
1516
 *
1517
 * @param string $texte
1518
 * @param string $delim
1519
 *      Ce par quoi sont remplacés les sauts
1520
 * @return string
1521
 **/
1522
function post_autobr($texte, $delim = "\n_ ") {
1523
	if (!function_exists('echappe_html')) {
1524
		include_spip('inc/texte_mini');
1525
	}
1526
	$texte = str_replace("\r\n", "\r", $texte);
1527
	$texte = str_replace("\r", "\n", $texte);
1528
1529 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...
1530
		$texte = substr($texte, 0, -strlen($fin = $fin[0]));
1531
	} else {
1532
		$fin = '';
1533
	}
1534
1535
	$texte = echappe_html($texte, '', true);
1536
1537
	// echapper les modeles
1538
	if (strpos($texte, "<") !== false) {
1539
		include_spip('inc/lien');
1540
		if (defined('_PREG_MODELE')) {
1541
			$preg_modeles = "@" . _PREG_MODELE . "@imsS";
1542
			$texte = echappe_html($texte, '', true, $preg_modeles);
1543
		}
1544
	}
1545
1546
	$debut = '';
1547
	$suite = $texte;
1548
	while ($t = strpos('-' . $suite, "\n", 1)) {
1549
		$debut .= substr($suite, 0, $t - 1);
1550
		$suite = substr($suite, $t);
1551
		$car = substr($suite, 0, 1);
1552
		if (($car <> '-') and ($car <> '_') and ($car <> "\n") and ($car <> "|") and ($car <> "}")
1553
			and !preg_match(',^\s*(\n|</?(quote|div|dl|dt|dd)|$),S', ($suite))
1554
			and !preg_match(',</?(quote|div|dl|dt|dd)> *$,iS', $debut)
1555
		) {
1556
			$debut .= $delim;
1557
		} else {
1558
			$debut .= "\n";
1559
		}
1560 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...
1561
			$debut .= $regs[0];
1562
			$suite = substr($suite, strlen($regs[0]));
1563
		}
1564
	}
1565
	$texte = $debut . $suite;
1566
1567
	$texte = echappe_retour($texte);
1568
1569
	return $texte . $fin;
1570
}
1571
1572
1573
/**
1574
 * Expression régulière pour obtenir le contenu des extraits idiomes `<:module:cle:>`
1575
 *
1576
 * @var string
1577
 */
1578
define('_EXTRAIRE_IDIOME', '@<:(?:([a-z0-9_]+):)?([a-z0-9_]+):>@isS');
1579
1580
/**
1581
 * Extrait une langue des extraits idiomes (`<:module:cle_de_langue:>`)
1582
 *
1583
 * Retrouve les balises `<:cle_de_langue:>` d'un texte et remplace son contenu
1584
 * par l'extrait correspondant à la langue demandée (si possible), sinon dans la
1585
 * langue par défaut du site.
1586
 *
1587
 * Ne pas mettre de span@lang=fr si on est déjà en fr.
1588
 *
1589
 * @filtre
1590
 * @uses inc_traduire_dist()
1591
 * @uses code_echappement()
1592
 * @uses echappe_retour()
1593
 *
1594
 * @param string $letexte
1595
 * @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...
1596
 *     Langue à retrouver (si vide, utilise la langue en cours).
1597
 * @param array $options Options {
1598
 * @type bool $echappe_span
1599
 *         True pour échapper les balises span (false par défaut)
1600
 * @type string $lang_defaut
1601
 *         Code de langue : permet de définir la langue utilisée par défaut,
1602
 *         en cas d'absence de traduction dans la langue demandée.
1603
 *         Par défaut la langue du site.
1604
 *         Indiquer 'aucune' pour ne pas retourner de texte si la langue
1605
 *         exacte n'a pas été trouvée.
1606
 * }
1607
 * @return string
1608
 **/
1609
function extraire_idiome($letexte, $lang = null, $options = array()) {
1610
	static $traduire = false;
1611
	if ($letexte
1612
		and preg_match_all(_EXTRAIRE_IDIOME, $letexte, $regs, PREG_SET_ORDER)
1613
	) {
1614
		if (!$traduire) {
1615
			$traduire = charger_fonction('traduire', 'inc');
1616
			include_spip('inc/lang');
1617
		}
1618
		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...
1619
			$lang = $GLOBALS['spip_lang'];
1620
		}
1621
		// Compatibilité avec le prototype de fonction précédente qui utilisait un boolean
1622
		if (is_bool($options)) {
1623
			$options = array('echappe_span' => $options);
1624
		}
1625 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...
1626
			$options = array_merge($options, array('echappe_span' => false));
1627
		}
1628
1629
		foreach ($regs as $reg) {
1630
			$cle = ($reg[1] ? $reg[1] . ':' : '') . $reg[2];
1631
			$desc = $traduire($cle, $lang, true);
1632
			$l = $desc->langue;
1633
			// si pas de traduction, on laissera l'écriture de l'idiome entier dans le texte.
1634
			if (strlen($desc->texte)) {
1635
				$trad = code_echappement($desc->texte, 'idiome', false);
1636
				if ($l !== $lang) {
1637
					$trad = str_replace("'", '"', inserer_attribut($trad, 'lang', $l));
1638
				}
1639 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...
1640
					$trad = str_replace("'", '"', inserer_attribut($trad, 'dir', lang_dir($l)));
1641
				}
1642
				if (!$options['echappe_span']) {
1643
					$trad = echappe_retour($trad, 'idiome');
1644
				}
1645
				$letexte = str_replace($reg[0], $trad, $letexte);
1646
			}
1647
		}
1648
	}
1649
	return $letexte;
1650
}
1651
1652
/**
1653
 * Expression régulière pour obtenir le contenu des extraits polyglottes `<multi>`
1654
 *
1655
 * @var string
1656
 */
1657
define('_EXTRAIRE_MULTI', "@<multi>(.*?)</multi>@sS");
1658
1659
1660
/**
1661
 * Extrait une langue des extraits polyglottes (`<multi>`)
1662
 *
1663
 * Retrouve les balises `<multi>` d'un texte et remplace son contenu
1664
 * par l'extrait correspondant à la langue demandée.
1665
 *
1666
 * Si la langue demandée n'est pas trouvée dans le multi, ni une langue
1667
 * approchante (exemple `fr` si on demande `fr_TU`), on retourne l'extrait
1668
 * correspondant à la langue par défaut (option 'lang_defaut'), qui est
1669
 * par défaut la langue du site. Et si l'extrait n'existe toujours pas
1670
 * dans cette langue, ça utilisera la première langue utilisée
1671
 * dans la balise `<multi>`.
1672
 *
1673
 * Ne pas mettre de span@lang=fr si on est déjà en fr.
1674
 *
1675
 * @filtre
1676
 * @link https://www.spip.net/5332
1677
 *
1678
 * @uses extraire_trads()
1679
 * @uses approcher_langue()
1680
 * @uses lang_typo()
1681
 * @uses code_echappement()
1682
 * @uses echappe_retour()
1683
 *
1684
 * @param string $letexte
1685
 * @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...
1686
 *     Langue à retrouver (si vide, utilise la langue en cours).
1687
 * @param array $options Options {
1688
 * @type bool $echappe_span
1689
 *         True pour échapper les balises span (false par défaut)
1690
 * @type string $lang_defaut
1691
 *         Code de langue : permet de définir la langue utilisée par défaut,
1692
 *         en cas d'absence de traduction dans la langue demandée.
1693
 *         Par défaut la langue du site.
1694
 *         Indiquer 'aucune' pour ne pas retourner de texte si la langue
1695
 *         exacte n'a pas été trouvée.
1696
 * }
1697
 * @return string
1698
 **/
1699
function extraire_multi($letexte, $lang = null, $options = array()) {
1700
1701
	if ($letexte
1702
		and preg_match_all(_EXTRAIRE_MULTI, $letexte, $regs, PREG_SET_ORDER)
1703
	) {
1704
		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...
1705
			$lang = $GLOBALS['spip_lang'];
1706
		}
1707
1708
		// Compatibilité avec le prototype de fonction précédente qui utilisait un boolean
1709
		if (is_bool($options)) {
1710
			$options = array('echappe_span' => $options, 'lang_defaut' => _LANGUE_PAR_DEFAUT);
1711
		}
1712 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...
1713
			$options = array_merge($options, array('echappe_span' => false));
1714
		}
1715
		if (!isset($options['lang_defaut'])) {
1716
			$options = array_merge($options, array('lang_defaut' => _LANGUE_PAR_DEFAUT));
1717
		}
1718
1719
		include_spip('inc/lang');
1720
		foreach ($regs as $reg) {
1721
			// chercher la version de la langue courante
1722
			$trads = extraire_trads($reg[1]);
1723
			if ($l = approcher_langue($trads, $lang)) {
1724
				$trad = $trads[$l];
1725
			} else {
1726
				if ($options['lang_defaut'] == 'aucune') {
1727
					$trad = '';
1728
				} else {
1729
					// langue absente, prendre le fr ou une langue précisée (meme comportement que inc/traduire.php)
1730
					// ou la premiere dispo
1731
					// mais typographier le texte selon les regles de celle-ci
1732
					// Attention aux blocs multi sur plusieurs lignes
1733
					if (!$l = approcher_langue($trads, $options['lang_defaut'])) {
1734
						$l = key($trads);
1735
					}
1736
					$trad = $trads[$l];
1737
					$typographie = charger_fonction(lang_typo($l), 'typographie');
1738
					$trad = $typographie($trad);
1739
					// Tester si on echappe en span ou en div
1740
					// il ne faut pas echapper en div si propre produit un seul paragraphe
1741
					include_spip('inc/texte');
1742
					$trad_propre = preg_replace(",(^<p[^>]*>|</p>$),Uims", "", propre($trad));
1743
					$mode = preg_match(',</?(' . _BALISES_BLOCS . ')[>[:space:]],iS', $trad_propre) ? 'div' : 'span';
1744
					if ($mode === 'div') {
1745
						$trad = rtrim($trad) . "\n\n";
1746
					}
1747
					$trad = code_echappement($trad, 'multi', false, $mode);
1748
					$trad = str_replace("'", '"', inserer_attribut($trad, 'lang', $l));
1749 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...
1750
						$trad = str_replace("'", '"', inserer_attribut($trad, 'dir', lang_dir($l)));
1751
					}
1752
					if (!$options['echappe_span']) {
1753
						$trad = echappe_retour($trad, 'multi');
1754
					}
1755
				}
1756
			}
1757
			$letexte = str_replace($reg[0], $trad, $letexte);
1758
		}
1759
	}
1760
1761
	return $letexte;
1762
}
1763
1764
/**
1765
 * Convertit le contenu d'une balise `<multi>` en un tableau
1766
 *
1767
 * Exemple de blocs.
1768
 * - `texte par défaut [fr] en français [en] en anglais`
1769
 * - `[fr] en français [en] en anglais`
1770
 *
1771
 * @param string $bloc
1772
 *     Le contenu intérieur d'un bloc multi
1773
 * @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...
1774
 *     Peut retourner un code de langue vide, lorsqu'un texte par défaut est indiqué.
1775
 **/
1776
function extraire_trads($bloc) {
1777
	$lang = '';
1778
// ce reg fait planter l'analyse multi s'il y a de l'{italique} dans le champ
1779
//	while (preg_match("/^(.*?)[{\[]([a-z_]+)[}\]]/siS", $bloc, $regs)) {
1780
	while (preg_match("/^(.*?)[\[]([a-z_]+)[\]]/siS", $bloc, $regs)) {
1781
		$texte = trim($regs[1]);
1782
		if ($texte or $lang) {
1783
			$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...
1784
		}
1785
		$bloc = substr($bloc, strlen($regs[0]));
1786
		$lang = $regs[2];
1787
	}
1788
	$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...
1789
1790
	return $trads;
1791
}
1792
1793
1794
/**
1795
 * Calculer l'initiale d'un nom
1796
 *
1797
 * @param string $nom
1798
 * @return string L'initiale en majuscule
1799
 */
1800
function filtre_initiale($nom) {
1801
	return spip_substr(trim(strtoupper(extraire_multi($nom))), 0, 1);
1802
}
1803
1804
1805
/**
1806
 * Retourne la donnée si c'est la première fois qu'il la voit
1807
 *
1808
 * Il est possible de gérer différentes "familles" de données avec
1809
 * le second paramètre.
1810
 *
1811
 * @filtre
1812
 * @link https://www.spip.net/4320
1813
 * @example
1814
 *     ```
1815
 *     [(#ID_SECTEUR|unique)]
1816
 *     [(#ID_SECTEUR|unique{tete})] n'a pas d'incidence sur
1817
 *     [(#ID_SECTEUR|unique{pied})]
1818
 *     [(#ID_SECTEUR|unique{pied,1})] affiche le nombre d'éléments.
1819
 *     Préférer totefois #TOTAL_UNIQUE{pied}
1820
 *     ```
1821
 *
1822
 * @todo
1823
 *    Ameliorations possibles :
1824
 *
1825
 *    1) si la donnée est grosse, mettre son md5 comme clé
1826
 *    2) purger $mem quand on change de squelette (sinon bug inclusions)
1827
 *
1828
 * @param string $donnee
1829
 *      Donnée que l'on souhaite unique
1830
 * @param string $famille
1831
 *      Famille de stockage (1 unique donnée par famille)
1832
 *
1833
 *      - _spip_raz_ : (interne) Vide la pile de mémoire et la retourne
1834
 *      - _spip_set_ : (interne) Affecte la pile de mémoire avec la donnée
1835
 * @param bool $cpt
1836
 *      True pour obtenir le nombre d'éléments différents stockés
1837
 * @return string|int|array|null|void
1838
 *
1839
 *      - string : Donnée si c'est la première fois qu'elle est vue
1840
 *      - void : si la donnée a déjà été vue
1841
 *      - int : si l'on demande le nombre d'éléments
1842
 *      - array (interne) : si on dépile
1843
 *      - null (interne) : si on empile
1844
 **/
1845
function unique($donnee, $famille = '', $cpt = false) {
1846
	static $mem = array();
1847
	// permettre de vider la pile et de la restaurer
1848
	// pour le calcul de introduction...
1849
	if ($famille == '_spip_raz_') {
1850
		$tmp = $mem;
1851
		$mem = array();
1852
1853
		return $tmp;
1854
	} elseif ($famille == '_spip_set_') {
1855
		$mem = $donnee;
1856
1857
		return;
1858
	}
1859
	// eviter une notice
1860
	if (!isset($mem[$famille])) {
1861
		$mem[$famille] = array();
1862
	}
1863
	if ($cpt) {
1864
		return count($mem[$famille]);
1865
	}
1866
	// eviter une notice
1867
	if (!isset($mem[$famille][$donnee])) {
1868
		$mem[$famille][$donnee] = 0;
1869
	}
1870
	if (!($mem[$famille][$donnee]++)) {
1871
		return $donnee;
1872
	}
1873
}
1874
1875
1876
/**
1877
 * Filtre qui alterne des valeurs en fonction d'un compteur
1878
 *
1879
 * Affiche à tour de rôle et dans l'ordre, un des arguments transmis
1880
 * à chaque incrément du compteur.
1881
 *
1882
 * S'il n'y a qu'un seul argument, et que c'est un tableau,
1883
 * l'alternance se fait sur les valeurs du tableau.
1884
 *
1885
 * Souvent appliqué à l'intérieur d'une boucle, avec le compteur `#COMPTEUR_BOUCLE`
1886
 *
1887
 * @example
1888
 *     - `[(#COMPTEUR_BOUCLE|alterner{bleu,vert,rouge})]`
1889
 *     - `[(#COMPTEUR_BOUCLE|alterner{#LISTE{bleu,vert,rouge}})]`
1890
 *
1891
 * @filtre
1892
 * @link https://www.spip.net/4145
1893
 *
1894
 * @param int $i
1895
 *     Le compteur
1896
 * @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...
1897
 *     Liste des éléments à alterner
1898
 * @return mixed
1899
 *     Une des valeurs en fonction du compteur.
1900
 **/
1901
function alterner($i, ...$args) {
1902
	// recuperer les arguments (attention fonctions un peu space)
1903
	$num = count($args);
1904
1905
	if ($num === 1 && is_array($args[0])) {
1906
		$args = $args[0];
1907
		$num = count($args);
1908
	}
1909
1910
	// renvoyer le i-ieme argument, modulo le nombre d'arguments
1911
	return $args[(intval($i) - 1) % $num];
1912
}
1913
1914
1915
/**
1916
 * Récupérer un attribut d'une balise HTML
1917
 *
1918
 * la regexp est mortelle : cf. `tests/unit/filtres/extraire_attribut.php`
1919
 * Si on a passé un tableau de balises, renvoyer un tableau de résultats
1920
 * (dans ce cas l'option `$complet` n'est pas disponible)
1921
 *
1922
 * @param string|array $balise
1923
 *     Texte ou liste de textes dont on veut extraire des balises
1924
 * @param string $attribut
1925
 *     Nom de l'attribut désiré
1926
 * @param bool $complet
1927
 *     True pour retourner un tableau avec
1928
 *     - le texte de la balise
1929
 *     - l'ensemble des résultats de la regexp ($r)
1930
 * @return string|array
1931
 *     - Texte de l'attribut retourné, ou tableau des texte d'attributs
1932
 *       (si 1er argument tableau)
1933
 *     - Tableau complet (si 2e argument)
1934
 **/
1935
function extraire_attribut($balise, $attribut, $complet = false) {
1936
	if (is_array($balise)) {
1937
		array_walk(
1938
			$balise,
1939
			function(&$a, $key, $t){
1940
				$a = extraire_attribut($a, $t);
1941
			},
1942
			$attribut
1943
		);
1944
1945
		return $balise;
1946
	}
1947
	if (preg_match(
1948
		',(^.*?<(?:(?>\s*)(?>[\w:.-]+)(?>(?:=(?:"[^"]*"|\'[^\']*\'|[^\'"]\S*))?))*?)(\s+'
1949
		. $attribut
1950
		. '(?:=\s*("[^"]*"|\'[^\']*\'|[^\'"]\S*))?)()((?:[\s/][^>]*)?>.*),isS',
1951
1952
		$balise, $r)) {
1953
		if (isset($r[3][0]) and ($r[3][0] == '"' || $r[3][0] == "'")) {
1954
			$r[4] = substr($r[3], 1, -1);
1955
			$r[3] = $r[3][0];
1956
		} elseif ($r[3] !== '') {
1957
			$r[4] = $r[3];
1958
			$r[3] = '';
1959
		} else {
1960
			$r[4] = trim($r[2]);
1961
		}
1962
		$att = $r[4];
1963 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...
1964
			$att = str_replace(array("&#039;", "&#39;", "&#034;", "&#34;"), array("'", "'", '"', '"'), $att);
1965
		}
1966
		$att = filtrer_entites($att);
1967
	} else {
1968
		$att = null;
1969
	}
1970
1971
	if ($complet) {
1972
		return array($att, $r);
1973
	} else {
1974
		return $att;
1975
	}
1976
}
1977
1978
/**
1979
 * Insérer (ou modifier) un attribut html dans une balise
1980
 *
1981
 * @example
1982
 *     - `[(#LOGO_ARTICLE|inserer_attribut{class, logo article})]`
1983
 *     - `[(#LOGO_ARTICLE|inserer_attribut{alt, #TTTRE|attribut_html|couper{60}})]`
1984
 *     - `[(#FICHIER|image_reduire{40}|inserer_attribut{data-description, #DESCRIPTIF})]`
1985
 *       Laissera les balises HTML de la valeur (ici `#DESCRIPTIF`) si on n'applique pas le
1986
 *       filtre `attribut_html` dessus.
1987
 *
1988
 * @filtre
1989
 * @link https://www.spip.net/4294
1990
 * @uses attribut_html()
1991
 * @uses extraire_attribut()
1992
 *
1993
 * @param string $balise
1994
 *     Code html de la balise (ou contenant une balise)
1995
 * @param string $attribut
1996
 *     Nom de l'attribut html à modifier
1997
 * @param string $val
1998
 *     Valeur de l'attribut à appliquer
1999
 * @param bool $proteger
2000
 *     Prépare la valeur en tant qu'attribut de balise (mais conserve les balises html).
2001
 * @param bool $vider
2002
 *     True pour vider l'attribut. Une chaîne vide pour `$val` fera pareil.
2003
 * @return string
2004
 *     Code html modifié
2005
 **/
2006
function inserer_attribut($balise, $attribut, $val, $proteger = true, $vider = false) {
2007
	// preparer l'attribut
2008
	// supprimer les &nbsp; etc mais pas les balises html
2009
	// qui ont un sens dans un attribut value d'un input
2010
	if ($proteger) {
2011
		$val = attribut_html($val, false);
2012
	}
2013
2014
	// echapper les ' pour eviter tout bug
2015
	$val = str_replace("'", "&#039;", $val);
2016
	if ($vider and strlen($val) == 0) {
2017
		$insert = '';
2018
	} else {
2019
		$insert = " $attribut='$val'";
2020
	}
2021
2022
	list($old, $r) = extraire_attribut($balise, $attribut, true);
2023
2024
	if ($old !== null) {
2025
		// Remplacer l'ancien attribut du meme nom
2026
		$balise = $r[1] . $insert . $r[5];
2027
	} else {
2028
		// preferer une balise " />" (comme <img />)
2029
		if (preg_match(',/>,', $balise)) {
2030
			$balise = preg_replace(",\s?/>,S", $insert . " />", $balise, 1);
2031
		} // sinon une balise <a ...> ... </a>
2032
		else {
2033
			$balise = preg_replace(",\s?>,S", $insert . ">", $balise, 1);
2034
		}
2035
	}
2036
2037
	return $balise;
2038
}
2039
2040
/**
2041
 * Supprime un attribut HTML
2042
 *
2043
 * @example `[(#LOGO_ARTICLE|vider_attribut{class})]`
2044
 *
2045
 * @filtre
2046
 * @link https://www.spip.net/4142
2047
 * @uses inserer_attribut()
2048
 * @see  extraire_attribut()
2049
 *
2050
 * @param string $balise Code HTML de l'élément
2051
 * @param string $attribut Nom de l'attribut à enlever
2052
 * @return string Code HTML sans l'attribut
2053
 **/
2054
function vider_attribut($balise, $attribut) {
2055
	return inserer_attribut($balise, $attribut, '', false, true);
2056
}
2057
2058
/**
2059
 * Fonction support pour les filtres |ajouter_class |supprimer_class |commuter_class
2060
 *
2061
 * @param string $balise
2062
 * @param string|array $class
2063
 * @param string $operation
2064
 * @return string
2065
 */
2066
function modifier_class($balise, $class, $operation='ajouter') {
2067
	if (is_string($class)) {
2068
		$class = explode(' ', trim($class));
2069
	}
2070
	$class = array_filter($class);
2071
2072
	// si la ou les classes ont des caracteres invalides on ne fait rien
2073
	if (preg_match(",[^\w-],", implode('', $class))) {
2074
		return $balise;
2075
	}
2076
2077
	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...
2078
		$class = array_unique($class);
2079
		$class_courante = extraire_attribut($balise, 'class');
2080
2081
		$class_new = $class_courante;
2082
		foreach ($class as $c) {
2083
			if ($c) {
2084
				$is_class_presente = false;
2085
				if (strpos($class_courante, $c) !== false
2086
					and preg_match("/(^|\s)".preg_quote($c)."($|\s)/", $class_courante)) {
2087
					$is_class_presente = true;
2088
				}
2089
				if (in_array($operation, ['ajouter', 'commuter'])
2090
					and !$is_class_presente) {
2091
					$class_new = rtrim($class_new) . " " . $c;
2092
				}
2093
				elseif (in_array($operation, ['supprimer', 'commuter'])
2094
					and $is_class_presente) {
2095
					$class_new = trim(preg_replace("/(^|\s)".preg_quote($c)."($|\s)/", "\\1", $class_new));
2096
				}
2097
			}
2098
		}
2099
2100
		if ($class_new !== $class_courante) {
2101
			if (strlen($class_new)) {
2102
				$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 2081 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...
2103
			}
2104
			elseif ($class_courante) {
2105
				$balise = vider_attribut($balise, 'class');
2106
			}
2107
		}
2108
	}
2109
2110
	return $balise;
2111
}
2112
2113
/**
2114
 * Ajoute une ou plusieurs classes sur une balise (si pas deja presentes)
2115
 * @param string $balise
2116
 * @param string|array $class
2117
 * @return string
2118
 */
2119
function ajouter_class($balise, $class){
2120
	return modifier_class($balise, $class, 'ajouter');
2121
}
2122
2123
/**
2124
 * Supprime une ou plusieurs classes sur une balise (si presentes)
2125
 * @param string $balise
2126
 * @param string|array $class
2127
 * @return string
2128
 */
2129
function supprimer_class($balise, $class){
2130
	return modifier_class($balise, $class, 'supprimer');
2131
}
2132
2133
/**
2134
 * Bascule une ou plusieurs classes sur une balise : ajoutees si absentes, supprimees si presentes
2135
 *
2136
 * @param string $balise
2137
 * @param string|array $class
2138
 * @return string
2139
 */
2140
function commuter_class($balise, $class){
2141
	return modifier_class($balise, $class, 'commuter');
2142
}
2143
2144
/**
2145
 * Un filtre pour déterminer le nom du statut des inscrits
2146
 *
2147
 * @param void|int $id
2148
 * @param string $mode
2149
 * @return string
2150
 */
2151
function tester_config($id, $mode = '') {
2152
	include_spip('action/inscrire_auteur');
2153
2154
	return tester_statut_inscription($mode, $id);
2155
}
2156
2157
//
2158
// Quelques fonctions de calcul arithmetique
2159
//
2160
function floatstr($a) { return str_replace(',','.',(string)floatval($a)); }
2161
function strize($f, $a, $b) { return floatstr($f(floatstr($a),floatstr($b))); }
2162
2163
/**
2164
 * Additionne 2 nombres
2165
 *
2166
 * @filtre
2167
 * @link https://www.spip.net/4307
2168
 * @see moins()
2169
 * @example
2170
 *     ```
2171
 *     [(#VAL{28}|plus{14})]
2172
 *     ```
2173
 *
2174
 * @param int $a
2175
 * @param int $b
2176
 * @return int $a+$b
2177
 **/
2178
function plus($a, $b) {
2179
	return $a + $b;
2180
}
2181
function strplus($a, $b) {return strize('plus', $a, $b);}
2182
/**
2183
 * Soustrait 2 nombres
2184
 *
2185
 * @filtre
2186
 * @link https://www.spip.net/4302
2187
 * @see plus()
2188
 * @example
2189
 *     ```
2190
 *     [(#VAL{28}|moins{14})]
2191
 *     ```
2192
 *
2193
 * @param int $a
2194
 * @param int $b
2195
 * @return int $a-$b
2196
 **/
2197
function moins($a, $b) {
2198
	return $a - $b;
2199
}
2200
function strmoins($a, $b) {return strize('moins', $a, $b);}
2201
2202
/**
2203
 * Multiplie 2 nombres
2204
 *
2205
 * @filtre
2206
 * @link https://www.spip.net/4304
2207
 * @see div()
2208
 * @see modulo()
2209
 * @example
2210
 *     ```
2211
 *     [(#VAL{28}|mult{14})]
2212
 *     ```
2213
 *
2214
 * @param int $a
2215
 * @param int $b
2216
 * @return int $a*$b
2217
 **/
2218
function mult($a, $b) {
2219
	return $a * $b;
2220
}
2221
function strmult($a, $b) {return strize('mult', $a, $b);}
2222
2223
/**
2224
 * Divise 2 nombres
2225
 *
2226
 * @filtre
2227
 * @link https://www.spip.net/4279
2228
 * @see mult()
2229
 * @see modulo()
2230
 * @example
2231
 *     ```
2232
 *     [(#VAL{28}|div{14})]
2233
 *     ```
2234
 *
2235
 * @param int $a
2236
 * @param int $b
2237
 * @return int $a/$b (ou 0 si $b est nul)
2238
 **/
2239
function div($a, $b) {
2240
	return $b ? $a / $b : 0;
2241
}
2242
function strdiv($a, $b) {return strize('div', $a, $b);}
2243
2244
/**
2245
 * Retourne le modulo 2 nombres
2246
 *
2247
 * @filtre
2248
 * @link https://www.spip.net/4301
2249
 * @see mult()
2250
 * @see div()
2251
 * @example
2252
 *     ```
2253
 *     [(#VAL{28}|modulo{14})]
2254
 *     ```
2255
 *
2256
 * @param int $nb
2257
 * @param int $mod
2258
 * @param int $add
2259
 * @return int ($nb % $mod) + $add
2260
 **/
2261
function modulo($nb, $mod, $add = 0) {
2262
	return ($mod ? $nb % $mod : 0) + $add;
2263
}
2264
2265
2266
/**
2267
 * Vérifie qu'un nom (d'auteur) ne comporte pas d'autres tags que <multi>
2268
 * et ceux volontairement spécifiés dans la constante
2269
 *
2270
 * @param string $nom
2271
 *      Nom (signature) proposé
2272
 * @return bool
2273
 *      - false si pas conforme,
2274
 *      - true sinon
2275
 **/
2276
function nom_acceptable($nom) {
2277
	if (!is_string($nom)) {
2278
		return false;
2279
	}
2280
	if (!defined('_TAGS_NOM_AUTEUR')) {
2281
		define('_TAGS_NOM_AUTEUR', '');
2282
	}
2283
	$tags_acceptes = array_unique(explode(',', 'multi,' . _TAGS_NOM_AUTEUR));
2284
	foreach ($tags_acceptes as $tag) {
2285
		if (strlen($tag)) {
2286
			$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...
2287
			$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...
2288
			$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...
2289
			$remp2[] = '\x60/' . trim($tag) . '\x61';
2290
		}
2291
	}
2292
	$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...
2293
2294
	return str_replace('&lt;', '<', $v_nom) == $nom;
2295
}
2296
2297
2298
/**
2299
 * Vérifier la conformité d'une ou plusieurs adresses email (suivant RFC 822)
2300
 *
2301
 * @param string|array $adresses
2302
 *      Adresse ou liste d'adresse
2303
 *      si on fournit un tableau, il est filtre et la fonction renvoie avec uniquement les adresses email valides (donc possiblement vide)
2304
 * @return bool|string|array
2305
 *      - false si une des adresses n'est pas conforme,
2306
 *      - la normalisation de la dernière adresse donnée sinon
2307
 *      - renvoie un tableau si l'entree est un tableau
2308
 **/
2309
function email_valide($adresses) {
2310
	if (is_array($adresses)) {
2311
		$adresses = array_map('email_valide', $adresses);
2312
		$adresses = array_filter($adresses);
2313
		return $adresses;
2314
	}
2315
2316
	$email_valide = charger_fonction('email_valide', 'inc');
2317
	return $email_valide($adresses);
2318
}
2319
2320
/**
2321
 * Permet d'afficher un symbole à côté des liens pointant vers les
2322
 * documents attachés d'un article (liens ayant `rel=enclosure`).
2323
 *
2324
 * @filtre
2325
 * @link https://www.spip.net/4134
2326
 *
2327
 * @param string $tags Texte
2328
 * @return string Texte
2329
 **/
2330
function afficher_enclosures($tags) {
2331
	$s = array();
2332
	foreach (extraire_balises($tags, 'a') as $tag) {
2333
		if (extraire_attribut($tag, 'rel') == 'enclosure'
2334
			and $t = extraire_attribut($tag, 'href')
2335
		) {
2336
			$s[] = preg_replace(',>[^<]+</a>,S',
2337
				'>'
2338
				. 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 2334 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...
2339
					'title="' . attribut_html($t) . '"')
0 ignored issues
show
Bug introduced by
It seems like $t defined by extraire_attribut($tag, 'href') on line 2334 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...
2340
				. '</a>', $tag);
2341
		}
2342
	}
2343
2344
	return join('&nbsp;', $s);
2345
}
2346
2347
/**
2348
 * Filtre des liens HTML `<a>` selon la valeur de leur attribut `rel`
2349
 * et ne retourne que ceux là.
2350
 *
2351
 * @filtre
2352
 * @link https://www.spip.net/4187
2353
 *
2354
 * @param string $tags Texte
2355
 * @param string $rels Attribut `rel` à capturer (ou plusieurs séparés par des virgules)
2356
 * @return string Liens trouvés
2357
 **/
2358
function afficher_tags($tags, $rels = 'tag,directory') {
2359
	$s = array();
2360
	foreach (extraire_balises($tags, 'a') as $tag) {
2361
		$rel = extraire_attribut($tag, 'rel');
2362
		if (strstr(",$rels,", ",$rel,")) {
2363
			$s[] = $tag;
2364
		}
2365
	}
2366
2367
	return join(', ', $s);
2368
}
2369
2370
2371
/**
2372
 * Convertir les médias fournis par un flux RSS (podcasts)
2373
 * en liens conformes aux microformats
2374
 *
2375
 * Passe un `<enclosure url="fichier" length="5588242" type="audio/mpeg"/>`
2376
 * au format microformat `<a rel="enclosure" href="fichier" ...>fichier</a>`.
2377
 *
2378
 * Peut recevoir un `<link` ou un `<media:content` parfois.
2379
 *
2380
 * Attention : `length="zz"` devient `title="zz"`, pour rester conforme.
2381
 *
2382
 * @filtre
2383
 * @see microformat2enclosure() Pour l'inverse
2384
 *
2385
 * @param string $e Tag RSS `<enclosure>`
2386
 * @return string Tag HTML `<a>` avec microformat.
2387
 **/
2388
function enclosure2microformat($e) {
2389
	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...
2390
		$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...
2391
	}
2392
	$type = extraire_attribut($e, 'type');
2393
	if (!$length = extraire_attribut($e, 'length')) {
2394
		# <media:content : longeur dans fileSize. On tente.
2395
		$length = extraire_attribut($e, 'fileSize');
2396
	}
2397
	$fichier = basename($url);
2398
2399
	return '<a rel="enclosure"'
2400
	. ($url ? ' href="' . spip_htmlspecialchars($url) . '"' : '')
2401
	. ($type ? ' type="' . spip_htmlspecialchars($type) . '"' : '')
0 ignored issues
show
Bug introduced by
It seems like $type defined by extraire_attribut($e, 'type') on line 2392 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
	. ($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...
2403
	. '>' . $fichier . '</a>';
2404
}
2405
2406
/**
2407
 * Convertir les liens conformes aux microformats en médias pour flux RSS,
2408
 * par exemple pour les podcasts
2409
 *
2410
 * Passe un texte ayant des liens avec microformat
2411
 * `<a rel="enclosure" href="fichier" ...>fichier</a>`
2412
 * au format RSS `<enclosure url="fichier" ... />`.
2413
 *
2414
 * @filtre
2415
 * @see enclosure2microformat() Pour l'inverse
2416
 *
2417
 * @param string $tags Texte HTML ayant des tag `<a>` avec microformat
2418
 * @return string Tags RSS `<enclosure>`.
2419
 **/
2420
function microformat2enclosure($tags) {
2421
	$enclosures = array();
2422
	foreach (extraire_balises($tags, 'a') as $e) {
2423
		if (extraire_attribut($e, 'rel') == 'enclosure') {
2424
			$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...
2425
			$type = extraire_attribut($e, 'type');
2426
			if (!$length = intval(extraire_attribut($e, 'title'))) {
2427
				$length = intval(extraire_attribut($e, 'length'));
2428
			} # vieux data
2429
			$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...
2430
			$enclosures[] = '<enclosure'
2431
				. ($url ? ' url="' . spip_htmlspecialchars($url) . '"' : '')
2432
				. ($type ? ' type="' . spip_htmlspecialchars($type) . '"' : '')
0 ignored issues
show
Bug introduced by
It seems like $type defined by extraire_attribut($e, 'type') on line 2425 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...
2433
				. ($length ? ' length="' . $length . '"' : '')
2434
				. ' />';
2435
		}
2436
	}
2437
2438
	return join("\n", $enclosures);
2439
}
2440
2441
2442
/**
2443
 * Créer les éléments ATOM `<dc:subject>` à partir des tags
2444
 *
2445
 * Convertit les liens avec attribut `rel="tag"`
2446
 * en balise `<dc:subject></dc:subject>` pour les flux RSS au format Atom.
2447
 *
2448
 * @filtre
2449
 *
2450
 * @param string $tags Texte
2451
 * @return string Tags RSS Atom `<dc:subject>`.
2452
 **/
2453
function tags2dcsubject($tags) {
2454
	$subjects = '';
2455
	foreach (extraire_balises($tags, 'a') as $e) {
2456
		if (extraire_attribut($e, 'rel') == 'tag') {
2457
			$subjects .= '<dc:subject>'
2458
				. texte_backend(textebrut($e))
2459
				. '</dc:subject>' . "\n";
2460
		}
2461
	}
2462
2463
	return $subjects;
2464
}
2465
2466
/**
2467
 * Retourne la premiere balise html du type demandé
2468
 *
2469
 * Retourne le contenu d'une balise jusqu'à la première fermeture rencontrée
2470
 * du même type.
2471
 * Si on a passe un tableau de textes, retourne un tableau de resultats.
2472
 *
2473
 * @example `[(#DESCRIPTIF|extraire_balise{img})]`
2474
 *
2475
 * @filtre
2476
 * @link https://www.spip.net/4289
2477
 * @see extraire_balises()
2478
 * @note
2479
 *     Attention : les résultats peuvent être incohérents sur des balises imbricables,
2480
 *     tel que demander à extraire `div` dans le texte `<div> un <div> mot </div> absent </div>`,
2481
 *     ce qui retournerait `<div> un <div> mot </div>` donc.
2482
 *
2483
 * @param string|array $texte
2484
 *     Texte(s) dont on souhaite extraire une balise html
2485
 * @param string $tag
2486
 *     Nom de la balise html à extraire
2487
 * @return void|string|array
2488
 *     - Code html de la balise, sinon rien
2489
 *     - Tableau de résultats, si tableau en entrée.
2490
 **/
2491 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...
2492
	if (is_array($texte)) {
2493
		array_walk(
2494
			$texte,
2495
			function(&$a, $key, $t){
2496
				$a = extraire_balise($a, $t);
2497
			},
2498
			$tag
2499
		);
2500
2501
		return $texte;
2502
	}
2503
2504
	if (preg_match(
2505
		",<$tag\b[^>]*(/>|>.*</$tag\b[^>]*>|>),UimsS",
2506
		$texte, $regs)) {
2507
		return $regs[0];
2508
	}
2509
}
2510
2511
/**
2512
 * Extrait toutes les balises html du type demandé
2513
 *
2514
 * Retourne dans un tableau le contenu de chaque balise jusqu'à la première
2515
 * fermeture rencontrée du même type.
2516
 * Si on a passe un tableau de textes, retourne un tableau de resultats.
2517
 *
2518
 * @example `[(#TEXTE|extraire_balises{img}|implode{" - "})]`
2519
 *
2520
 * @filtre
2521
 * @link https://www.spip.net/5618
2522
 * @see extraire_balise()
2523
 * @note
2524
 *     Attention : les résultats peuvent être incohérents sur des balises imbricables,
2525
 *     tel que demander à extraire `div` dans un texte.
2526
 *
2527
 * @param string|array $texte
2528
 *     Texte(s) dont on souhaite extraire une balise html
2529
 * @param string $tag
2530
 *     Nom de la balise html à extraire
2531
 * @return array
2532
 *     - Liste des codes html des occurrences de la balise, sinon tableau vide
2533
 *     - Tableau de résultats, si tableau en entrée.
2534
 **/
2535 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...
2536
	if (is_array($texte)) {
2537
		array_walk(
2538
			$texte,
2539
			function(&$a, $key, $t){
2540
				$a = extraire_balises($a, $t);
2541
			},
2542
			$tag
2543
		);
2544
2545
		return $texte;
2546
	}
2547
2548
	if (preg_match_all(
2549
		",<${tag}\b[^>]*(/>|>.*</${tag}\b[^>]*>|>),UimsS",
2550
		$texte, $regs, PREG_PATTERN_ORDER)) {
2551
		return $regs[0];
2552
	} else {
2553
		return array();
2554
	}
2555
}
2556
2557
/**
2558
 * Indique si le premier argument est contenu dans le second
2559
 *
2560
 * Cette fonction est proche de `in_array()` en PHP avec comme principale
2561
 * différence qu'elle ne crée pas d'erreur si le second argument n'est pas
2562
 * un tableau (dans ce cas elle tentera de le désérialiser, et sinon retournera
2563
 * la valeur par défaut transmise).
2564
 *
2565
 * @example `[(#VAL{deux}|in_any{#LISTE{un,deux,trois}}|oui) ... ]`
2566
 *
2567
 * @filtre
2568
 * @see filtre_find() Assez proche, avec les arguments valeur et tableau inversés.
2569
 *
2570
 * @param string $val
2571
 *     Valeur à chercher dans le tableau
2572
 * @param array|string $vals
2573
 *     Tableau des valeurs. S'il ce n'est pas un tableau qui est transmis,
2574
 *     la fonction tente de la désérialiser.
2575
 * @param string $def
2576
 *     Valeur par défaut retournée si `$vals` n'est pas un tableau.
2577
 * @return string
2578
 *     - ' ' si la valeur cherchée est dans le tableau
2579
 *     - '' si la valeur n'est pas dans le tableau
2580
 *     - `$def` si on n'a pas transmis de tableau
2581
 **/
2582
function in_any($val, $vals, $def = '') {
2583
	if (!is_array($vals) and $v = unserialize($vals)) {
2584
		$vals = $v;
2585
	}
2586
2587
	return (!is_array($vals) ? $def : (in_array($val, $vals) ? ' ' : ''));
2588
}
2589
2590
2591
/**
2592
 * Retourne le résultat d'une expression mathématique simple
2593
 *
2594
 * N'accepte que les *, + et - (à ameliorer si on l'utilise vraiment).
2595
 *
2596
 * @filtre
2597
 * @example
2598
 *      ```
2599
 *      valeur_numerique("3*2") retourne 6
2600
 *      ```
2601
 *
2602
 * @param string $expr
2603
 *     Expression mathématique `nombre operateur nombre` comme `3*2`
2604
 * @return int
2605
 *     Résultat du calcul
2606
 **/
2607
function valeur_numerique($expr) {
2608
	$a = 0;
2609
	if (preg_match(',^[0-9]+(\s*[+*-]\s*[0-9]+)*$,S', trim($expr))) {
2610
		eval("\$a = $expr;");
2611
	}
2612
2613
	return intval($a);
2614
}
2615
2616
/**
2617
 * Retourne un calcul de règle de trois
2618
 *
2619
 * @filtre
2620
 * @example
2621
 *     ```
2622
 *     [(#VAL{6}|regledetrois{4,3})] retourne 8
2623
 *     ```
2624
 *
2625
 * @param int $a
2626
 * @param int $b
2627
 * @param int $c
2628
 * @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...
2629
 *      Retourne `$a*$b/$c`
2630
 **/
2631
function regledetrois($a, $b, $c) {
2632
	return round($a * $b / $c);
2633
}
2634
2635
2636
/**
2637
 * Crée des tags HTML input hidden pour chaque paramètre et valeur d'une URL
2638
 *
2639
 * Fournit la suite de Input-Hidden correspondant aux paramètres de
2640
 * l'URL donnée en argument, compatible avec les types_urls
2641
 *
2642
 * @filtre
2643
 * @link https://www.spip.net/4286
2644
 * @see balise_ACTION_FORMULAIRE()
2645
 *     Également pour transmettre les actions à un formulaire
2646
 * @example
2647
 *     ```
2648
 *     [(#ENV{action}|form_hidden)] dans un formulaire
2649
 *     ```
2650
 *
2651
 * @param string $action URL
2652
 * @return string Suite de champs input hidden
2653
 **/
2654
function form_hidden($action) {
2655
2656
	$contexte = array();
2657
	include_spip('inc/urls');
2658
	if ($p = urls_decoder_url($action, '')
2659
		and reset($p)
2660
	) {
2661
		$fond = array_shift($p);
2662
		if ($fond != '404') {
2663
			$contexte = array_shift($p);
2664
			$contexte['page'] = $fond;
2665
			$action = preg_replace('/([?]' . preg_quote($fond) . '[^&=]*[0-9]+)(&|$)/', '?&', $action);
2666
		}
2667
	}
2668
	// defaire ce qu'a injecte urls_decoder_url : a revoir en modifiant la signature de urls_decoder_url
2669
	if (defined('_DEFINIR_CONTEXTE_TYPE') and _DEFINIR_CONTEXTE_TYPE) {
2670
		unset($contexte['type']);
2671
	}
2672
	if (defined('_DEFINIR_CONTEXTE_TYPE_PAGE') and _DEFINIR_CONTEXTE_TYPE_PAGE) {
2673
		unset($contexte['type-page']);
2674
	}
2675
2676
	// on va remplir un tableau de valeurs en prenant bien soin de ne pas
2677
	// ecraser les elements de la forme mots[]=1&mots[]=2
2678
	$values = array();
2679
2680
	// d'abord avec celles de l'url
2681
	if (false !== ($p = strpos($action, '?'))) {
2682
		foreach (preg_split('/&(amp;)?/S', substr($action, $p + 1)) as $c) {
2683
			$c = explode('=', $c, 2);
2684
			$var = array_shift($c);
2685
			$val = array_shift($c);
2686
			if ($var) {
2687
				$val = rawurldecode($val);
2688
				$var = rawurldecode($var); // decoder les [] eventuels
2689 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...
2690
					$values[] = array($var, $val);
2691
				} else {
2692
					if (!isset($values[$var])) {
2693
						$values[$var] = array($var, $val);
2694
					}
2695
				}
2696
			}
2697
		}
2698
	}
2699
2700
	// ensuite avec celles du contexte, sans doublonner !
2701
	foreach ($contexte as $var => $val) {
2702 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...
2703
			$values[] = array($var, $val);
2704
		} else {
2705
			if (!isset($values[$var])) {
2706
				$values[$var] = array($var, $val);
2707
			}
2708
		}
2709
	}
2710
2711
	// puis on rassemble le tout
2712
	$hidden = array();
2713
	foreach ($values as $value) {
2714
		list($var, $val) = $value;
2715
		$hidden[] = '<input name="'
2716
			. entites_html($var)
2717
			. '"'
2718
			. (is_null($val)
2719
				? ''
2720
				: ' value="' . entites_html($val) . '"'
2721
			)
2722
			. ' type="hidden"' . "\n/>";
2723
	}
2724
2725
	return join("", $hidden);
2726
}
2727
2728
2729
/**
2730
 * Retourne la première valeur d'un tableau
2731
 *
2732
 * Plus précisément déplace le pointeur du tableau sur la première valeur et la retourne.
2733
 *
2734
 * @example `[(#LISTE{un,deux,trois}|reset)]` retourne 'un'
2735
 *
2736
 * @filtre
2737
 * @link http://php.net/manual/fr/function.reset.php
2738
 * @see filtre_end()
2739
 *
2740
 * @param array $array
2741
 * @return mixed|null|false
2742
 *    - null si $array n'est pas un tableau,
2743
 *    - false si le tableau est vide
2744
 *    - la première valeur du tableau sinon.
2745
 **/
2746
function filtre_reset($array) {
2747
	return !is_array($array) ? null : reset($array);
2748
}
2749
2750
/**
2751
 * Retourne la dernière valeur d'un tableau
2752
 *
2753
 * Plus précisément déplace le pointeur du tableau sur la dernière valeur et la retourne.
2754
 *
2755
 * @example `[(#LISTE{un,deux,trois}|end)]` retourne 'trois'
2756
 *
2757
 * @filtre
2758
 * @link http://php.net/manual/fr/function.end.php
2759
 * @see filtre_reset()
2760
 *
2761
 * @param array $array
2762
 * @return mixed|null|false
2763
 *    - null si $array n'est pas un tableau,
2764
 *    - false si le tableau est vide
2765
 *    - la dernière valeur du tableau sinon.
2766
 **/
2767
function filtre_end($array) {
2768
	return !is_array($array) ? null : end($array);
2769
}
2770
2771
/**
2772
 * Empile une valeur à la fin d'un tableau
2773
 *
2774
 * @example `[(#LISTE{un,deux,trois}|push{quatre}|print)]`
2775
 *
2776
 * @filtre
2777
 * @link https://www.spip.net/4571
2778
 * @link http://php.net/manual/fr/function.array-push.php
2779
 *
2780
 * @param array $array
2781
 * @param mixed $val
2782
 * @return array|string
2783
 *     - '' si $array n'est pas un tableau ou si echec.
2784
 *     - le tableau complété de la valeur sinon.
2785
 *
2786
 **/
2787
function filtre_push($array, $val) {
2788
	if (!is_array($array) or !array_push($array, $val)) {
2789
		return '';
2790
	}
2791
2792
	return $array;
2793
}
2794
2795
/**
2796
 * Indique si une valeur est contenue dans un tableau
2797
 *
2798
 * @example `[(#LISTE{un,deux,trois}|find{quatre}|oui) ... ]`
2799
 *
2800
 * @filtre
2801
 * @link https://www.spip.net/4575
2802
 * @see in_any() Assez proche, avec les paramètres tableau et valeur inversés.
2803
 *
2804
 * @param array $array
2805
 * @param mixed $val
2806
 * @return bool
2807
 *     - `false` si `$array` n'est pas un tableau
2808
 *     - `true` si la valeur existe dans le tableau, `false` sinon.
2809
 **/
2810
function filtre_find($array, $val) {
2811
	return (is_array($array) and in_array($val, $array));
2812
}
2813
2814
2815
/**
2816
 * Passer les url relatives à la css d'origine en url absolues
2817
 *
2818
 * @uses suivre_lien()
2819
 *
2820
 * @param string $contenu
2821
 *     Contenu du fichier CSS
2822
 * @param string $source
2823
 *     Chemin du fichier CSS
2824
 * @return string
2825
 *     Contenu avec urls en absolus
2826
 **/
2827
function urls_absolues_css($contenu, $source) {
2828
	$path = suivre_lien(url_absolue($source), './');
2829
2830
	return preg_replace_callback(
2831
		",url\s*\(\s*['\"]?([^'\"/#\s][^:]*)['\"]?\s*\),Uims",
2832
		function($x) use ($path) {
2833
			return "url('" . suivre_lien($path, $x[1]) . "')";
2834
		},
2835
		$contenu
2836
	);
2837
}
2838
2839
2840
/**
2841
 * Inverse le code CSS (left <--> right) d'une feuille de style CSS
2842
 *
2843
 * Récupère le chemin d'une CSS existante et :
2844
 *
2845
 * 1. regarde si une CSS inversée droite-gauche existe dans le meme répertoire
2846
 * 2. sinon la crée (ou la recrée) dans `_DIR_VAR/cache_css/`
2847
 *
2848
 * Si on lui donne à manger une feuille nommée `*_rtl.css` il va faire l'inverse.
2849
 *
2850
 * @filtre
2851
 * @example
2852
 *     ```
2853
 *     [<link rel="stylesheet" href="(#CHEMIN{css/perso.css}|direction_css)" type="text/css" />]
2854
 *     ```
2855
 * @param string $css
2856
 *     Chemin vers le fichier CSS
2857
 * @param string $voulue
2858
 *     Permet de forcer le sens voulu (en indiquant `ltr`, `rtl` ou un
2859
 *     code de langue). En absence, prend le sens de la langue en cours.
2860
 *
2861
 * @return string
2862
 *     Chemin du fichier CSS inversé
2863
 **/
2864
function direction_css($css, $voulue = '') {
2865
	if (!preg_match(',(_rtl)?\.css$,i', $css, $r)) {
2866
		return $css;
2867
	}
2868
	include_spip("inc/lang");
2869
	// si on a precise le sens voulu en argument, le prendre en compte
2870
	if ($voulue = strtolower($voulue)) {
2871
		if ($voulue != 'rtl' and $voulue != 'ltr') {
2872
			$voulue = lang_dir($voulue);
2873
		}
2874
	} else {
2875
		$voulue = lang_dir();
2876
	}
2877
2878
	$r = count($r) > 1;
2879
	$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...
2880
	$dir = $r ? 'rtl' : 'ltr';
2881
	$ndir = $r ? 'ltr' : 'rtl';
2882
2883
	if ($voulue == $dir) {
2884
		return $css;
2885
	}
2886
2887
	if (
2888
		// url absolue
2889
		preg_match(",^https?:,i", $css)
2890
		// ou qui contient un ?
2891
		or (($p = strpos($css, '?')) !== false)
2892
	) {
2893
		$distant = true;
2894
		$cssf = parse_url($css);
2895
		$cssf = $cssf['path'] . ($cssf['query'] ? "?" . $cssf['query'] : "");
2896
		$cssf = preg_replace(',[?:&=],', "_", $cssf);
2897
	} else {
2898
		$distant = false;
2899
		$cssf = $css;
2900
		// 1. regarder d'abord si un fichier avec la bonne direction n'est pas aussi
2901
		//propose (rien a faire dans ce cas)
2902
		$f = preg_replace(',(_rtl)?\.css$,i', '_' . $ndir . '.css', $css);
2903
		if (@file_exists($f)) {
2904
			return $f;
2905
		}
2906
	}
2907
2908
	// 2.
2909
	$dir_var = sous_repertoire(_DIR_VAR, 'cache-css');
2910
	$f = $dir_var
2911
		. preg_replace(',.*/(.*?)(_rtl)?\.css,', '\1', $cssf)
2912
		. '.' . substr(md5($cssf), 0, 4) . '_' . $ndir . '.css';
2913
2914
	// la css peut etre distante (url absolue !)
2915
	if ($distant) {
2916
		include_spip('inc/distant');
2917
		$res = recuperer_url($css);
2918
		if (!$res or !$contenu = $res['page']) {
2919
			return $css;
2920
		}
2921
	} else {
2922 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...
2923
			and (_VAR_MODE != 'recalcul')
2924
		) {
2925
			return $f;
2926
		}
2927
		if (!lire_fichier($css, $contenu)) {
2928
			return $css;
2929
		}
2930
	}
2931
2932
2933
	// Inverser la direction gauche-droite en utilisant CSSTidy qui gere aussi les shorthands
2934
	include_spip("lib/csstidy/class.csstidy");
2935
	$parser = new csstidy();
2936
	$parser->set_cfg('optimise_shorthands', 0);
2937
	$parser->set_cfg('reverse_left_and_right', true);
2938
	$parser->parse($contenu);
2939
2940
	$contenu = $parser->print->plain();
2941
2942
2943
	// reperer les @import auxquels il faut propager le direction_css
2944
	preg_match_all(",\@import\s*url\s*\(\s*['\"]?([^'\"/][^:]*)['\"]?\s*\),Uims", $contenu, $regs);
2945
	$src = array();
2946
	$src_direction_css = array();
2947
	$src_faux_abs = array();
2948
	$d = dirname($css);
2949
	foreach ($regs[1] as $k => $import_css) {
2950
		$css_direction = direction_css("$d/$import_css", $voulue);
2951
		// si la css_direction est dans le meme path que la css d'origine, on tronque le path, elle sera passee en absolue
2952
		if (substr($css_direction, 0, strlen($d) + 1) == "$d/") {
2953
			$css_direction = substr($css_direction, strlen($d) + 1);
2954
		} // si la css_direction commence par $dir_var on la fait passer pour une absolue
2955
		elseif (substr($css_direction, 0, strlen($dir_var)) == $dir_var) {
2956
			$css_direction = substr($css_direction, strlen($dir_var));
2957
			$src_faux_abs["/@@@@@@/" . $css_direction] = $css_direction;
2958
			$css_direction = "/@@@@@@/" . $css_direction;
2959
		}
2960
		$src[] = $regs[0][$k];
2961
		$src_direction_css[] = str_replace($import_css, $css_direction, $regs[0][$k]);
2962
	}
2963
	$contenu = str_replace($src, $src_direction_css, $contenu);
2964
2965
	$contenu = urls_absolues_css($contenu, $css);
2966
2967
	// virer les fausses url absolues que l'on a mis dans les import
2968
	if (count($src_faux_abs)) {
2969
		$contenu = str_replace(array_keys($src_faux_abs), $src_faux_abs, $contenu);
2970
	}
2971
2972
	if (!ecrire_fichier($f, $contenu)) {
2973
		return $css;
2974
	}
2975
2976
	return $f;
2977
}
2978
2979
2980
/**
2981
 * Transforme les urls relatives d'un fichier CSS en absolues
2982
 *
2983
 * Récupère le chemin d'une css existante et crée (ou recrée) dans `_DIR_VAR/cache_css/`
2984
 * une css dont les url relatives sont passées en url absolues
2985
 *
2986
 * Le calcul n'est pas refait si le fichier cache existe déjà et que
2987
 * la source n'a pas été modifiée depuis.
2988
 *
2989
 * @uses recuperer_url() si l'URL source n'est pas sur le même site
2990
 * @uses urls_absolues_css()
2991
 *
2992
 * @param string $css
2993
 *     Chemin ou URL du fichier CSS source
2994
 * @return string
2995
 *     - Chemin du fichier CSS transformé (si source lisible et mise en cache réussie)
2996
 *     - Chemin ou URL du fichier CSS source sinon.
2997
 **/
2998
function url_absolue_css($css) {
2999
	if (!preg_match(',\.css$,i', $css, $r)) {
3000
		return $css;
3001
	}
3002
3003
	$url_absolue_css = url_absolue($css);
3004
3005
	$f = basename($css, '.css');
3006
	$f = sous_repertoire(_DIR_VAR, 'cache-css')
3007
		. preg_replace(",(.*?)(_rtl|_ltr)?$,", "\\1-urlabs-" . substr(md5("$css-urlabs"), 0, 4) . "\\2", $f)
3008
		. '.css';
3009
3010 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...
3011
		return $f;
3012
	}
3013
3014
	if ($url_absolue_css == $css) {
3015
		if (strncmp($GLOBALS['meta']['adresse_site'], $css, $l = strlen($GLOBALS['meta']['adresse_site'])) != 0
3016
			or !lire_fichier(_DIR_RACINE . substr($css, $l), $contenu)
3017
		) {
3018
			include_spip('inc/distant');
3019
			$contenu = recuperer_url($css);
3020
			$contenu = $contenu['page'] ?? '';
3021
			if (!$contenu) {
3022
				return $css;
3023
			}
3024
		}
3025
	} elseif (!lire_fichier($css, $contenu)) {
3026
		return $css;
3027
	}
3028
3029
	// passer les url relatives a la css d'origine en url absolues
3030
	$contenu = urls_absolues_css($contenu, $css);
3031
3032
	// ecrire la css
3033
	if (!ecrire_fichier($f, $contenu)) {
3034
		return $css;
3035
	}
3036
3037
	return $f;
3038
}
3039
3040
3041
/**
3042
 * Récupère la valeur d'une clé donnée
3043
 * dans un tableau (ou un objet).
3044
 *
3045
 * @filtre
3046
 * @link https://www.spip.net/4572
3047
 * @example
3048
 *     ```
3049
 *     [(#VALEUR|table_valeur{cle/sous/element})]
3050
 *     ```
3051
 *
3052
 * @param mixed $table
3053
 *     Tableau ou objet PHP
3054
 *     (ou chaîne serialisée de tableau, ce qui permet d'enchaîner le filtre)
3055
 * @param string $cle
3056
 *     Clé du tableau (ou paramètre public de l'objet)
3057
 *     Cette clé peut contenir des caractères / pour sélectionner
3058
 *     des sous éléments dans le tableau, tel que `sous/element/ici`
3059
 *     pour obtenir la valeur de `$tableau['sous']['element']['ici']`
3060
 * @param mixed $defaut
3061
 *     Valeur par defaut retournée si la clé demandée n'existe pas
3062
 * @param bool  $conserver_null
3063
 *     Permet de forcer la fonction à renvoyer la valeur null d'un index
3064
 *     et non pas $defaut comme cela est fait naturellement par la fonction
3065
 *     isset. On utilise alors array_key_exists() à la place de isset().
3066
 * 
3067
 * @return mixed
3068
 *     Valeur trouvée ou valeur par défaut.
3069
 **/
3070
function table_valeur($table, $cle, $defaut = '', $conserver_null = false) {
3071
	foreach (explode('/', $cle) as $k) {
3072
3073
		$table = is_string($table) ? @unserialize($table) : $table;
3074
3075
		if (is_object($table)) {
3076
			$table = (($k !== "") and isset($table->$k)) ? $table->$k : $defaut;
3077
		} elseif (is_array($table)) {
3078
			if ($conserver_null) {
3079
				$table = array_key_exists($k, $table) ? $table[$k] : $defaut;
3080
			} else {
3081
				$table = isset($table[$k]) ? $table[$k] : $defaut;
3082
			}
3083
		} else {
3084
			$table = $defaut;
3085
		}
3086
	}
3087
3088
	return $table;
3089
}
3090
3091
/**
3092
 * Retrouve un motif dans un texte à partir d'une expression régulière
3093
 *
3094
 * S'appuie sur la fonction `preg_match()` en PHP
3095
 *
3096
 * @example
3097
 *    - `[(#TITRE|match{toto})]`
3098
 *    - `[(#TEXTE|match{^ceci$,Uims})]`
3099
 *    - `[(#TEXTE|match{truc(...)$, UimsS, 1})]` Capture de la parenthèse indiquée
3100
 *    - `[(#TEXTE|match{truc(...)$, 1})]` Équivalent, sans indiquer les modificateurs
3101
 *
3102
 * @filtre
3103
 * @link https://www.spip.net/4299
3104
 * @link http://php.net/manual/fr/function.preg-match.php Pour des infos sur `preg_match()`
3105
 *
3106
 * @param string $texte
3107
 *     Texte dans lequel chercher
3108
 * @param string|int $expression
3109
 *     Expression régulière de recherche, sans le délimiteur
3110
 * @param string $modif
3111
 *     - string : Modificateurs de l'expression régulière
3112
 *     - int : Numéro de parenthèse capturante
3113
 * @param int $capte
3114
 *     Numéro de parenthèse capturante
3115
 * @return bool|string
3116
 *     - false : l'expression n'a pas été trouvée
3117
 *     - true : expression trouvée, mais pas la parenthèse capturante
3118
 *     - string : expression trouvée.
3119
 **/
3120
function filtre_match_dist($texte, $expression, $modif = "UimsS", $capte = 0) {
3121
	if (intval($modif) and $capte == 0) {
3122
		$capte = $modif;
3123
		$modif = "UimsS";
3124
	}
3125
	$expression = str_replace("\/", "/", $expression);
3126
	$expression = str_replace("/", "\/", $expression);
3127
3128
	if (preg_match('/' . $expression . '/' . $modif, $texte, $r)) {
3129
		if (isset($r[$capte])) {
3130
			return $r[$capte];
3131
		} else {
3132
			return true;
3133
		}
3134
	}
3135
3136
	return false;
3137
}
3138
3139
3140
/**
3141
 * Remplacement de texte à base d'expression régulière
3142
 *
3143
 * @filtre
3144
 * @link https://www.spip.net/4309
3145
 * @see match()
3146
 * @example
3147
 *     ```
3148
 *     [(#TEXTE|replace{^ceci$,cela,UimsS})]
3149
 *     ```
3150
 *
3151
 * @param string $texte
3152
 *     Texte
3153
 * @param string $expression
3154
 *     Expression régulière
3155
 * @param string $replace
3156
 *     Texte de substitution des éléments trouvés
3157
 * @param string $modif
3158
 *     Modificateurs pour l'expression régulière.
3159
 * @return string
3160
 *     Texte
3161
 **/
3162
function replace($texte, $expression, $replace = '', $modif = "UimsS") {
3163
	$expression = str_replace("\/", "/", $expression);
3164
	$expression = str_replace("/", "\/", $expression);
3165
3166
	return preg_replace('/' . $expression . '/' . $modif, $replace, $texte);
3167
}
3168
3169
3170
/**
3171
 * Cherche les documents numerotés dans un texte traite par `propre()`
3172
 *
3173
 * Affecte la liste des doublons['documents']
3174
 *
3175
 * @param array $doublons
3176
 *     Liste des doublons
3177
 * @param string $letexte
3178
 *     Le texte
3179
 * @return string
3180
 *     Le texte
3181
 **/
3182
function traiter_doublons_documents(&$doublons, $letexte) {
3183
3184
	// Verifier dans le texte & les notes (pas beau, helas)
3185
	$t = $letexte . $GLOBALS['les_notes'];
3186
3187
	if (strstr($t, 'spip_document_') // evite le preg_match_all si inutile
3188
		and preg_match_all(
3189
			',<[^>]+\sclass=["\']spip_document_([0-9]+)[\s"\'],imsS',
3190
			$t, $matches, PREG_PATTERN_ORDER)
3191
	) {
3192
		if (!isset($doublons['documents'])) {
3193
			$doublons['documents'] = "";
3194
		}
3195
		$doublons['documents'] .= "," . join(',', $matches[1]);
3196
	}
3197
3198
	return $letexte;
3199
}
3200
3201
/**
3202
 * Filtre vide qui ne renvoie rien
3203
 *
3204
 * @example
3205
 *     `[(#CALCUL|vide)]` n'affichera pas le résultat du calcul
3206
 * @filtre
3207
 *
3208
 * @param mixed $texte
3209
 * @return string Chaîne vide
3210
 **/
3211
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...
3212
	return "";
3213
}
3214
3215
//
3216
// Filtres pour le modele/emb (embed document)
3217
//
3218
3219
/**
3220
 * Écrit des balises HTML `<param...>` à partir d'un tableau de données tel que `#ENV`
3221
 *
3222
 * Permet d'écrire les balises `<param>` à indiquer dans un `<object>`
3223
 * en prenant toutes les valeurs du tableau transmis.
3224
 *
3225
 * Certaines clés spécifiques à SPIP et aux modèles embed sont omises :
3226
 * id, lang, id_document, date, date_redac, align, fond, recurs, emb, dir_racine
3227
 *
3228
 * @example `[(#ENV*|env_to_params)]`
3229
 *
3230
 * @filtre
3231
 * @link https://www.spip.net/4005
3232
 *
3233
 * @param array|string $env
3234
 *      Tableau cle => valeur des paramètres à écrire, ou chaine sérialisée de ce tableau
3235
 * @param array $ignore_params
3236
 *      Permet de compléter les clés ignorées du tableau.
3237
 * @return string
3238
 *      Code HTML résultant
3239
 **/
3240 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...
3241
	$ignore_params = array_merge(
3242
		array('id', 'lang', 'id_document', 'date', 'date_redac', 'align', 'fond', '', 'recurs', 'emb', 'dir_racine'),
3243
		$ignore_params
3244
	);
3245
	if (!is_array($env)) {
3246
		$env = unserialize($env);
3247
	}
3248
	$texte = "";
3249
	if ($env) {
3250
		foreach ($env as $i => $j) {
3251
			if (is_string($j) and !in_array($i, $ignore_params)) {
3252
				$texte .= "<param name='" . attribut_html($i) . "'\n\tvalue='" . attribut_html($j) . "' />";
3253
			}
3254
		}
3255
	}
3256
3257
	return $texte;
3258
}
3259
3260
/**
3261
 * Écrit des attributs HTML à partir d'un tableau de données tel que `#ENV`
3262
 *
3263
 * Permet d'écrire des attributs d'une balise HTML en utilisant les données du tableau transmis.
3264
 * Chaque clé deviendra le nom de l'attribut (et la valeur, sa valeur)
3265
 *
3266
 * Certaines clés spécifiques à SPIP et aux modèles embed sont omises :
3267
 * id, lang, id_document, date, date_redac, align, fond, recurs, emb, dir_racine
3268
 *
3269
 * @example `<embed src='#URL_DOCUMENT' [(#ENV*|env_to_attributs)] width='#GET{largeur}' height='#GET{hauteur}'></embed>`
3270
 * @filtre
3271
 *
3272
 * @param array|string $env
3273
 *      Tableau cle => valeur des attributs à écrire, ou chaine sérialisée de ce tableau
3274
 * @param array $ignore_params
3275
 *      Permet de compléter les clés ignorées du tableau.
3276
 * @return string
3277
 *      Code HTML résultant
3278
 **/
3279 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...
3280
	$ignore_params = array_merge(
3281
		array('id', 'lang', 'id_document', 'date', 'date_redac', 'align', 'fond', '', 'recurs', 'emb', 'dir_racine'),
3282
		$ignore_params
3283
	);
3284
	if (!is_array($env)) {
3285
		$env = unserialize($env);
3286
	}
3287
	$texte = "";
3288
	if ($env) {
3289
		foreach ($env as $i => $j) {
3290
			if (is_string($j) and !in_array($i, $ignore_params)) {
3291
				$texte .= attribut_html($i) . "='" . attribut_html($j) . "' ";
3292
			}
3293
		}
3294
	}
3295
3296
	return $texte;
3297
}
3298
3299
3300
/**
3301
 * Concatène des chaînes
3302
 *
3303
 * @filtre
3304
 * @link https://www.spip.net/4150
3305
 * @example
3306
 *     ```
3307
 *     #TEXTE|concat{texte1,texte2,...}
3308
 *     ```
3309
 *
3310
 * @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...
3311
 * @return string Chaînes concaténés
3312
 **/
3313
function concat(...$args) : string {
3314
	return join('', $args);
3315
}
3316
3317
3318
/**
3319
 * Retourne le contenu d'un ou plusieurs fichiers
3320
 *
3321
 * Les chemins sont cherchés dans le path de SPIP
3322
 *
3323
 * @see balise_INCLURE_dist() La balise `#INCLURE` peut appeler cette fonction
3324
 *
3325
 * @param array|string $files
3326
 *     - array : Liste de fichiers
3327
 *     - string : fichier ou fichiers séparés par `|`
3328
 * @param bool $script
3329
 *     - si true, considère que c'est un fichier js à chercher `javascript/`
3330
 * @return string
3331
 *     Contenu du ou des fichiers, concaténé
3332
 **/
3333
function charge_scripts($files, $script = true) {
3334
	$flux = "";
3335
	foreach (is_array($files) ? $files : explode("|", $files) as $file) {
3336
		if (!is_string($file)) {
3337
			continue;
3338
		}
3339
		if ($script) {
3340
			$file = preg_match(",^\w+$,", $file) ? "javascript/$file.js" : '';
3341
		}
3342
		if ($file) {
3343
			$path = find_in_path($file);
3344
			if ($path) {
3345
				$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 3343 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...
3346
			}
3347
		}
3348
	}
3349
3350
	return $flux;
3351
}
3352
3353
3354
/**
3355
 * Produit une balise img avec un champ alt d'office si vide
3356
 *
3357
 * Attention le htmlentities et la traduction doivent être appliqués avant.
3358
 *
3359
 * @param string $img
3360
 * @param string $alt
3361
 * @param string $atts
3362
 * @param string $title
3363
 * @param array $options
3364
 *   chemin_image : utiliser chemin_image sur $img fourni, ou non (oui par dafaut)
3365
 *   utiliser_suffixe_size : utiliser ou non le suffixe de taille dans le nom de fichier de l'image
3366
 *   sous forme -xx.png (pour les icones essentiellement) (oui par defaut)
3367
 *   variante_svg_si_possible: utiliser l'image -xx.svg au lieu de -32.png par exemple (si la variante svg est disponible)
3368
 * @return string
3369
 */
3370
function http_img_pack($img, $alt, $atts = '', $title = '', $options = array()) {
3371
3372
	$img_file = $img;
3373
	if ($p = strpos($img_file, '?')) {
3374
		$img_file = substr($img_file,0, $p);
3375
	}
3376
	if (!isset($options['chemin_image']) or $options['chemin_image'] == true) {
3377
		$img_file = chemin_image($img);
3378
	}
3379
	else {
3380
		if (!isset($options['variante_svg_si_possible']) or $options['variante_svg_si_possible'] == true){
3381
			// on peut fournir une icone generique -xx.svg qui fera le job dans toutes les tailles, et qui est prioritaire sur le png
3382
			// si il y a un .svg a la bonne taille (-16.svg) a cote, on l'utilise en remplacement du -16.png
3383
			if (preg_match(',-(\d+)[.](png|gif|svg)$,', $img_file, $m)
3384
			  and $variante_svg_generique = substr($img_file, 0, -strlen($m[0])) . "-xx.svg"
3385
			  and file_exists($variante_svg_generique)) {
3386
				if ($variante_svg_size = substr($variante_svg_generique,0,-6) . $m[1] . ".svg" and file_exists($variante_svg_size)) {
3387
					$img_file = $variante_svg_size;
3388
				}
3389
				else {
3390
					$img_file = $variante_svg_generique;
3391
				}
3392
			}
3393
		}
3394
	}
3395
	if (stripos($atts, 'width') === false) {
3396
		// utiliser directement l'info de taille presente dans le nom
3397
		if ((!isset($options['utiliser_suffixe_size'])
3398
				or $options['utiliser_suffixe_size'] == true
3399
			  or strpos($img_file, '-xx.svg') !== false)
3400
			and (preg_match(',-([0-9]+)[.](png|gif|svg)$,', $img, $regs)
3401
					 or preg_match(',\?([0-9]+)px$,', $img, $regs))
3402
		) {
3403
			$largeur = $hauteur = intval($regs[1]);
3404
		} else {
3405
			$taille = taille_image($img_file);
3406
			list($hauteur, $largeur) = $taille;
3407
			if (!$hauteur or !$largeur) {
3408
				return "";
3409
			}
3410
		}
3411
		$atts .= " width='" . $largeur . "' height='" . $hauteur . "'";
3412
	}
3413
3414
	if (file_exists($img_file)) {
3415
		$img_file = timestamp($img_file);
3416
	}
3417
	if ($alt === false) {
3418
		$alt = '';
3419
	}
3420
	elseif($alt or $alt==='') {
3421
		$alt = " alt='".attribut_html($alt)."'";
3422
	}
3423
	else {
3424
		$alt = " alt='".attribut_html($title)."'";
3425
	}
3426
	return "<img src='$img_file'$alt"
3427
	. ($title ? ' title="' . attribut_html($title) . '"' : '')
3428
	. " " . ltrim($atts)
3429
	. " />";
3430
}
3431
3432
/**
3433
 * Générer une directive `style='background:url()'` à partir d'un fichier image
3434
 *
3435
 * @param string $img
3436
 * @param string $att
3437
 * @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...
3438
 * @return string
3439
 */
3440
function http_style_background($img, $att = '', $size=null) {
3441
	if ($size and is_numeric($size)){
3442
		$size = trim($size) . "px";
3443
	}
3444
	return " style='background" .
3445
		($att ? "" : "-image") . ": url(\"" . chemin_image($img) . "\")" . ($att ? (' ' . $att) : '') . ";"
3446
		. ($size ? "background-size:{$size};" : '')
3447
		. "'";
3448
}
3449
3450
3451
function helper_filtre_balise_img_svg_arguments($alt_or_size, $class_or_size, $size) {
3452
	$args = [$alt_or_size, $class_or_size, $size];
3453
	while (is_null(end($args)) and count($args)) {
3454
		array_pop($args);
3455
	}
3456
	if (!count($args)) {
3457
		return [null, null, null];
3458
	}
3459
	if (count($args) < 3) {
3460
		$maybe_size = array_pop($args);
3461
		// @2x
3462
		// @1.5x
3463
		// 512
3464
		// 512x*
3465
		// 512x300
3466
		if (!strlen($maybe_size)
3467
			or !preg_match(',^(@\d+(\.\d+)?x|\d+(x\*)?|\d+x\d+)$,', trim($maybe_size))) {
3468
			$args[] = $maybe_size;
3469
			$maybe_size = null;
3470
		}
3471
		while (count($args)<2) {
3472
			$args[] = null; // default alt or class
3473
		}
3474
		$args[] = $maybe_size;
3475
	}
3476
	return $args;
3477
}
3478
3479
function helper_filtre_balise_img_svg_size($img, $size) {
3480
	// si size est de la forme '@2x' c'est un coeff multiplicateur sur la densite
3481
	if (strpos($size, '@') === 0 and substr($size,-1) === 'x') {
3482
		$coef = floatval(substr($size, 1, -1));
3483
		list($h, $w) = taille_image($img);
3484
		$height = intval(round($h / $coef));
3485
		$width = intval(round($w / $coef));
3486
	}
3487
	// sinon c'est une valeur seule si image caree ou largeurxhauteur
3488
	else {
3489
		$size = explode('x', $size, 2);
3490
		$size = array_map('trim', $size);
3491
		$height = $width = intval(array_shift($size));
3492
3493
		if (count($size) and reset($size)) {
3494
			$height = array_shift($size);
3495
			if ($height === '*') {
3496
				list($h, $w) = taille_image($img);
3497
				$height = intval(round($h * $width / $w));
3498
			}
3499
		}
3500
	}
3501
3502
	return [$width, $height];
3503
}
3504
3505
/**
3506
 * Générer une balise HTML `img` à partir d'un nom de fichier et/ou renseigne son alt/class/width/height
3507
 * selon les arguments passés
3508
 *
3509
 * Le class et le alt peuvent etre omis et dans ce cas size peut-être renseigné comme dernier argument :
3510
 * [(#FICHIER|balise_img{@2x})]
3511
 * [(#FICHIER|balise_img{1024})]
3512
 * [(#FICHIER|balise_img{1024x*})]
3513
 * [(#FICHIER|balise_img{1024x640})]
3514
 * [(#FICHIER|balise_img{'un nuage',1024x640})]
3515
 * [(#FICHIER|balise_img{'un nuage','spip_logo',1024x640})]
3516
 * 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
3517
 * [(#FICHIER|balise_img{'@2x','',''})]
3518
 *
3519
 * @uses http_img_pack()
3520
 *
3521
 * @param string $img
3522
 *   chemin vers un fichier ou balise `<img src='...' />` (generee par un filtre image par exemple)
3523
 * @param string $alt
3524
 *   texte alternatif ; une valeur nulle pour explicitement ne pas avoir de balise alt sur l'image (au lieu d'un alt vide)
3525
 * @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...
3526
 *   attribut class ; null par defaut (ie si img est une balise, son attribut class sera inchange. pas de class inseree
3527
 * @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...
3528
 *   taille imposee
3529
 *     @2x : pour imposer une densite x2 (widht et height seront divisees par 2)
3530
 *     largeur : pour une image carree
3531
 *     largeurx* : pour imposer uniquement la largeur (un attribut height sera aussi ajoute, mais calcule automatiquement pour respecter le ratio initial de l'image)
3532
 *     largeurxhauteur pour fixer les 2 dimensions
3533
 * @return string
3534
 *     Code HTML de la balise IMG
3535
 */
3536
function filtre_balise_img_dist($img, $alt = '', $class = null, $size=null) {
3537
3538
	list($alt, $class, $size) = helper_filtre_balise_img_svg_arguments($alt, $class, $size);
3539
3540
	$img = trim($img);
3541
	if (strpos($img, '<img') === 0) {
3542
		if (!is_null($alt)) {
3543
			$img = inserer_attribut($img, 'alt', $alt);
3544
		}
3545 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...
3546
			if (strlen($class)) {
3547
				$img = inserer_attribut($img, 'class', $class);
3548
			}
3549
			else {
3550
				$img = vider_attribut($img, 'class');
3551
			}
3552
		}
3553
	}
3554
	else {
3555
		$img = http_img_pack($img, $alt, $class ? " class='" . attribut_html($class) . "'" : '', '',
3556
				array('chemin_image' => false, 'utiliser_suffixe_size' => false));
3557
		if (is_null($alt)) {
3558
			$img = vider_attribut($img, 'alt');
3559
		}
3560
	}
3561
3562
	if ($img and !is_null($size) and strlen($size = trim($size))) {
3563
		list($width, $height) = helper_filtre_balise_img_svg_size($img, $size);
3564
3565
		$img = inserer_attribut($img, 'width', $width);
3566
		$img = inserer_attribut($img, 'height', $height);
3567
	}
3568
3569
	return $img;
3570
}
3571
3572
3573
/**
3574
 * Inserer un svg inline
3575
 * http://www.accede-web.com/notices/html-css-javascript/6-images-icones/6-2-svg-images-vectorielles/
3576
 *
3577
 * pour l'inserer avec une balise <img>, utiliser le filtre |balise_img
3578
 *
3579
 * Le class et le alt peuvent etre omis et dans ce cas size peut-être renseigné comme dernier argument :
3580
 * [(#FICHIER|balise_svg{1024x640})]
3581
 * [(#FICHIER|balise_svg{'un nuage',1024x640})]
3582
 * [(#FICHIER|balise_svg{'un nuage','spip_logo',1024x640})]
3583
 * 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
3584
 * [(#FICHIER|balise_svg{'un nuage','@2x',''})]
3585
 *
3586
 * @param string $img
3587
 *   chemin vers un fichier ou balise `<svg ... >... </svg>` deja preparee
3588
 * @param string $alt
3589
 *   texte alternatif ; une valeur nulle pour explicitement ne pas avoir de balise alt sur l'image (au lieu d'un alt vide)
3590
 * @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...
3591
 *   attribut class ; null par defaut (ie si img est une balise, son attribut class sera inchange. pas de class inseree
3592
 * @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...
3593
 *   taille imposee
3594
 *     @2x : pour imposer une densite x2 (widht et height seront divisees par 2)
3595
 *     largeur : pour une image carree
3596
 *     largeurx* : pour imposer uniquement la largeur (un attribut height sera aussi ajoute, mais calcule automatiquement pour respecter le ratio initial de l'image)
3597
 *     largeurxhauteur pour fixer les 2 dimensions
3598
 * @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...
3599
 *     Code HTML de la balise SVG
3600
 */
3601
function filtre_balise_svg_dist($img, $alt = '', $class = null, $size=null) {
3602
3603
	$img = trim($img);
3604
	$img_file = $img;
3605
	if (strpos($img, '<svg') === false){
3606
		if ($p = strpos($img_file, '?')){
3607
			$img_file = substr($img_file, 0, $p);
3608
		}
3609
3610
		if (!$img_file or !$svg = file_get_contents($img_file)){
3611
			return '';
3612
		}
3613
	}
3614
3615
	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...
3616
		return '';
3617
	}
3618
3619
	list($alt, $class, $size) = helper_filtre_balise_img_svg_arguments($alt, $class, $size);
3620
3621
	$balise_svg = $match[0];
3622
	$balise_svg_source = $balise_svg;
3623
3624
	// entete XML à supprimer
3625
	$svg = preg_replace(',^\s*<\?xml[^>]*\?' . '>,', '', $svg);
3626
3627
	// IE est toujours mon ami
3628
	$balise_svg = inserer_attribut($balise_svg, 'focusable', 'false');
3629
3630
	// regler la classe
3631 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...
3632
		if (strlen($class)) {
3633
			$balise_svg = inserer_attribut($balise_svg, 'class', $class);
3634
		}
3635
		else {
3636
			$balise_svg = vider_attribut($balise_svg, 'class');
3637
		}
3638
	}
3639
3640
	// regler le alt
3641
	if ($alt){
3642
		$balise_svg = inserer_attribut($balise_svg, 'role', 'img');
3643
		$id = "img-svg-title-" . substr(md5("$img_file:$svg:$alt"),0,4);
3644
		$balise_svg = inserer_attribut($balise_svg, 'aria-labelledby', $id);
3645
		$title = "<title id=\"$id\">" . entites_html($alt)."</title>\n";
3646
		$balise_svg .= $title;
3647
	}
3648
	else {
3649
		$balise_svg = inserer_attribut($balise_svg, 'aria-hidden', 'true');
3650
	}
3651
3652
	$svg = str_replace($balise_svg_source, $balise_svg, $svg);
3653
3654
	if (!is_null($size) and strlen($size = trim($size))) {
3655
		list($width, $height) = helper_filtre_balise_img_svg_size($svg, $size);
3656
3657
		if (!function_exists('svg_redimensionner')) {
3658
			include_spip('inc/svg');
3659
		}
3660
		$svg = svg_redimensionner($svg, $width, $height);
3661
	}
3662
3663
	return $svg;
3664
}
3665
3666
3667
3668
/**
3669
 * Affiche chaque valeur d'un tableau associatif en utilisant un modèle
3670
 * 
3671
 * @deprecated 4.0
3672
 * @see Utiliser `<BOUCLE_x(DATA){source table, #GET{tableau}}>`
3673
 *
3674
 * @example
3675
 *     - `[(#ENV*|unserialize|foreach)]`
3676
 *     - `[(#ARRAY{a,un,b,deux}|foreach)]`
3677
 *
3678
 * @filtre
3679
 * @link https://www.spip.net/4248
3680
 *
3681
 * @param array $tableau
3682
 *     Tableau de données à afficher
3683
 * @param string $modele
3684
 *     Nom du modèle à utiliser
3685
 * @return string
3686
 *     Code HTML résultant
3687
 **/
3688
function filtre_foreach_dist($tableau, $modele = 'foreach') {
3689
	$texte = '';
3690
	if (is_array($tableau)) {
3691
		foreach ($tableau as $k => $v) {
3692
			$res = recuperer_fond('modeles/' . $modele,
3693
				array_merge(array('cle' => $k), (is_array($v) ? $v : array('valeur' => $v)))
3694
			);
3695
			$texte .= $res;
3696
		}
3697
	}
3698
3699
	return $texte;
3700
}
3701
3702
3703
/**
3704
 * Obtient des informations sur les plugins actifs
3705
 *
3706
 * @filtre
3707
 * @uses liste_plugin_actifs() pour connaître les informations affichables
3708
 *
3709
 * @param string $plugin
3710
 *     Préfixe du plugin ou chaîne vide
3711
 * @param string $type_info
3712
 *     Type d'info demandée
3713
 * @param bool $reload
3714
 *     true (à éviter) pour forcer le recalcul du cache des informations des plugins.
3715
 * @return array|string|bool
3716
 *
3717
 *     - Liste sérialisée des préfixes de plugins actifs (si $plugin = '')
3718
 *     - Suivant $type_info, avec $plugin un préfixe
3719
 *         - est_actif : renvoie true s'il est actif, false sinon
3720
 *         - x : retourne l'information x du plugin si présente (et plugin actif)
3721
 *         - tout : retourne toutes les informations du plugin actif
3722
 **/
3723
function filtre_info_plugin_dist($plugin, $type_info, $reload = false) {
3724
	include_spip('inc/plugin');
3725
	$plugin = strtoupper($plugin);
3726
	$plugins_actifs = liste_plugin_actifs();
3727
3728
	if (!$plugin) {
3729
		return serialize(array_keys($plugins_actifs));
3730
	} elseif (empty($plugins_actifs[$plugin]) and !$reload) {
3731
		return '';
3732
	} elseif (($type_info == 'est_actif') and !$reload) {
3733
		return $plugins_actifs[$plugin] ? 1 : 0;
3734
	} elseif (isset($plugins_actifs[$plugin][$type_info]) and !$reload) {
3735
		return $plugins_actifs[$plugin][$type_info];
3736
	} else {
3737
		$get_infos = charger_fonction('get_infos', 'plugins');
3738
		// On prend en compte les extensions
3739
		if (!is_dir($plugins_actifs[$plugin]['dir_type'])) {
3740
			$dir_plugins = constant($plugins_actifs[$plugin]['dir_type']);
3741
		} else {
3742
			$dir_plugins = $plugins_actifs[$plugin]['dir_type'];
3743
		}
3744
		if (!$infos = $get_infos($plugins_actifs[$plugin]['dir'], $reload, $dir_plugins)) {
3745
			return '';
3746
		}
3747
		if ($type_info == 'tout') {
3748
			return $infos;
3749
		} elseif ($type_info == 'est_actif') {
3750
			return $infos ? 1 : 0;
3751
		} else {
3752
			return strval($infos[$type_info]);
3753
		}
3754
	}
3755
}
3756
3757
3758
/**
3759
 * Affiche la puce statut d'un objet, avec un menu rapide pour changer
3760
 * de statut si possibilité de l'avoir
3761
 *
3762
 * @see inc_puce_statut_dist()
3763
 *
3764
 * @filtre
3765
 *
3766
 * @param int $id_objet
3767
 *     Identifiant de l'objet
3768
 * @param string $statut
3769
 *     Statut actuel de l'objet
3770
 * @param int $id_rubrique
3771
 *     Identifiant du parent
3772
 * @param string $type
3773
 *     Type d'objet
3774
 * @param bool $ajax
3775
 *     Indique s'il ne faut renvoyer que le coeur du menu car on est
3776
 *     dans une requete ajax suite à un post de changement rapide
3777
 * @return string
3778
 *     Code HTML de l'image de puce de statut à insérer (et du menu de changement si présent)
3779
 */
3780
function puce_changement_statut($id_objet, $statut, $id_rubrique, $type, $ajax = false) {
3781
	$puce_statut = charger_fonction('puce_statut', 'inc');
3782
3783
	return $puce_statut($id_objet, $statut, $id_rubrique, $type, $ajax);
3784
}
3785
3786
3787
/**
3788
 * Affiche la puce statut d'un objet, avec un menu rapide pour changer
3789
 * de statut si possibilité de l'avoir
3790
 *
3791
 * Utilisable sur tout objet qui a declaré ses statuts
3792
 *
3793
 * @example
3794
 *     [(#STATUT|puce_statut{article})] affiche une puce passive
3795
 *     [(#STATUT|puce_statut{article,#ID_ARTICLE,#ID_RUBRIQUE})] affiche une puce avec changement rapide
3796
 *
3797
 * @see inc_puce_statut_dist()
3798
 *
3799
 * @filtre
3800
 *
3801
 * @param string $statut
3802
 *     Statut actuel de l'objet
3803
 * @param string $objet
3804
 *     Type d'objet
3805
 * @param int $id_objet
3806
 *     Identifiant de l'objet
3807
 * @param int $id_parent
3808
 *     Identifiant du parent
3809
 * @return string
3810
 *     Code HTML de l'image de puce de statut à insérer (et du menu de changement si présent)
3811
 */
3812
function filtre_puce_statut_dist($statut, $objet, $id_objet = 0, $id_parent = 0) {
3813
	static $puce_statut = null;
3814
	if (!$puce_statut) {
3815
		$puce_statut = charger_fonction('puce_statut', 'inc');
3816
	}
3817
3818
	return $puce_statut($id_objet, $statut, $id_parent, $objet, false,
3819
		objet_info($objet, 'editable') ? _ACTIVER_PUCE_RAPIDE : false);
3820
}
3821
3822
3823
/**
3824
 * Encoder un contexte pour l'ajax
3825
 *
3826
 * Encoder le contexte, le signer avec une clé, le crypter
3827
 * avec le secret du site, le gziper si possible.
3828
 *
3829
 * L'entrée peut-être sérialisée (le `#ENV**` des fonds ajax et ajax_stat)
3830
 *
3831
 * @see  decoder_contexte_ajax()
3832
 * @uses calculer_cle_action()
3833
 *
3834
 * @param string|array $c
3835
 *   contexte, peut etre un tableau serialize
3836
 * @param string $form
3837
 *   nom du formulaire eventuel
3838
 * @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...
3839
 *   contenu a emboiter dans le conteneur ajax
3840
 * @param string $ajaxid
3841
 *   ajaxid pour cibler le bloc et forcer sa mise a jour
3842
 * @return string
3843
 *   hash du contexte
3844
 */
3845
function encoder_contexte_ajax($c, $form = '', $emboite = null, $ajaxid = '') {
3846
	if (is_string($c)
3847
		and @unserialize($c) !== false
3848
	) {
3849
		$c = unserialize($c);
3850
	}
3851
3852
	// supprimer les parametres debut_x
3853
	// pour que la pagination ajax ne soit pas plantee
3854
	// si on charge la page &debut_x=1 : car alors en cliquant sur l'item 0,
3855
	// le debut_x=0 n'existe pas, et on resterait sur 1
3856
	if (is_array($c)) {
3857
		foreach ($c as $k => $v) {
3858
			if (strpos($k, 'debut_') === 0) {
3859
				unset($c[$k]);
3860
			}
3861
		}
3862
	}
3863
3864
	if (!function_exists('calculer_cle_action')) {
3865
		include_spip("inc/securiser_action");
3866
	}
3867
3868
	$c = serialize($c);
3869
	$cle = calculer_cle_action($form . $c);
3870
	$c = "$cle:$c";
3871
3872
	// on ne stocke pas les contextes dans des fichiers en cache
3873
	// par defaut, sauf si cette configuration a été forcée
3874
	// OU que la longueur de l’argument géneré est plus long
3875
	// que ce qui est toléré.
3876
	$cache_contextes_ajax = (defined('_CACHE_CONTEXTES_AJAX') and _CACHE_CONTEXTES_AJAX);
3877
	if (!$cache_contextes_ajax) {
3878
		$env = $c;
3879
		if (function_exists('gzdeflate') && function_exists('gzinflate')) {
3880
			$env = gzdeflate($env);
3881
		}
3882
		$env = _xor($env);
3883
		$env = base64_encode($env);
3884
		$len = strlen($env);
3885
		// Si l’url est trop longue pour le navigateur
3886
		$max_len = _CACHE_CONTEXTES_AJAX_SUR_LONGUEUR;
3887
		if ($len > $max_len) {
3888
			$cache_contextes_ajax = true;
3889
			spip_log("Contextes AJAX forces en fichiers !"
3890
				. " Cela arrive lorsque la valeur du contexte" 
3891
				. " depasse la longueur maximale autorisee ($max_len). Ici : $len."
3892
				, _LOG_AVERTISSEMENT);
3893
		}
3894
		// Sinon si Suhosin est actif et a une la valeur maximale des variables en GET...
3895
		elseif (
3896
			$max_len = @ini_get('suhosin.get.max_value_length')
3897
			and $max_len < $len
3898
		) {
3899
			$cache_contextes_ajax = true;
3900
			spip_log("Contextes AJAX forces en fichiers !"
3901
				. " Cela arrive lorsque la valeur du contexte"
3902
				. " depasse la longueur maximale autorisee par Suhosin"
3903
				. " ($max_len) dans 'suhosin.get.max_value_length'. Ici : $len."
3904
				. " Vous devriez modifier les parametres de Suhosin"
3905
				. " pour accepter au moins 1024 caracteres.", _LOG_AVERTISSEMENT);
3906
		} 
3907
3908
	}
3909
3910
	if ($cache_contextes_ajax) {
3911
		$dir = sous_repertoire(_DIR_CACHE, 'contextes');
3912
		// stocker les contextes sur disque et ne passer qu'un hash dans l'url
3913
		$md5 = md5($c);
3914
		ecrire_fichier("$dir/c$md5", $c);
3915
		$env = $md5;
3916
	}
3917
3918
	if ($emboite === null) {
3919
		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...
3920
	}
3921
	if (!trim($emboite)) {
3922
		return "";
3923
	}
3924
	// toujours encoder l'url source dans le bloc ajax
3925
	$r = self();
3926
	$r = ' data-origin="' . $r . '"';
3927
	$class = 'ajaxbloc';
3928
	if ($ajaxid and is_string($ajaxid)) {
3929
		// ajaxid est normalement conforme a un nom de classe css
3930
		// on ne verifie pas la conformite, mais on passe entites_html par dessus par precaution
3931
		$class .= ' ajax-id-' . entites_html($ajaxid);
3932
	}
3933
3934
	return "<div class='$class' " . "data-ajax-env='$env'$r>\n$emboite</div><!--ajaxbloc-->\n";
3935
}
3936
3937
/**
3938
 * Décoder un hash de contexte pour l'ajax
3939
 *
3940
 * Précude inverse de `encoder_contexte_ajax()`
3941
 *
3942
 * @see  encoder_contexte_ajax()
3943
 * @uses calculer_cle_action()
3944
 *
3945
 * @param string $c
3946
 *   hash du contexte
3947
 * @param string $form
3948
 *   nom du formulaire eventuel
3949
 * @return array|string|bool
3950
 *   - array|string : contexte d'environnement, possiblement sérialisé
3951
 *   - false : erreur de décodage
3952
 */
3953
function decoder_contexte_ajax($c, $form = '') {
3954
	if (!function_exists('calculer_cle_action')) {
3955
		include_spip("inc/securiser_action");
3956
	}
3957
	if (((defined('_CACHE_CONTEXTES_AJAX') and _CACHE_CONTEXTES_AJAX) or strlen($c) == 32)
3958
		and $dir = sous_repertoire(_DIR_CACHE, 'contextes')
3959
		and lire_fichier("$dir/c$c", $contexte)
3960
	) {
3961
		$c = $contexte;
3962
	} else {
3963
		$c = @base64_decode($c);
3964
		$c = _xor($c);
3965
		if (function_exists('gzdeflate') && function_exists('gzinflate')) {
3966
			$c = @gzinflate($c);
3967
		}
3968
	}
3969
3970
	// extraire la signature en debut de contexte
3971
	// et la verifier avant de deserializer
3972
	// format : signature:donneesserializees
3973 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...
3974
		$cle = substr($c,0,$p);
3975
		$c = substr($c,$p+1);
3976
3977
		if ($cle == calculer_cle_action($form . $c)) {
3978
			$env = @unserialize($c);
3979
			return $env;
3980
		}
3981
	}
3982
3983
	return false;
3984
}
3985
3986
3987
/**
3988
 * Encrypte ou décrypte un message
3989
 *
3990
 * @link http://www.php.net/manual/fr/language.operators.bitwise.php#81358
3991
 *
3992
 * @param string $message
3993
 *    Message à encrypter ou décrypter
3994
 * @param null|string $key
3995
 *    Clé de cryptage / décryptage.
3996
 *    Une clé sera calculée si non transmise
3997
 * @return string
3998
 *    Message décrypté ou encrypté
3999
 **/
4000
function _xor($message, $key = null) {
4001
	if (is_null($key)) {
4002
		if (!function_exists('calculer_cle_action')) {
4003
			include_spip("inc/securiser_action");
4004
		}
4005
		$key = pack("H*", calculer_cle_action('_xor'));
4006
	}
4007
4008
	$keylen = strlen($key);
4009
	$messagelen = strlen($message);
4010
	for ($i = 0; $i < $messagelen; $i++) {
4011
		$message[$i] = ~($message[$i] ^ $key[$i % $keylen]);
4012
	}
4013
4014
	return $message;
4015
}
4016
4017
/**
4018
 * Retourne une URL de réponse de forum (aucune action ici)
4019
 *
4020
 * @see filtre_url_reponse_forum() du plugin forum (prioritaire)
4021
 * @note
4022
 *   La vraie fonction est dans le plugin forum,
4023
 *   mais on évite ici une erreur du compilateur en absence du plugin
4024
 * @param string $texte
4025
 * @return string
4026
 */
4027
function url_reponse_forum($texte) { return $texte; }
4028
4029
/**
4030
 * retourne une URL de suivi rss d'un forum (aucune action ici)
4031
 *
4032
 * @see filtre_url_rss_forum() du plugin forum (prioritaire)
4033
 * @note
4034
 *   La vraie fonction est dans le plugin forum,
4035
 *   mais on évite ici une erreur du compilateur en absence du plugin
4036
 * @param string $texte
4037
 * @return string
4038
 */
4039
function url_rss_forum($texte) { return $texte; }
4040
4041
4042
/**
4043
 * Génère des menus avec liens ou `<strong class='on'>` non clicable lorsque
4044
 * l'item est sélectionné
4045
 * Le parametre $on peut recevoir un selecteur simplifié de type 'a.active' 'strong.active.expose'
4046
 * pour préciser la balise (a, span ou strong uniquement) et la/les classes à utiliser quand on est expose
4047
 *
4048
 * @filtre
4049
 * @link https://www.spip.net/4004
4050
 * @example
4051
 *   ```
4052
 *   [(#URL_RUBRIQUE|lien_ou_expose{#TITRE, #ENV{test}|=={en_cours}})]
4053
 *   [(#URL_RUBRIQUE|lien_ou_expose{#TITRE, #ENV{test}|=={en_cours}|?{a.monlien.active}, 'monlien'})]
4054
 *   ```
4055
 *
4056
 *
4057
 * @param string $url
4058
 *   URL du lien
4059
 * @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...
4060
 *   Texte du lien
4061
 * @param bool $on
4062
 *   É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>
4063
 *   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
4064
 * @param string $class
4065
 *   Classes CSS ajoutées au lien
4066
 * @param string $title
4067
 *   Title ajouté au lien
4068
 * @param string $rel
4069
 *   Attribut `rel` ajouté au lien
4070
 * @param string $evt
4071
 *   Complement à la balise `a` pour gérer un événement javascript,
4072
 *   de la forme ` onclick='...'`
4073
 * @return string
4074
 *   Code HTML
4075
 */
4076
function lien_ou_expose($url, $libelle = null, $on = false, $class = "", $title = "", $rel = "", $evt = '') {
4077
	if ($on) {
4078
		$bal = 'strong';
4079
		$class = "";
4080
		$att = "";
4081
		// si $on passe la balise et optionnelement une ou ++classe
4082
		// a.active span.selected.active etc....
4083
		if (is_string($on) and (strncmp($on, 'a', 1)==0 or strncmp($on, 'span', 4)==0 or strncmp($on, 'strong', 6)==0)){
4084
			$on = explode(".", $on);
4085
			// on verifie que c'est exactement une des 3 balises a, span ou strong
4086
			if (in_array(reset($on), array('a', 'span', 'strong'))){
4087
				$bal = array_shift($on);
4088
				$class = implode(" ", $on);
4089
				if ($bal=="a"){
4090
					$att = 'href="#" ';
4091
				}
4092
			}
4093
		}
4094
		$att .= 'class="' . ($class ? attribut_html($class) . ' ' : '') . (defined('_LIEN_OU_EXPOSE_CLASS_ON') ? _LIEN_OU_EXPOSE_CLASS_ON : 'on') . '"';
4095
	} else {
4096
		$bal = 'a';
4097
		$att = "href='$url'"
4098
			. ($title ? " title='" . attribut_html($title) . "'" : '')
4099
			. ($class ? " class='" . attribut_html($class) . "'" : '')
4100
			. ($rel ? " rel='" . attribut_html($rel) . "'" : '')
4101
			. $evt;
4102
	}
4103
	if ($libelle === null) {
4104
		$libelle = $url;
4105
	}
4106
4107
	return "<$bal $att>$libelle</$bal>";
4108
}
4109
4110
4111
/**
4112
 * Afficher un message "un truc"/"N trucs"
4113
 * Les items sont à indiquer comme pour la fonction _T() sous la forme :
4114
 * "module:chaine"
4115
 *
4116
 * @param int $nb : le nombre
4117
 * @param string $chaine_un : l'item de langue si $nb vaut un
4118
 * @param string $chaine_plusieurs : l'item de lanque si $nb >= 2
4119
 * @param string $var : La variable à remplacer par $nb dans l'item de langue (facultatif, défaut "nb")
4120
 * @param array $vars : Les autres variables nécessaires aux chaines de langues (facultatif)
4121
 * @return string : la chaine de langue finale en utilisant la fonction _T()
4122
 */
4123
function singulier_ou_pluriel($nb, $chaine_un, $chaine_plusieurs, $var = 'nb', $vars = array()) {
4124
	static $local_singulier_ou_pluriel = array();
4125
4126
	// si nb=0 ou pas de $vars valide on retourne une chaine vide, a traiter par un |sinon
4127
	if (!is_numeric($nb) or $nb == 0) {
4128
		return "";
4129
	}
4130
	if (!is_array($vars)) {
4131
		return "";
4132
	}
4133
4134
	$langue = $GLOBALS['spip_lang'];
4135
	if (!isset($local_singulier_ou_pluriel[$langue])) {
4136
		$local_singulier_ou_pluriel[$langue] = false;
4137
		if ($f = charger_fonction("singulier_ou_pluriel_${langue}", 'inc', true)
4138
		  or $f = charger_fonction("singulier_ou_pluriel", 'inc', true)) {
4139
			$local_singulier_ou_pluriel[$langue] = $f;
4140
		}
4141
	}
4142
4143
	// si on a une surcharge on l'utilise
4144
	if ($local_singulier_ou_pluriel[$langue]) {
4145
		return ($local_singulier_ou_pluriel[$langue])($nb, $chaine_un, $chaine_plusieurs, $var, $vars);
4146
	}
4147
4148
	// sinon traitement par defaut
4149
	$vars[$var] = $nb;
4150
	if ($nb >= 2) {
4151
		return _T($chaine_plusieurs, $vars);
4152
	} else {
4153
		return _T($chaine_un, $vars);
4154
	}
4155
}
4156
4157
4158
/**
4159
 * Fonction de base pour une icone dans un squelette.
4160
 * Il peut s'agir soit d'un simple lien, structure html : `<span><a><img><b>texte</b></span>`,
4161
 * soit d'un bouton d'action, structure identique au retour de `#BOUTON_ACTION`.
4162
 *
4163
 * @param string $type
4164
 *  'lien' ou 'bouton'
4165
 * @param string $lien
4166
 *  url
4167
 * @param string $texte
4168
 *  texte du lien / alt de l'image
4169
 * @param string $fond
4170
 *  objet avec ou sans son extension et sa taille (article, article-24, article-24.png)
4171
 * @param string $fonction
4172
 *  new/del/edit
4173
 * @param string $class
4174
 *  Classes supplémentaires (horizontale, verticale, ajax…)
4175
 * @param string $javascript
4176
 *  code javascript tel que "onclick='...'" par exemple
4177
 *  ou texte du message de confirmation s'il s'agit d'un bouton
4178
 * @return string
4179
 */
4180
function prepare_icone_base($type, $lien, $texte, $fond, $fonction = '', $class = '', $javascript = '') {
4181
4182
	$class_lien = $class_bouton = $class;
4183
4184
	// Normaliser la fonction et compléter la classe en fonction
4185
	if (in_array($fonction, ['del', 'supprimer.gif'])) {
4186
		$class_lien .= ' danger';
4187
		$class_bouton .= ' btn_danger';
4188
	} elseif ($fonction == 'rien.gif') {
4189
		$fonction = '';
4190
	} elseif ($fonction == 'delsafe') {
4191
		$fonction = 'del';
4192
	}
4193
4194
	$fond_origine = $fond;
4195
	// Remappage des icone : article-24.png+new => article-new-24.png
4196 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...
4197
		list($fond, $fonction) = $icone_renommer($fond, $fonction);
4198
	}
4199
4200
	// Ajouter le type d'objet dans la classe
4201
	$objet_type = substr(basename($fond), 0, -4);
4202
	$class_lien .= " $objet_type";
4203
	$class_bouton .= " $objet_type";
4204
4205
	// Texte
4206
	$alt = attribut_html($texte);
4207
	$title = " title=\"$alt\""; // est-ce pertinent de doubler le alt par un title ?
4208
4209
	// Liens : préparer les classes ajax
4210
	$ajax = '';
4211
	if ($type === 'lien') {
4212
		if (strpos($class_lien, 'ajax') !== false) {
4213
			$ajax = 'ajax';
4214
			if (strpos($class_lien, 'preload') !== false) {
4215
				$ajax .= ' preload';
4216
			}
4217
			if (strpos($class_lien, 'nocache') !== false) {
4218
				$ajax .= ' nocache';
4219
			}
4220
			$ajax = " class='$ajax'";
4221
		}
4222
	}
4223
4224
	// Repérer la taille et l'ajouter dans la classe
4225
	$size = 24;
4226
	if (preg_match("/-([0-9]{1,3})[.](gif|png|svg)$/i", $fond, $match)
4227
	  or preg_match("/-([0-9]{1,3})([.](gif|png|svg))?$/i", $fond_origine, $match)) {
4228
		$size = $match[1];
4229
	}
4230
	$class_lien .= " s$size";
4231
	$class_bouton .= " s$size";
4232
4233
	// Icône
4234
	$icone = http_img_pack($fond, $alt, "width='$size' height='$size'");
4235
	$icone = "<span class=\"icone-image".($fonction ? " icone-fonction icone-fonction-$fonction" : "") . "\">$icone</span>";
4236
4237
	// Markup final
4238
	if ($type == 'lien') {
4239
		return "<span class='icone $class_lien'>"
4240
		. "<a href='$lien'$title$ajax$javascript>"
4241
		. $icone
4242
		. "<b>$texte</b>"
4243
		. "</a></span>\n";
4244
	} else {
4245
		return bouton_action("$icone $texte", $lien, $class_bouton, $javascript, $alt);
4246
	}
4247
}
4248
4249
/**
4250
 * Crée un lien ayant une icone
4251
 *
4252
 * @uses prepare_icone_base()
4253
 *
4254
 * @param string $lien
4255
 *     URL du lien
4256
 * @param string $texte
4257
 *     Texte du lien
4258
 * @param string $fond
4259
 *     Objet avec ou sans son extension et sa taille (article, article-24, article-24.png)
4260
 * @param string $fonction
4261
 *     Fonction du lien (`edit`, `new`, `del`)
4262
 * @param string $class
4263
 *     Classe CSS, tel que `left`, `right` pour définir un alignement
4264
 * @param string $javascript
4265
 *     Javascript ajouté sur le lien
4266
 * @return string
4267
 *     Code HTML du lien
4268
 **/
4269
function icone_base($lien, $texte, $fond, $fonction = "", $class = "", $javascript = "") {
4270
	return prepare_icone_base('lien', $lien, $texte, $fond, $fonction, $class, $javascript);
4271
}
4272
4273
/**
4274
 * Crée un lien précédé d'une icone au dessus du texte
4275
 *
4276
 * @uses icone_base()
4277
 * @see  icone_verticale() Pour un usage dans un code PHP.
4278
 *
4279
 * @filtre
4280
 * @example
4281
 *     ```
4282
 *     [(#AUTORISER{voir,groupemots,#ID_GROUPE})
4283
 *         [(#URL_ECRIRE{groupe_mots,id_groupe=#ID_GROUPE}
4284
 *            |icone_verticale{<:mots:icone_voir_groupe_mots:>,groupe_mots-24.png,'',left})]
4285
 *    ]
4286
 *     ```
4287
 *
4288
 * @param string $lien
4289
 *     URL du lien
4290
 * @param string $texte
4291
 *     Texte du lien
4292
 * @param string $fond
4293
 *     Objet avec ou sans son extension et sa taille (article, article-24, article-24.png)
4294
 * @param string $fonction
4295
 *     Fonction du lien (`edit`, `new`, `del`)
4296
 * @param string $class
4297
 *     Classe CSS à ajouter, tel que `left`, `right`, `center` pour définir un alignement.
4298
 *     Il peut y en avoir plusieurs : `left ajax`
4299
 * @param string $javascript
4300
 *     Javascript ajouté sur le lien
4301
 * @return string
4302
 *     Code HTML du lien
4303
 **/
4304
function filtre_icone_verticale_dist($lien, $texte, $fond, $fonction = "", $class = "", $javascript = "") {
4305
	return icone_base($lien, $texte, $fond, $fonction, "verticale $class", $javascript);
4306
}
4307
4308
/**
4309
 * Crée un lien précédé d'une icone horizontale
4310
 *
4311
 * @uses icone_base()
4312
 * @see  icone_horizontale() Pour un usage dans un code PHP.
4313
 *
4314
 * @filtre
4315
 * @example
4316
 *     En tant que filtre dans un squelettes :
4317
 *     ```
4318
 *     [(#URL_ECRIRE{sites}|icone_horizontale{<:sites:icone_voir_sites_references:>,site-24.png})]
4319
 *
4320
 *     [(#AUTORISER{supprimer,groupemots,#ID_GROUPE}|oui)
4321
 *         [(#URL_ACTION_AUTEUR{supprimer_groupe_mots,#ID_GROUPE,#URL_ECRIRE{mots}}
4322
 *             |icone_horizontale{<:mots:icone_supprimer_groupe_mots:>,groupe_mots,del})]
4323
 *     ]
4324
 *     ```
4325
 *
4326
 *     En tant que filtre dans un code php :
4327
 *     ```
4328
 *     $icone_horizontale=chercher_filtre('icone_horizontale');
4329
 *     $icone = $icone_horizontale(generer_url_ecrire("stats_visites","id_article=$id_article"),
4330
 *         _T('statistiques:icone_evolution_visites', array('visites' => $visites)),
4331
 *         "statistique-24.png");
4332
 *     ```
4333
 *
4334
 * @param string $lien
4335
 *     URL du lien
4336
 * @param string $texte
4337
 *     Texte du lien
4338
 * @param string $fond
4339
 *     Objet avec ou sans son extension et sa taille (article, article-24, article-24.png)
4340
 * @param string $fonction
4341
 *     Fonction du lien (`edit`, `new`, `del`)
4342
 * @param string $class
4343
 *     Classe CSS à ajouter
4344
 * @param string $javascript
4345
 *     Javascript ajouté sur le lien
4346
 * @return string
4347
 *     Code HTML du lien
4348
 **/
4349
function filtre_icone_horizontale_dist($lien, $texte, $fond, $fonction = "", $class = "", $javascript = "") {
4350
	return icone_base($lien, $texte, $fond, $fonction, "horizontale $class", $javascript);
4351
}
4352
4353
/**
4354
 * Crée un bouton d'action intégrant une icone horizontale
4355
 *
4356
 * @uses prepare_icone_base()
4357
 *
4358
 * @filtre
4359
 * @example
4360
 *     ```
4361
 *     [(#URL_ACTION_AUTEUR{supprimer_mot, #ID_MOT, #URL_ECRIRE{groupe_mots,id_groupe=#ID_GROUPE}}
4362
 *         |bouton_action_horizontal{<:mots:info_supprimer_mot:>,mot-24.png,del,#ARRAY{bouton,btn_large}})]
4363
 *     ```
4364
 *
4365
 * @param string $lien
4366
 *     URL de l'action
4367
 * @param string $texte
4368
 *     Texte du bouton
4369
 * @param string $fond
4370
 *     Objet avec ou sans son extension et sa taille (article, article-24, article-24.png)
4371
 * @param string $fonction
4372
 *     Fonction du bouton (`edit`, `new`, `del`)
4373
 * @param string $class
4374
 *     Classes à ajouter au bouton, à l'exception de `ajax` qui est placé sur le formulaire.
4375
 * @param string $confirm
4376
 *     Message de confirmation à ajouter en javascript sur le bouton
4377
 * @return string
4378
 *     Code HTML du lien
4379
 **/
4380
function filtre_bouton_action_horizontal_dist($lien, $texte, $fond, $fonction = "", $class = "", $confirm = "") {
4381
	return prepare_icone_base('bouton', $lien, $texte, $fond, $fonction, $class, $confirm);
4382
}
4383
4384
/**
4385
 * Filtre `icone` pour compatibilité mappé sur `icone_base`
4386
 *
4387
 * @uses icone_base()
4388
 * @see  filtre_icone_verticale_dist()
4389
 *
4390
 * @filtre
4391
 * @deprecated 3.1
4392
 * @see Utiliser le filtre `icone_verticale`
4393
 *
4394
 * @param string $lien
4395
 *     URL du lien
4396
 * @param string $texte
4397
 *     Texte du lien
4398
 * @param string $fond
4399
 *     Nom de l'image utilisée
4400
 * @param string $align
4401
 *     Classe CSS d'alignement (`left`, `right`, `center`)
4402
 * @param string $fonction
4403
 *     Fonction du lien (`edit`, `new`, `del`)
4404
 * @param string $class
4405
 *     Classe CSS à ajouter
4406
 * @param string $javascript
4407
 *     Javascript ajouté sur le lien
4408
 * @return string
4409
 *     Code HTML du lien
4410
 */
4411
function filtre_icone_dist($lien, $texte, $fond, $align = "", $fonction = "", $class = "", $javascript = "") {
4412
	return icone_base($lien, $texte, $fond, $fonction, "verticale $align $class", $javascript);
4413
}
4414
4415
4416
/**
4417
 * Explose un texte en tableau suivant un séparateur
4418
 *
4419
 * @note
4420
 *     Inverse l'écriture de la fonction PHP de même nom
4421
 *     pour que le filtre soit plus pratique dans les squelettes
4422
 *
4423
 * @filtre
4424
 * @example
4425
 *     ```
4426
 *     [(#GET{truc}|explode{-})]
4427
 *     ```
4428
 *
4429
 * @param string $a Texte
4430
 * @param string $b Séparateur
4431
 * @return array Liste des éléments
4432
 */
4433
function filtre_explode_dist($a, $b) { return explode($b, $a); }
4434
4435
/**
4436
 * Implose un tableau en chaine en liant avec un séparateur
4437
 *
4438
 * @note
4439
 *     Inverse l'écriture de la fonction PHP de même nom
4440
 *     pour que le filtre soit plus pratique dans les squelettes
4441
 *
4442
 * @filtre
4443
 * @example
4444
 *     ```
4445
 *     [(#GET{truc}|implode{-})]
4446
 *     ```
4447
 *
4448
 * @param array $a Tableau
4449
 * @param string $b Séparateur
4450
 * @return string Texte
4451
 */
4452
function filtre_implode_dist($a, $b) { return is_array($a) ? implode($b, $a) : $a; }
4453
4454
/**
4455
 * Produire les styles privés qui associent item de menu avec icone en background
4456
 *
4457
 * @return string Code CSS
4458
 */
4459
function bando_images_background() {
4460
	include_spip('inc/bandeau');
4461
	// recuperer tous les boutons et leurs images
4462
	$boutons = definir_barre_boutons(definir_barre_contexte(), true, false);
4463
4464
	$res = "";
4465
	foreach ($boutons as $page => $detail) {
4466
		$selecteur = (in_array($page, array('outils_rapides', 'outils_collaboratifs')) ? "" : ".navigation_avec_icones ");
4467
		if (is_array($detail->sousmenu)) {
4468
			foreach ($detail->sousmenu as $souspage => $sousdetail) {
4469
				if ($sousdetail->icone and strlen(trim($sousdetail->icone))) {
4470
					$res .= "\n$selecteur.bando2_$souspage {background-image:url(" . $sousdetail->icone . ");}";
4471
				}
4472
			}
4473
		}
4474
	}
4475
4476
	return $res;
4477
}
4478
4479
/**
4480
 * Générer un bouton_action
4481
 * utilisé par #BOUTON_ACTION
4482
 *
4483
 * @param string $libelle
4484
 *   Libellé du bouton
4485
 * @param string $url
4486
 *   URL d'action sécurisée, généralement obtenue avec generer_action_auteur()
4487
 * @param string $class
4488
 *   Classes à ajouter au bouton, à l'exception de `ajax` qui est placé sur le formulaire.
4489
 * @param string $confirm
4490
 *   Message de confirmation oui/non avant l'action
4491
 * @param string $title
4492
 *   Attribut title à ajouter au bouton
4493
 * @param string $callback
4494
 *   Callback js a appeler lors de l'évènement action et avant execution de l'action
4495
 *   (ou après confirmation éventuelle si $confirm est non vide).
4496
 *   Si la callback renvoie false, elle annule le déclenchement de l'action.
4497
 * @return string
4498
 */
4499
function bouton_action($libelle, $url, $class = '', $confirm = '', $title = '', $callback = '') {
4500
4501
	// Classes : dispatcher `ajax` sur le formulaire
4502
	$class_form = '';
4503
	if (strpos($class, 'ajax') !== false) {
4504
		$class_form = 'ajax';
4505
		$class = str_replace('ajax', '', $class);
4506
	}
4507
	$class_btn = 'submit ' . trim($class);
4508
4509
	if ($confirm) {
4510
		$confirm = "confirm(\"" . attribut_html($confirm) . "\")";
4511
		if ($callback) {
4512
			$callback = "$confirm?($callback):false";
4513
		} else {
4514
			$callback = $confirm;
4515
		}
4516
	}
4517
	$onclick = $callback ? " onclick='return " . addcslashes($callback, "'") . "'" : "";
4518
	$title = $title ? " title='$title'" : '';
4519
4520
	return "<form class='bouton_action_post $class_form' method='post' action='$url'><div>" . form_hidden($url)
4521
	. "<button type='submit' class='$class_btn'$title$onclick>$libelle</button></div></form>";
4522
}
4523
4524
/**
4525
 * Donner n'importe quelle information sur un objet de maniere generique.
4526
 *
4527
 * La fonction va gerer en interne deux cas particuliers les plus utilises :
4528
 * l'URL et le titre (qui n'est pas forcemment le champ SQL "titre").
4529
 *
4530
 * On peut ensuite personnaliser les autres infos en creant une fonction
4531
 * generer_<nom_info>_entite($id_objet, $type_objet, $ligne).
4532
 * $ligne correspond a la ligne SQL de tous les champs de l'objet, les fonctions
4533
 * de personnalisation n'ont donc pas a refaire de requete.
4534
 *
4535
 * @param int $id_objet
4536
 * @param string $type_objet
4537
 * @param string $info
4538
 * @param string $etoile
4539
 * @param array $params
4540
 *     Tableau de paramètres supplémentaires transmis aux fonctions generer_xxx
4541
 * @return string
4542
 */
4543
function generer_info_entite($id_objet, $type_objet, $info, $etoile = '', $params = []) {
4544
	static $trouver_table = null;
4545
	static $objets;
4546
4547
	// On verifie qu'on a tout ce qu'il faut
4548
	$id_objet = intval($id_objet);
4549
	if (!($id_objet and $type_objet and $info)) {
4550
		return '';
4551
	}
4552
4553
	// si on a deja note que l'objet n'existe pas, ne pas aller plus loin
4554
	if (isset($objets[$type_objet]) and $objets[$type_objet] === false) {
4555
		return '';
4556
	}
4557
4558
	// Si on demande l'url, on retourne direct la fonction
4559
	if ($info == 'url') {
4560
		return generer_url_entite($id_objet, $type_objet, ...$params);
4561
	}
4562
4563
	// Sinon on va tout chercher dans la table et on garde en memoire
4564
	$demande_titre = ($info === 'titre');
4565
	$demande_introduction = ($info === 'introduction');
4566
4567
	// 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
4568
	if (!isset($objets[$type_objet][$id_objet])
4569
		or
4570
		($demande_titre and !isset($objets[$type_objet][$id_objet]['titre']))
4571
	) {
4572
		if (!$trouver_table) {
4573
			$trouver_table = charger_fonction('trouver_table', 'base');
4574
		}
4575
		$desc = $trouver_table(table_objet_sql($type_objet));
4576
		if (!$desc) {
4577
			return $objets[$type_objet] = false;
4578
		}
4579
4580
		// Si on demande le titre, on le gere en interne
4581
		$champ_titre = "";
4582
		if ($demande_titre) {
4583
			// si pas de titre declare mais champ titre, il sera peuple par le select *
4584
			$champ_titre = (!empty($desc['titre'])) ? ', ' . $desc['titre'] : '';
4585
		}
4586
		include_spip('base/abstract_sql');
4587
		include_spip('base/connect_sql');
4588
		$objets[$type_objet][$id_objet] = sql_fetsel(
4589
			'*' . $champ_titre,
4590
			$desc['table_sql'],
4591
			id_table_objet($type_objet) . ' = ' . intval($id_objet)
4592
		);
4593
4594
		// Toujours noter la longueur d'introduction, même si pas demandé cette fois-ci
4595
		$objets[$type_objet]['introduction_longueur'] = $desc['introduction_longueur'] ?? null;
4596
	}
4597
4598
	// Pour les fonction generer_xxx, si on demande l'introduction,
4599
	// ajouter la longueur au début des params supplémentaires
4600
	if ($demande_introduction) {
4601
		$introduction_longueur = $objets[$type_objet]['introduction_longueur'];
4602
		array_unshift($params, $introduction_longueur);
4603
	}
4604
4605
	// Si la fonction generer_TRUC_TYPE existe, on l'utilise pour formater $info_generee
4606
	if ($generer = charger_fonction("generer_${info}_${type_objet}", '', true)) {
4607
		$info_generee = $generer($id_objet, $objets[$type_objet][$id_objet], ...$params);
4608
	} // Si la fonction generer_TRUC_entite existe, on l'utilise pour formater $info_generee
4609
	else {
4610
		if ($generer = charger_fonction("generer_${info}_entite", '', true)) {
4611
			$info_generee = $generer($id_objet, $type_objet, $objets[$type_objet][$id_objet], ...$params);
4612
		} // Sinon on prend directement le champ SQL tel quel
4613
		else {
4614
			$info_generee = (isset($objets[$type_objet][$id_objet][$info]) ? $objets[$type_objet][$id_objet][$info] : '');
4615
		}
4616
	}
4617
4618
	// On va ensuite appliquer les traitements automatiques si besoin
4619
	if (!$etoile) {
4620
		// FIXME: on fournit un ENV minimum avec id et type et connect=''
4621
		// mais ce fonctionnement est a ameliorer !
4622
		$info_generee = appliquer_traitement_champ($info_generee, $info, table_objet($type_objet),
4623
			array('id_objet' => $id_objet, 'objet' => $type_objet, ''));
4624
	}
4625
4626
	return $info_generee;
4627
}
4628
4629
/**
4630
 * Fonction privée pour donner l'introduction d'un objet de manière générique.
4631
 *
4632
 * Cette fonction est mutualisée entre les balises #INTRODUCTION et #INFO_INTRODUCTION.
4633
 * Elle se charge de faire le tri entre descriptif, texte et chapo,
4634
 * et normalise les paramètres pour la longueur et la suite.
4635
 * Ensuite elle fait appel au filtre 'introduction' qui construit celle-ci à partir de ces données.
4636
 *
4637
 * @uses filtre_introduction_dist()
4638
 * @see generer_info_entite()
4639
 * @see balise_INTRODUCTION_dist()
4640
 *
4641
 * @param int $id_objet
4642
 *     Numéro de l'objet
4643
 * @param string $type_objet
4644
 *     Type d'objet
4645
 * @param string $desc
0 ignored issues
show
Bug introduced by
There is no parameter named $desc. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
4646
 *     Ligne SQL de l'objet avec au moins descriptif, texte et chapo
4647
 * @param int $introduction_longueur
0 ignored issues
show
Documentation introduced by
Should the type for parameter $introduction_longueur not be 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...
4648
 *     Longueur de l'introduction donnée dans la description de la table l'objet
4649
 * @param int|string $longueur_ou_suite
0 ignored issues
show
Documentation introduced by
Should the type for parameter $longueur_ou_suite not be integer|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...
4650
 *     Longueur de l'introduction OU points de suite si on coupe
4651
 * @param string $suite
0 ignored issues
show
Documentation introduced by
Should the type for parameter $suite 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...
4652
 *     Points de suite si on coupe
4653
 * @param string $connect
4654
 *     Nom du connecteur à la base de données
4655
 * @return string
4656
 */
4657
function generer_introduction_entite($id_objet, $type_objet, $ligne_sql, $introduction_longueur = null, $longueur_ou_suite = null, $suite = null, $connect = '') {
0 ignored issues
show
Unused Code introduced by
The parameter $id_objet 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...
Unused Code introduced by
The parameter $type_objet 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...
4658
4659
	$descriptif = $ligne_sql['descriptif'] ?? '';
4660
	$texte = $ligne_sql['texte'] ?? '';
4661
	// En absence de descriptif, on se rabat sur chapo + texte
4662
	if (isset($ligne_sql['chapo'])) {
4663
		$chapo = $ligne_sql['chapo'];
4664
		$texte = strlen($descriptif) ?
4665
			'' :
4666
			"$chapo \n\n $texte";
4667
	}
4668
4669
	// Longueur en paramètre, sinon celle renseignée dans la description de l'objet, sinon valeur en dur
4670
	if (!intval($longueur_ou_suite)) {
4671
		$longueur = intval($introduction_longueur ?: 600);
4672
	} else {
4673
		$longueur = intval($longueur_ou_suite);
4674
	}
4675
4676
	// On peut optionnellement passer la suite en 1er paramètre de la balise
4677
	// Ex : #INTRODUCTION{...}
4678
	if (
4679
		is_null($suite)
4680
		and !intval($longueur_ou_suite)
4681
	) {
4682
		$suite = $longueur_ou_suite;
4683
	}
4684
4685
	$f = chercher_filtre('introduction');
4686
	$introduction = $f($descriptif, $texte, $longueur, $connect, $suite);
4687
4688
	return $introduction;
4689
}
4690
4691
/**
4692
 * Appliquer a un champ SQL le traitement qui est configure pour la balise homonyme dans les squelettes
4693
 *
4694
 * @param string $texte
4695
 * @param string $champ
4696
 * @param string $table_objet
4697
 * @param array $env
4698
 * @param string $connect
4699
 * @return string
4700
 */
4701
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...
4702
	if (!$champ) {
4703
		return $texte;
4704
	}
4705
	
4706
	// On charge toujours les filtres de texte car la majorité des traitements les utilisent
4707
	// et il ne faut pas partir du principe que c'est déjà chargé (form ajax, etc)
4708
	include_spip('inc/texte');
4709
	
4710
	$champ = strtoupper($champ);
4711
	$traitements = isset($GLOBALS['table_des_traitements'][$champ]) ? $GLOBALS['table_des_traitements'][$champ] : false;
4712
	if (!$traitements or !is_array($traitements)) {
4713
		return $texte;
4714
	}
4715
4716
	$traitement = '';
4717
	if ($table_objet and (!isset($traitements[0]) or count($traitements) > 1)) {
4718
		// necessaire pour prendre en charge les vieux appels avec un table_objet_sql en 3e arg
4719
		$table_objet = table_objet($table_objet);
4720
		if (isset($traitements[$table_objet])) {
4721
			$traitement = $traitements[$table_objet];
4722
		}
4723
	}
4724
	if (!$traitement and isset($traitements[0])) {
4725
		$traitement = $traitements[0];
4726
	}
4727
	// (sinon prendre le premier de la liste par defaut ?)
4728
4729
	if (!$traitement) {
4730
		return $texte;
4731
	}
4732
4733
	$traitement = str_replace('%s', "'" . texte_script($texte) . "'", $traitement);
4734
4735
	// Fournir $connect et $Pile[0] au traitement si besoin
4736
	$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...
4737
	eval("\$texte = $traitement;");
4738
4739
	return $texte;
4740
}
4741
4742
4743
/**
4744
 * Generer un lien (titre clicable vers url) vers un objet
4745
 *
4746
 * @param int $id_objet
4747
 * @param $objet
4748
 * @param int $longueur
4749
 * @param null|string $connect
4750
 * @return string
4751
 */
4752
function generer_lien_entite($id_objet, $objet, $longueur = 80, $connect = null) {
4753
	include_spip('inc/liens');
4754
	$titre = traiter_raccourci_titre($id_objet, $objet, $connect);
4755
	// lorsque l'objet n'est plus declare (plugin desactive par exemple)
4756
	// le raccourcis n'est plus valide
4757
	$titre = isset($titre['titre']) ? typo($titre['titre']) : '';
4758
	// on essaye avec generer_info_entite ?
4759
	if (!strlen($titre) and !$connect) {
4760
		$titre = generer_info_entite($id_objet, $objet, 'titre');
4761
	}
4762
	if (!strlen($titre)) {
4763
		$titre = _T('info_sans_titre');
4764
	}
4765
	$url = generer_url_entite($id_objet, $objet, '', '', $connect);
4766
4767
	return "<a href='$url' class='$objet'>" . couper($titre, $longueur) . "</a>";
4768
}
4769
4770
4771
/**
4772
 * Englobe (Wrap) un texte avec des balises
4773
 *
4774
 * @example `wrap('mot','<b>')` donne `<b>mot</b>'`
4775
 *
4776
 * @filtre
4777
 * @uses extraire_balises()
4778
 *
4779
 * @param string $texte
4780
 * @param string $wrap
4781
 * @return string
4782
 */
4783
function wrap($texte, $wrap) {
4784
	$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...
4785
	if (preg_match_all(",<([a-z]\w*)\b[^>]*>,UimsS", $wrap, $regs, PREG_PATTERN_ORDER)) {
4786
		$texte = $wrap . $texte;
4787
		$regs = array_reverse($regs[1]);
4788
		$wrap = "</" . implode("></", $regs) . ">";
4789
		$texte = $texte . $wrap;
4790
	}
4791
4792
	return $texte;
4793
}
4794
4795
4796
/**
4797
 * afficher proprement n'importe quoi
4798
 * On affiche in fine un pseudo-yaml qui premet de lire humainement les tableaux et de s'y reperer
4799
 *
4800
 * Les textes sont retournes avec simplement mise en forme typo
4801
 *
4802
 * 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
4803
 * les tableaux-listes (qui n'ont que des cles numeriques), sont affiches sous forme de liste separee par des virgules :
4804
 * c'est VOULU !
4805
 *
4806
 * @param $u
4807
 * @param string $join
4808
 * @param int $indent
4809
 * @return array|mixed|string
4810
 */
4811
function filtre_print_dist($u, $join = "<br />", $indent = 0) {
4812
	if (is_string($u)) {
4813
		$u = typo($u);
4814
4815
		return $u;
4816
	}
4817
4818
	// caster $u en array si besoin
4819
	if (is_object($u)) {
4820
		$u = (array)$u;
4821
	}
4822
4823
	if (is_array($u)) {
4824
		$out = "";
4825
		// toutes les cles sont numeriques ?
4826
		// et aucun enfant n'est un tableau
4827
		// liste simple separee par des virgules
4828
		$numeric_keys = array_map('is_numeric', array_keys($u));
4829
		$array_values = array_map('is_array', $u);
4830
		$object_values = array_map('is_object', $u);
4831
		if (array_sum($numeric_keys) == count($numeric_keys)
4832
			and !array_sum($array_values)
4833
			and !array_sum($object_values)
4834
		) {
4835
			return join(", ", array_map('filtre_print_dist', $u));
4836
		}
4837
4838
		// sinon on passe a la ligne et on indente
4839
		$i_str = str_pad("", $indent, " ");
4840
		foreach ($u as $k => $v) {
4841
			$out .= $join . $i_str . "$k: " . filtre_print_dist($v, $join, $indent + 2);
4842
		}
4843
4844
		return $out;
4845
	}
4846
4847
	// on sait pas quoi faire...
4848
	return $u;
4849
}
4850
4851
4852
/**
4853
 * Renvoyer l'info d'un objet
4854
 * telles que definies dans declarer_tables_objets_sql
4855
 *
4856
 * @param string $objet
4857
 * @param string $info
4858
 * @return string|array
4859
 */
4860
function objet_info($objet, $info) {
4861
	$table = table_objet_sql($objet);
4862
	$infos = lister_tables_objets_sql($table);
4863
4864
	return (isset($infos[$info]) ? $infos[$info] : '');
4865
}
4866
4867
/**
4868
 * Filtre pour afficher 'Aucun truc' ou '1 truc' ou 'N trucs'
4869
 * avec la bonne chaîne de langue en fonction de l'objet utilisé
4870
 *
4871
 * @param int $nb
4872
 *     Nombre d'éléments
4873
 * @param string $objet
4874
 *     Objet
4875
 * @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...
4876
 *     Texte traduit du comptage, tel que '3 articles'
4877
 */
4878
function objet_afficher_nb($nb, $objet) {
4879
	if (!$nb) {
4880
		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...
4881
	} else {
4882
		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...
4883
	}
4884
}
4885
4886
/**
4887
 * Filtre pour afficher l'img icone d'un objet
4888
 *
4889
 * @param string $objet
4890
 * @param int $taille
4891
 * @param string $class
4892
 * @return string
4893
 */
4894
function objet_icone($objet, $taille = 24, $class='') {
4895
	$icone = objet_info($objet, 'icone_objet') . "-" . $taille . ".png";
4896
	$icone = chemin_image($icone);
4897
	$balise_img = charger_filtre('balise_img');
4898
4899
	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...
4900
}
4901
4902
/**
4903
 * Renvoyer une traduction d'une chaine de langue contextuelle à un objet si elle existe,
4904
 * la traduction de la chaine generique
4905
 *
4906
 * Ex : [(#ENV{objet}|objet_label{trad_reference})]
4907
 * va chercher si une chaine objet:trad_reference existe et renvoyer sa trad le cas echeant
4908
 * sinon renvoie la trad de la chaine trad_reference
4909
 * Si la chaine fournie contient un prefixe il est remplacé par celui de l'objet pour chercher la chaine contextuelle
4910
 *
4911
 * Les arguments $args et $options sont ceux de la fonction _T
4912
 *
4913
 * @param string $objet
4914
 * @param string $chaine
4915
 * @param array $args
4916
 * @param array $options
4917
 * @return string
4918
 */
4919
function objet_T($objet, $chaine, $args = array(), $options = array()){
4920
	$chaine = explode(':',$chaine);
4921
	if ($t = _T($objet . ':' . end($chaine), $args, array_merge($options, array('force'=>false)))) {
4922
		return $t;
4923
	}
4924
	$chaine = implode(':',$chaine);
4925
	return _T($chaine, $args, $options);
4926
}
4927
4928
/**
4929
 * Fonction de secours pour inserer le head_css de facon conditionnelle
4930
 *
4931
 * Appelée en filtre sur le squelette qui contient #INSERT_HEAD,
4932
 * elle vérifie l'absence éventuelle de #INSERT_HEAD_CSS et y suplée si besoin
4933
 * pour assurer la compat avec les squelettes qui n'utilisent pas.
4934
 *
4935
 * @param string $flux Code HTML
4936
 * @return string      Code HTML
4937
 */
4938
function insert_head_css_conditionnel($flux) {
4939
	if (strpos($flux, '<!-- insert_head_css -->') === false
4940
		and $p = strpos($flux, '<!-- insert_head -->')
4941
	) {
4942
		// plutot avant le premier js externe (jquery) pour etre non bloquant
4943
		if ($p1 = stripos($flux, '<script src=') and $p1 < $p) {
4944
			$p = $p1;
4945
		}
4946
		$flux = substr_replace($flux, pipeline('insert_head_css', '<!-- insert_head_css -->'), $p, 0);
4947
	}
4948
4949
	return $flux;
4950
}
4951
4952
/**
4953
 * Produire un fichier statique à partir d'un squelette dynamique
4954
 *
4955
 * Permet ensuite à Apache de le servir en statique sans repasser
4956
 * par spip.php à chaque hit sur le fichier.
4957
 *
4958
 * Formats supportés : html, css, js, json, xml, svg
4959
 *
4960
 * Si le format est passé dans `contexte['format']`, on l'utilise
4961
 * sinon on regarde l'extension du fond, sinon on utilise "html"
4962
 *
4963
 * @uses urls_absolues_css()
4964
 *
4965
 * @param string $fond
4966
 * @param array $contexte
4967
 * @param array $options
4968
 * @param string $connect
4969
 * @return string
4970
 */
4971
function produire_fond_statique($fond, $contexte = array(), $options = array(), $connect = '') {
4972
	if (isset($contexte['format'])) {
4973
		$extension = $contexte['format'];
4974
		unset($contexte['format']);
4975
	} else {
4976
		$extension = 'html';
4977
		if (preg_match(',[.](css|js|json|xml|svg)$,', $fond, $m)) {
4978
			$extension = $m[1];
4979
		}
4980
	}
4981
	// recuperer le contenu produit par le squelette
4982
	$options['raw'] = true;
4983
	$cache = recuperer_fond($fond, $contexte, $options, $connect);
4984
4985
	// calculer le nom de la css
4986
	$dir_var = sous_repertoire(_DIR_VAR, 'cache-' . $extension);
4987
	$nom_safe = preg_replace(",\W,", '_', str_replace('.', '_', $fond));
4988
	$contexte_implicite = calculer_contexte_implicite();
4989
4990
	// par defaut on hash selon les contextes qui sont a priori moins variables
4991
	// mais on peut hasher selon le contenu a la demande, si plusieurs contextes produisent un meme contenu
4992
	// reduit la variabilite du nom et donc le nombre de css concatenees possibles in fine
4993
	if (isset($options['hash_on_content']) and $options['hash_on_content']) {
4994
		$hash = md5($contexte_implicite['host'] . '::'. $cache);
4995
	}
4996
	else {
4997
		unset($contexte_implicite['notes']); // pas pertinent pour signaler un changeemnt de contenu pour des css/js
4998
		ksort($contexte);
4999
		$hash = md5($fond . json_encode($contexte_implicite) . json_encode($contexte) . $connect);
5000
	}
5001
	$filename = $dir_var . $extension . "dyn-$nom_safe-" . substr($hash, 0, 8) . ".$extension";
5002
5003
	// mettre a jour le fichier si il n'existe pas
5004
	// ou trop ancien
5005
	// le dernier fichier produit est toujours suffixe par .last
5006
	// et recopie sur le fichier cible uniquement si il change
5007
	if (!file_exists($filename)
5008
		or !file_exists($filename . ".last")
5009
		or (isset($cache['lastmodified']) and $cache['lastmodified'] and filemtime($filename . ".last") < $cache['lastmodified'])
5010
		or (defined('_VAR_MODE') and _VAR_MODE == 'recalcul')
5011
	) {
5012
		$contenu = $cache['texte'];
5013
		// passer les urls en absolu si c'est une css
5014
		if ($extension == "css") {
5015
			$contenu = urls_absolues_css($contenu,
5016
				test_espace_prive() ? generer_url_ecrire('accueil') : generer_url_public($fond));
5017
		}
5018
5019
		$comment = '';
5020
		// ne pas insérer de commentaire sur certains formats
5021
		if (!in_array($extension, ['json', 'xml', 'svg'])) {
5022
			$comment = "/* #PRODUIRE{fond=$fond";
5023
			foreach ($contexte as $k => $v) {
5024
				$comment .= ",$k=$v";
5025
			}
5026
			// pas de date dans le commentaire car sinon ca invalide le md5 et force la maj
5027
			// mais on peut mettre un md5 du contenu, ce qui donne un aperu rapide si la feuille a change ou non
5028
			$comment .= "}\n   md5:" . md5($contenu) . " */\n";
5029
		}
5030
		// et ecrire le fichier si il change
5031
		ecrire_fichier_calcule_si_modifie($filename, $comment . $contenu, false, true);
5032
	}
5033
5034
	return timestamp($filename);
5035
}
5036
5037
/**
5038
 * Ajouter un timestamp a une url de fichier
5039
 * [(#CHEMIN{monfichier}|timestamp)]
5040
 *
5041
 * @param string $fichier
5042
 *    Le chemin du fichier sur lequel on souhaite ajouter le timestamp
5043
 * @return string
5044
 *    $fichier auquel on a ajouté le timestamp
5045
 */
5046
function timestamp($fichier) {
5047
	if (!$fichier
5048
		or !file_exists($fichier)
5049
		or !$m = filemtime($fichier)
5050
	) {
5051
		return $fichier;
5052
	}
5053
5054
	return "$fichier?$m";
5055
}
5056
5057
/**
5058
 * Supprimer le timestamp d'une url
5059
 *
5060
 * @param string $url
5061
 * @return string
5062
 */
5063
function supprimer_timestamp($url) {
5064
	if (strpos($url, "?") === false) {
5065
		return $url;
5066
	}
5067
5068
	return preg_replace(",\?[[:digit:]]+$,", "", $url);
5069
}
5070
5071
/**
5072
 * Nettoyer le titre d'un email
5073
 *
5074
 * Éviter une erreur lorsqu'on utilise `|nettoyer_titre_email` dans un squelette de mail
5075
 *
5076
 * @filtre
5077
 * @uses nettoyer_titre_email()
5078
 *
5079
 * @param string $titre
5080
 * @return string
5081
 */
5082
function filtre_nettoyer_titre_email_dist($titre) {
5083
	include_spip('inc/envoyer_mail');
5084
5085
	return nettoyer_titre_email($titre);
5086
}
5087
5088
/**
5089
 * Afficher le sélecteur de rubrique
5090
 *
5091
 * Il permet de placer un objet dans la hiérarchie des rubriques de SPIP
5092
 *
5093
 * @uses chercher_rubrique()
5094
 *
5095
 * @param string $titre
5096
 * @param int $id_objet
5097
 * @param int $id_parent
5098
 * @param string $objet
5099
 * @param int $id_secteur
5100
 * @param bool $restreint
5101
 * @param bool $actionable
5102
 *   true : fournit le selecteur dans un form directement postable
5103
 * @param bool $retour_sans_cadre
5104
 * @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...
5105
 */
5106
function filtre_chercher_rubrique_dist(
5107
	$titre,
5108
	$id_objet,
5109
	$id_parent,
5110
	$objet,
5111
	$id_secteur,
5112
	$restreint,
5113
	$actionable = false,
5114
	$retour_sans_cadre = false
5115
) {
5116
	include_spip('inc/filtres_ecrire');
5117
5118
	return chercher_rubrique($titre, $id_objet, $id_parent, $objet, $id_secteur, $restreint, $actionable,
5119
		$retour_sans_cadre);
5120
}
5121
5122
/**
5123
 * Rediriger une page suivant une autorisation,
5124
 * et ce, n'importe où dans un squelette, même dans les inclusions.
5125
 *
5126
 * En l'absence de redirection indiquée, la fonction redirige par défaut
5127
 * sur une 403 dans l'espace privé et 404 dans l'espace public.
5128
 *
5129
 * @example
5130
 *     ```
5131
 *     [(#AUTORISER{non}|sinon_interdire_acces)]
5132
 *     [(#AUTORISER{non}|sinon_interdire_acces{#URL_PAGE{login}, 401})]
5133
 *     ```
5134
 *
5135
 * @filtre
5136
 * @param bool $ok
5137
 *     Indique si l'on doit rediriger ou pas
5138
 * @param string $url
5139
 *     Adresse eventuelle vers laquelle rediriger
5140
 * @param int $statut
5141
 *     Statut HTML avec lequel on redirigera
5142
 * @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...
5143
 *     message d'erreur
5144
 * @return string|void
5145
 *     Chaîne vide si l'accès est autorisé
5146
 */
5147
function sinon_interdire_acces($ok = false, $url = '', $statut = 0, $message = null) {
5148
	if ($ok) {
5149
		return '';
5150
	}
5151
5152
	// Vider tous les tampons
5153
	$level = @ob_get_level();
5154
	while ($level--) {
5155
		@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...
5156
	}
5157
5158
	include_spip('inc/headers');
5159
5160
	// S'il y a une URL, on redirige (si pas de statut, la fonction mettra 302 par défaut)
5161
	if ($url) {
5162
		redirige_par_entete($url, '', $statut);
5163
	}
5164
5165
	// ecriture simplifiee avec message en 3eme argument (= statut 403)
5166
	if (!is_numeric($statut) and is_null($message)) {
5167
		$message = $statut;
5168
		$statut = 0;
5169
	}
5170
	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...
5171
		$message = '';
5172
	}
5173
	$statut = intval($statut);
5174
5175
	// Si on est dans l'espace privé, on génère du 403 Forbidden par defaut ou du 404
5176
	if (test_espace_prive()) {
5177
		if (!$statut or !in_array($statut, array(404, 403))) {
5178
			$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...
5179
		}
5180
		http_status(403);
5181
		$echec = charger_fonction('403', 'exec');
5182
		$echec($message);
5183
	} else {
5184
		// Sinon dans l'espace public on redirige vers une 404 par défaut, car elle toujours présente normalement
5185
		if (!$statut) {
5186
			$statut = 404;
5187
		}
5188
		// Dans tous les cas on modifie l'entité avec ce qui est demandé
5189
		http_status($statut);
5190
		// Si le statut est une erreur et qu'il n'y a pas de redirection on va chercher le squelette du même nom
5191
		if ($statut >= 400) {
5192
			echo recuperer_fond("$statut", array('erreur' => $message));
5193
		}
5194
	}
5195
5196
5197
	exit;
5198
}
5199
5200
/**
5201
 * Assurer le fonctionnement de |compacte meme sans l'extension compresseur
5202
 *
5203
 * @param string $source
5204
 * @param null|string $format
5205
 * @return string
5206
 */
5207
function filtre_compacte_dist($source, $format = null) {
5208
	if (function_exists('compacte')) {
5209
		return compacte($source, $format);
5210
	}
5211
5212
	return $source;
5213
}
5214
5215
5216
/**
5217
 * Afficher partiellement un mot de passe que l'on ne veut pas rendre lisible par un champ hidden
5218
 * @param string $passe
5219
 * @param bool $afficher_partiellement
5220
 * @param int|null $portion_pourcent
5221
 * @return string
5222
 */
5223
function spip_affiche_mot_de_passe_masque($passe, $afficher_partiellement = false, $portion_pourcent = null) {
5224
	$l = strlen($passe);
5225
5226
	if ($l<=8 or !$afficher_partiellement){
5227
		if (!$l) {
5228
			return ''; // montrer qu'il y a pas de mot de passe si il y en a pas
5229
		}
5230
		return str_pad('',$afficher_partiellement ? $l : 16,'*');
5231
	}
5232
5233
	if (is_null($portion_pourcent)) {
5234
		if (!defined('_SPIP_AFFICHE_MOT_DE_PASSE_MASQUE_PERCENT')) {
5235
			define('_SPIP_AFFICHE_MOT_DE_PASSE_MASQUE_PERCENT', 20); // 20%
5236
		}
5237
		$portion_pourcent = _SPIP_AFFICHE_MOT_DE_PASSE_MASQUE_PERCENT;
5238
	}
5239
	if ($portion_pourcent >= 100) {
5240
		return $passe;
5241
	}
5242
	$e = intval(ceil($l * $portion_pourcent / 100 / 2));
5243
	$e = max($e, 0);
5244
	$mid = str_pad('',$l-2*$e,'*');
5245
	if ($e>0 and strlen($mid)>8){
5246
		$mid = '***...***';
5247
	}
5248
	return substr($passe,0,$e) . $mid . ($e > 0 ? substr($passe,-$e) : '');
5249
}
5250
5251
5252
/**
5253
 * Cette fonction permet de transformer un texte clair en nom court pouvant servir d'identifiant, class, id, url...
5254
 * en ne conservant que des caracteres alphanumeriques et un separateur
5255
 *
5256
 * @param string $texte
5257
 *   Texte à transformer en nom machine
5258
 * @param string $type
5259
 *
5260
 * @param array $options
5261
 *   string separateur : par défaut, un underscore `_`.
5262
 *   int longueur_maxi : par defaut 60
5263
 *   int longueur_mini : par defaut 0
5264
 *
5265
 * @return string
5266
 */
5267
function identifiant_slug($texte, $type = '', $options = array()) {
5268
5269
	$original = $texte;
5270
	$separateur = (isset($options['separateur'])?$options['separateur']:'_');
5271
	$longueur_maxi = (isset($options['longueur_maxi'])?$options['longueur_maxi']:60);
5272
	$longueur_mini = (isset($options['longueur_mini'])?$options['longueur_mini']:0);
5273
5274
	if (!function_exists('translitteration')) {
5275
		include_spip('inc/charsets');
5276
	}
5277
5278
	// pas de balise html
5279
	if (strpos($texte, '<') !== false) {
5280
		$texte = strip_tags($texte);
5281
	}
5282
	if (strpos($texte, '&') !== false) {
5283
		$texte = unicode2charset($texte);
5284
	}
5285
	// On enlève les espaces indésirables
5286
	$texte = trim($texte);
5287
5288
	// On enlève les accents et cie
5289
	$texte = translitteration($texte);
5290
5291
	// On remplace tout ce qui n'est pas un mot par un séparateur
5292
	$texte = preg_replace(',[\W_]+,ms', $separateur, $texte);
5293
5294
	// nettoyer les doubles occurences du separateur si besoin
5295
	while (strpos($texte, "$separateur$separateur") !== false) {
5296
		$texte = str_replace("$separateur$separateur", $separateur, $texte);
5297
	}
5298
5299
	// pas de separateur au debut ni a la fin
5300
	$texte = trim($texte, $separateur);
5301
5302
	// en minuscules
5303
	$texte = strtolower($texte);
5304
5305
	switch ($type) {
5306
		case 'class':
5307
		case 'id':
5308
		case 'anchor':
5309
			if (preg_match(',^\d,', $texte)) {
5310
				$texte = substr($type, 0, 1).$texte;
5311
			}
5312
	}
5313
5314
	if (strlen($texte)>$longueur_maxi) {
5315
		$texte = substr($texte, 0, $longueur_maxi);
5316
	}
5317
5318
	if (strlen($texte) < $longueur_mini and $longueur_mini < $longueur_maxi) {
5319
		if (preg_match(',^\d,', $texte)) {
5320
			$texte = ($type ? substr($type,0,1) : "s") . $texte;
5321
		}
5322
		$texte .= $separateur . md5($original);
5323
		$texte = substr($texte, 0, $longueur_mini);
5324
	}
5325
5326
	return $texte;
5327
}
5328
5329
5330
/**
5331
 * Prépare un texte pour un affichage en label ou titre
5332
 * 
5333
 * Enlève un ':' à la fin d'une chaine de caractère, ainsi que les espaces qui pourraient l'accompagner,
5334
 * Met la première lettre en majuscule (par défaut)
5335
 *
5336
 * Utile afficher dans un contexte de titre des chaines de langues qui contiennent des ':'
5337
 * 
5338
 * @note
5339
 *    Les chaines de langues (historiques) de SPIP contiennent parfois des ':', parfois pas. 
5340
 *    Les fonctions `label_nettoyer` et `label_ponctuer` permettent de choisir l'une ou l'autre selon le contexte.
5341
 *    Il convient dans les chaines de langues de labels de préférer les écritures sans ':'.
5342
 * 
5343
 * @see label_ponctuer()
5344
 * @exemple `<:info_maximum|label_nettoyer:>`
5345
 */
5346
function label_nettoyer(string $text, bool $ucfirst = true) : string {
5347
	$label = rtrim($text, " : \t\n\r\0\x0B\xc2\xa0");
5348
	if ($label and $label[-1] === ';') {
5349
		$label = preg_replace("#(\&nbsp;)+$#", "", $label);
5350
	}
5351
	if ($ucfirst) {
5352
		$label = spip_ucfirst($label);
5353
	}
5354
	return $label;
5355
}
5356
5357
/**
5358
 * Prépare un texte pour un affichage en label ou titre en ligne, systématiquement avec ' :' à la fin
5359
 * 
5360
 * Ajoute ' :' aux chaines qui n'en ont pas (ajoute le caractère adapté à la langue utilisée). 
5361
 * Passe la première lettre en majuscule par défaut.
5362
 * 
5363
 * @uses label_nettoyer()
5364
 * @exemple `<:info_maximum|label_ponctuer:>`
5365
 */
5366
function label_ponctuer(string $text, bool $ucfirst = true) : string {
5367
	$label = label_nettoyer($text, $ucfirst);
5368
	return _T('label_ponctuer', ['label' => $label]);
5369
}
5370
5371
5372
5373
/**
5374
 * Helper pour les filtres associés aux fonctions objet_lister_(parents|enfants)(_par_type)?
5375
 *
5376
 * @param string $objet
5377
 * @param int|string $id_objet
5378
 * @return array
5379
 */
5380
function helper_filtre_objet_lister_enfants_ou_parents($objet, $id_objet, $fonction) {
5381
	if (!in_array($fonction, ['objet_lister_parents', 'objet_lister_enfants', 'objet_lister_parents_par_type', 'objet_lister_enfants_par_type'])) {
5382
		return [];
5383
	}
5384
5385
	// compatibilite signature inversee
5386
	if (is_numeric($objet) and !is_numeric($id_objet)) {
5387
		list($objet, $id_objet) = [$id_objet, $objet];
5388
	}
5389
5390
	if (!function_exists($fonction)) {
5391
		include_spip('base/objets');
5392
	}
5393
	return $fonction($objet, $id_objet);
5394
}
5395
5396
5397
/**
5398
 * Cherche les contenus parents d'un objet
5399
 * Supporte l'ecriture inverseee sous la forme
5400
 * [(#ID_OBJET|objet_lister_parents{#OBJET})]
5401
 *
5402
 * @see objet_lister_parents()
5403
 * @param string $objet
5404
 * @param int|string $id_objet
5405
 * @return array
5406
 */
5407
function filtre_objet_lister_parents_dist($objet, $id_objet) {
5408
	return helper_filtre_objet_lister_enfants_ou_parents($objet, $id_objet, 'objet_lister_parents');
5409
}
5410
5411
/**
5412
 * Cherche les contenus parents d'un objet
5413
 * Supporte l'ecriture inverseee sous la forme
5414
 * [(#ID_OBJET|objet_lister_parents_par_type{#OBJET})]
5415
 *
5416
 * @see objet_lister_parents_par_type()
5417
 * @param string $objet
5418
 * @param int|string $id_objet
5419
 * @return array
5420
 */
5421
function filtre_objet_lister_parents_par_type_dist($objet, $id_objet) {
5422
	return helper_filtre_objet_lister_enfants_ou_parents($objet, $id_objet, 'objet_lister_parents_par_type');
5423
}
5424
5425
/**
5426
 * Cherche les contenus enfants d'un objet
5427
 * Supporte l'ecriture inverseee sous la forme
5428
 * [(#ID_OBJET|objet_lister_enfants{#OBJET})]
5429
 *
5430
 * @see objet_lister_enfants()
5431
 * @param string $objet
5432
 * @param int|string $id_objet
5433
 * @return array
5434
 */
5435
function filtre_objet_lister_enfants_dist($objet, $id_objet) {
5436
	return helper_filtre_objet_lister_enfants_ou_parents($objet, $id_objet, 'objet_lister_enfants');
5437
}
5438
5439
/**
5440
 * Cherche les contenus enfants d'un objet
5441
 * Supporte l'ecriture inverseee sous la forme
5442
 * [(#ID_OBJET|objet_lister_enfants_par_type{#OBJET})]
5443
 *
5444
 * @see objet_lister_enfants_par_type()
5445
 * @param string $objet
5446
 * @param int|string $id_objet
5447
 * @return array
5448
 */
5449
function filtre_objet_lister_enfants_par_type_dist($objet, $id_objet) {
5450
	return helper_filtre_objet_lister_enfants_ou_parents($objet, $id_objet, 'objet_lister_enfants_par_type');
5451
}
5452
5453