Completed
Push — master ( be10ed...127510 )
by cam
04:28
created

filtres.php ➔ lien_ou_expose()   C

Complexity

Conditions 14
Paths 48

Size

Total Lines 33

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 14
nc 48
nop 7
dl 0
loc 33
rs 6.2666
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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

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

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

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

Loading history...
75
 *     Fonction PHP correspondante du filtre demandé
76
 */
77
function chercher_filtre($fonc, $default = null) {
78
	if (!$fonc) {
79
		return $default;
80
	}
81
	// Cas des types mime, sans confondre avec les appels de fonction de classe
82
	// Foo::Bar
83
	// qui peuvent etre avec un namespace : space\Foo::Bar
84
	if (preg_match(',^[\w]+/,', $fonc)) {
85
		$nom = preg_replace(',\W,', '_', $fonc);
86
		$f = chercher_filtre($nom);
87
		// cas du sous-type MIME sans filtre associe, passer au type:
88
		// si filtre_text_plain pas defini, passe a filtre_text
89
		if (!$f and $nom !== $fonc) {
90
			$f = chercher_filtre(preg_replace(',\W.*$,', '', $fonc));
91
		}
92
93
		return $f;
94
	}
95
96
	include_fichiers_fonctions();
97
	foreach (array('filtre_' . $fonc, 'filtre_' . $fonc . '_dist', $fonc) as $f) {
98
		trouver_filtre_matrice($f); // charge des fichiers spécifiques éventuels
99
		// fonction ou name\space\fonction
100
		if (is_callable($f)) {
101
			return $f;
102
		}
103
		// méthode statique d'une classe Classe::methode ou name\space\Classe::methode
104
		elseif (false === strpos($f, '::') and is_callable(array($f))) {
105
			return $f;
106
		}
107
	}
108
109
	return $default;
110
}
111
112
/**
113
 * Applique un filtre
114
 *
115
 * Fonction générique qui prend en argument l’objet (texte, etc) à modifier
116
 * et le nom du filtre. Retrouve les arguments du filtre demandé dans les arguments
117
 * transmis à cette fonction, via func_get_args().
118
 *
119
 * @see filtrer() Assez proche
120
 *
121
 * @param mixed $arg
122
 *     Texte (le plus souvent) sur lequel appliquer le filtre
123
 * @param string $filtre
124
 *     Nom du filtre à appliquer
125
 * @param bool $force
0 ignored issues
show
Documentation introduced by
Should the type for parameter $force not be boolean|null?

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

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

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

Loading history...
126
 *     La fonction doit-elle retourner le texte ou rien si le filtre est absent ?
127
 * @return string
128
 *     Texte traité par le filtre si le filtre existe,
129
 *     Texte d'origine si le filtre est introuvable et si $force à `true`
130
 *     Chaîne vide sinon (filtre introuvable).
131
 **/
132
function appliquer_filtre($arg, $filtre, $force = null) {
133
	$f = chercher_filtre($filtre);
134
	if (!$f) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $f of type null|string is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

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

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

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

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

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

$a = canBeFalseAndNull();

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

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
136
			return '';
137
		} else {
138
			return $arg;
139
		}
140
	}
141
142
	$args = func_get_args();
143
	array_shift($args); // enlever $arg
144
	array_shift($args); // enlever $filtre
145
	array_unshift($args, $arg); // remettre $arg
146
	return call_user_func_array($f, $args);
147
}
148
149
/**
150
 * Retourne la version de SPIP
151
 *
152
 * Si l'on retrouve un numéro de révision GIT ou SVN, il est ajouté entre crochets.
153
 * Si effectivement le SPIP est installé par Git ou Svn, 'GIT' ou 'SVN' est ajouté avant sa révision.
154
 *
155
 * @global spip_version_affichee Contient la version de SPIP
156
 * @uses version_vcs_courante() Pour trouver le numéro de révision
157
 *
158
 * @return string
159
 *     Version de SPIP
160
 **/
161
function spip_version() {
162
	$version = $GLOBALS['spip_version_affichee'];
163
	if ($vcs_version = version_vcs_courante(_DIR_RACINE)) {
164
		$version .= " $vcs_version";
165
	}
166
167
	return $version;
168
}
169
170
/**
171
 * Retourne une courte description d’une révision VCS d’un répertoire
172
 *
173
 * @param string $dir Le répertoire à tester
174
 * @param array $raw True pour avoir les données brutes, false pour un texte à afficher
0 ignored issues
show
Documentation introduced by
Should the type for parameter $raw not be false|array? Also, consider making the array more specific, something like array<String>, or String[].

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

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

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

Loading history...
175
 * @retun string|array|null
176
 *    - array|null si $raw = true,
177
 *    - string|null si $raw = false
178
 */
179
function version_vcs_courante($dir, $raw = false) {
180
	$desc = decrire_version_git($dir);
181
	if ($desc === null) {
182
		$desc = decrire_version_svn($dir);
183
	}
184
	if ($desc === null or $raw) {
185
		return $desc;
186
	}
187
	// affichage "GIT [master: abcdef]"
188
	$commit = isset($desc['commit_short']) ? $desc['commit_short'] : $desc['commit'];
189
	if ($desc['branch']) {
190
		$commit = $desc['branch'] . ': ' . $commit;
191
	}
192
	return "{$desc['vcs']} [$commit]";
193
}
194
195
/**
196
 * Retrouve un numéro de révision Git d'un répertoire
197
 *
198
 * @param string $dir Chemin du répertoire
199
 * @return array|null
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array<string,string>|null.

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

Loading history...
200
 *      null si aucune info trouvée
201
 *      array ['branch' => xx, 'commit' => yy] sinon.
202
 **/
203
function decrire_version_git($dir) {
204
	if (!$dir) {
205
		$dir = '.';
206
	}
207
208
	// version installee par GIT
209
	if (lire_fichier($dir . '/.git/HEAD', $c)) {
210
		$currentHead = trim(substr($c, 4));
211
		if (lire_fichier($dir . '/.git/' . $currentHead, $hash)) {
212
			return [
213
				'vcs' => 'GIT',
214
				'branch' => basename($currentHead),
215
				'commit' => trim($hash),
216
				'commit_short' => substr(trim($hash), 0, 8),
217
			];
218
		}
219
	}
220
221
	return null;
222
}
223
224
225
/**
226
 * Retrouve un numéro de révision Svn d'un répertoire
227
 *
228
 * @param string $dir Chemin du répertoire
229
 * @return array|null
230
 *      null si aucune info trouvée
231
 *      array ['commit' => yy, 'date' => xx, 'author' => xx] sinon.
232
 **/
233
function decrire_version_svn($dir) {
234
	if (!$dir) {
235
		$dir = '.';
236
	}
237
	// version installee par SVN
238
	if (file_exists($dir . '/.svn/wc.db') && class_exists('SQLite3')) {
239
		$db = new SQLite3($dir . '/.svn/wc.db');
240
		$result = $db->query('SELECT changed_revision FROM nodes WHERE local_relpath = "" LIMIT 1');
241
		if ($result) {
242
			$row = $result->fetchArray();
243
			if ($row['changed_revision'] != "") {
244
				return [
245
					'vcs' => 'SVN',
246
					'branch' => '',
247
					'commit' => $row['changed_revision'],
248
				];
249
			}
250
		}
251
	}
252
	return null;
253
}
254
255
/**
256
 * Retrouve un numéro de révision SVN d'un répertoire
257
 *
258
 * Mention de la révision SVN courante d'un répertoire
259
 * /!\ Retourne un nombre négatif si on est sur .svn
260
 *
261
 * @deprecated Utiliser version_vcs_courante()
262
 * @param string $dir Chemin du répertoire
263
 * @return int
0 ignored issues
show
Documentation introduced by
Should the return type not be integer|double?

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

Loading history...
264
 *
265
 *     - 0 si aucune info trouvée
266
 *     - -NN (entier) si info trouvée par .svn/wc.db
267
 *
268
 **/
269
function version_svn_courante($dir) {
270
	if ($desc = decrire_version_svn($dir)) {
271
		return -$desc['commit'];
272
	}
273
	return 0;
274
}
275
276
// La matrice est necessaire pour ne filtrer _que_ des fonctions definies dans filtres_images
277
// et laisser passer les fonctions personnelles baptisees image_...
278
$GLOBALS['spip_matrice']['image_graver'] = true;//'inc/filtres_images_mini.php';
279
$GLOBALS['spip_matrice']['image_select'] = true;//'inc/filtres_images_mini.php';
280
$GLOBALS['spip_matrice']['image_reduire'] = true;//'inc/filtres_images_mini.php';
281
$GLOBALS['spip_matrice']['image_reduire_par'] = true;//'inc/filtres_images_mini.php';
282
$GLOBALS['spip_matrice']['image_passe_partout'] = true;//'inc/filtres_images_mini.php';
283
284
$GLOBALS['spip_matrice']['couleur_html_to_hex'] = 'inc/filtres_images_mini.php';
285
$GLOBALS['spip_matrice']['couleur_foncer'] = 'inc/filtres_images_mini.php';
286
$GLOBALS['spip_matrice']['couleur_eclaircir'] = 'inc/filtres_images_mini.php';
287
288
// ou pour inclure un script au moment ou l'on cherche le filtre
289
$GLOBALS['spip_matrice']['filtre_image_dist'] = 'inc/filtres_mime.php';
290
$GLOBALS['spip_matrice']['filtre_audio_dist'] = 'inc/filtres_mime.php';
291
$GLOBALS['spip_matrice']['filtre_video_dist'] = 'inc/filtres_mime.php';
292
$GLOBALS['spip_matrice']['filtre_application_dist'] = 'inc/filtres_mime.php';
293
$GLOBALS['spip_matrice']['filtre_message_dist'] = 'inc/filtres_mime.php';
294
$GLOBALS['spip_matrice']['filtre_multipart_dist'] = 'inc/filtres_mime.php';
295
$GLOBALS['spip_matrice']['filtre_text_dist'] = 'inc/filtres_mime.php';
296
$GLOBALS['spip_matrice']['filtre_text_csv_dist'] = 'inc/filtres_mime.php';
297
$GLOBALS['spip_matrice']['filtre_text_html_dist'] = 'inc/filtres_mime.php';
298
$GLOBALS['spip_matrice']['filtre_audio_x_pn_realaudio'] = 'inc/filtres_mime.php';
299
300
301
/**
302
 * Charge et exécute un filtre (graphique ou non)
303
 *
304
 * Recherche la fonction prévue pour un filtre (qui peut être un filtre graphique `image_*`)
305
 * et l'exécute avec les arguments transmis à la fonction, obtenus avec `func_get_args()`
306
 *
307
 * @api
308
 * @uses image_filtrer() Pour un filtre image
309
 * @uses chercher_filtre() Pour un autre filtre
310
 *
311
 * @param string $filtre
312
 *     Nom du filtre à appliquer
313
 * @return string
314
 *     Code HTML retourné par le filtre
315
 **/
316
function filtrer($filtre) {
317
	$tous = func_get_args();
318
	if (trouver_filtre_matrice($filtre) and substr($filtre, 0, 6) == 'image_') {
319
		return image_filtrer($tous);
320
	} elseif ($f = chercher_filtre($filtre)) {
321
		array_shift($tous);
322
		return call_user_func_array($f, $tous);
323
	} else {
324
		// le filtre n'existe pas, on provoque une erreur
325
		$msg = array('zbug_erreur_filtre', array('filtre' => texte_script($filtre)));
326
		erreur_squelette($msg);
327
		return '';
328
	}
329
}
330
331
/**
332
 * Cherche un filtre spécial indiqué dans la globale `spip_matrice`
333
 * et charge le fichier éventuellement associé contenant le filtre.
334
 *
335
 * Les filtres d'images par exemple sont déclarés de la sorte, tel que :
336
 * ```
337
 * $GLOBALS['spip_matrice']['image_reduire'] = true;
338
 * $GLOBALS['spip_matrice']['image_monochrome'] = 'filtres/images_complements.php';
339
 * ```
340
 *
341
 * @param string $filtre
342
 * @return bool true si on trouve le filtre dans la matrice, false sinon.
343
 */
344
function trouver_filtre_matrice($filtre) {
345
	if (isset($GLOBALS['spip_matrice'][$filtre]) and is_string($f = $GLOBALS['spip_matrice'][$filtre])) {
346
		find_in_path($f, '', true);
347
		$GLOBALS['spip_matrice'][$filtre] = true;
348
	}
349
	return !empty($GLOBALS['spip_matrice'][$filtre]);
350
}
351
352
353
/**
354
 * Filtre `set` qui sauve la valeur en entrée dans une variable
355
 *
356
 * La valeur pourra être retrouvée avec `#GET{variable}`.
357
 *
358
 * @example
359
 *     `[(#CALCUL|set{toto})]` enregistre le résultat de `#CALCUL`
360
 *     dans la variable `toto` et renvoie vide.
361
 *     C'est équivalent à `[(#SET{toto, #CALCUL})]` dans ce cas.
362
 *     `#GET{toto}` retourne la valeur sauvegardée.
363
 *
364
 * @example
365
 *     `[(#CALCUL|set{toto,1})]` enregistre le résultat de `#CALCUL`
366
 *      dans la variable toto et renvoie la valeur. Cela permet d'utiliser
367
 *      d'autres filtres ensuite. `#GET{toto}` retourne la valeur.
368
 *
369
 * @filtre
370
 * @param array $Pile Pile de données
371
 * @param mixed $val Valeur à sauver
372
 * @param string $key Clé d'enregistrement
373
 * @param bool $continue True pour retourner la valeur
0 ignored issues
show
Documentation introduced by
Should the type for parameter $continue not be boolean|null?

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

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

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

Loading history...
374
 * @return mixed
375
 */
376
function filtre_set(&$Pile, $val, $key, $continue = null) {
377
	$Pile['vars'][$key] = $val;
378
	return $continue ? $val : '';
379
}
380
381
/**
382
 * Filtre `setenv` qui enregistre une valeur dans l'environnement du squelette
383
 *
384
 * La valeur pourra être retrouvée avec `#ENV{variable}`.
385
 * 
386
 * @example
387
 *     `[(#CALCUL|setenv{toto})]` enregistre le résultat de `#CALCUL`
388
 *      dans l'environnement toto et renvoie vide.
389
 *      `#ENV{toto}` retourne la valeur.
390
 *
391
 *      `[(#CALCUL|setenv{toto,1})]` enregistre le résultat de `#CALCUL`
392
 *      dans l'environnement toto et renvoie la valeur.
393
 *      `#ENV{toto}` retourne la valeur.
394
 *
395
 * @filtre
396
 *
397
 * @param array $Pile
398
 * @param mixed $val Valeur à enregistrer
399
 * @param mixed $key Nom de la variable
400
 * @param null|mixed $continue Si présent, retourne la valeur en sortie
401
 * @return string|mixed Retourne `$val` si `$continue` présent, sinon ''.
402
 */
403
function filtre_setenv(&$Pile, $val, $key, $continue = null) {
404
	$Pile[0][$key] = $val;
405
	return $continue ? $val : '';
406
}
407
408
/**
409
 * @param array $Pile
410
 * @param array|string $keys
411
 * @return string
412
 */
413
function filtre_sanitize_env(&$Pile, $keys) {
414
	$Pile[0] = spip_sanitize_from_request($Pile[0], $keys);
415
	return '';
416
}
417
418
419
/**
420
 * Filtre `debug` qui affiche un debug de la valeur en entrée
421
 *
422
 * Log la valeur dans `debug.log` et l'affiche si on est webmestre.
423
 *
424
 * @example
425
 *     `[(#TRUC|debug)]` affiche et log la valeur de `#TRUC`
426
 * @example
427
 *     `[(#TRUC|debug{avant}|calcul|debug{apres}|etc)]`
428
 *     affiche la valeur de `#TRUC` avant et après le calcul,
429
 *     en précisant "avant" et "apres".
430
 *
431
 * @filtre
432
 * @link https://www.spip.net/5695
433
 * @param mixed $val La valeur à debugguer
434
 * @param mixed|null $key Clé pour s'y retrouver
435
 * @return mixed Retourne la valeur (sans la modifier).
436
 */
437
function filtre_debug($val, $key = null) {
438
	$debug = (
439
		is_null($key) ? '' : (var_export($key, true) . " = ")
440
		) . var_export($val, true);
441
442
	include_spip('inc/autoriser');
443
	if (autoriser('webmestre')) {
444
		echo "<div class='spip_debug'>\n", $debug, "</div>\n";
445
	}
446
447
	spip_log($debug, 'debug');
448
449
	return $val;
450
}
451
452
453
/**
454
 * Exécute un filtre image
455
 *
456
 * Fonction générique d'entrée des filtres images.
457
 * Accepte en entrée :
458
 *
459
 * - un texte complet,
460
 * - un img-log (produit par #LOGO_XX),
461
 * - un tag `<img ...>` complet,
462
 * - un nom de fichier *local* (passer le filtre `|copie_locale` si on veut
463
 *   l'appliquer à un document distant).
464
 *
465
 * Applique le filtre demande à chacune des occurrences
466
 *
467
 * @param array $args
468
 *     Liste des arguments :
469
 *
470
 *     - le premier est le nom du filtre image à appliquer
471
 *     - le second est le texte sur lequel on applique le filtre
472
 *     - les suivants sont les arguments du filtre image souhaité.
473
 * @return string
474
 *     Texte qui a reçu les filtres
475
 **/
476
function image_filtrer($args) {
477
	$filtre = array_shift($args); # enlever $filtre
478
	$texte = array_shift($args);
479
	if (!strlen($texte)) {
480
		return;
481
	}
482
	find_in_path('filtres_images_mini.php', 'inc/', true);
483
	statut_effacer_images_temporaires(true); // activer la suppression des images temporaires car le compilo finit la chaine par un image_graver
0 ignored issues
show
Unused Code introduced by
The call to the function statut_effacer_images_temporaires() seems unnecessary as the function has no side-effects.
Loading history...
484
	// Cas du nom de fichier local
485
	$is_file = trim($texte);
486
	if (strpos(substr($is_file, strlen(_DIR_RACINE)), '..') !== false
487
		  or strpbrk($is_file, "<>\n\r\t") !== false
488
		  or strpos($is_file, '/') === 0
489
	) {
490
		$is_file = false;
491
	}
492
	if ($is_file) {
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...
493
		$is_local_file = function($path) {
494
			if (strpos($path, "?") !== false) {
495
				$path = supprimer_timestamp($path);
496
				// remove ?24px added by find_in_theme on .svg files
497
				$path = preg_replace(",\?[[:digit:]]+(px)$,", "", $path);
498
			}
499
			return file_exists($path);
500
		};
501
		if ($is_local_file($is_file) or tester_url_absolue($is_file)) {
502
			array_unshift($args, "<img src='$is_file' />");
503
			$res = call_user_func_array($filtre, $args);
504
			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...
505
			return $res;
506
		}
507
	}
508
509
	// Cas general : trier toutes les images, avec eventuellement leur <span>
510
	if (preg_match_all(
511
		',(<([a-z]+) [^<>]*spip_documents[^<>]*>)?\s*(<img\s.*>),UimsS',
512
		$texte, $tags, PREG_SET_ORDER)) {
513
		foreach ($tags as $tag) {
514
			$class = extraire_attribut($tag[3], 'class');
515
			if (!$class or
516
				(strpos($class, 'filtre_inactif') === false
517
					// compat historique a virer en 3.2
518
					and strpos($class, 'no_image_filtrer') === false)
519
			) {
520
				array_unshift($args, $tag[3]);
521
				if ($reduit = call_user_func_array($filtre, $args)) {
522
					// En cas de span spip_documents, modifier le style=...width:
523
					if ($tag[1]) {
524
						$w = extraire_attribut($reduit, 'width');
525
						if (!$w and preg_match(",width:\s*(\d+)px,S", extraire_attribut($reduit, 'style'), $regs)) {
526
							$w = $regs[1];
527
						}
528
						if ($w and ($style = extraire_attribut($tag[1], 'style'))) {
529
							$style = preg_replace(",width:\s*\d+px,S", "width:${w}px", $style);
530
							$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 529 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...
531
							$texte = str_replace($tag[1], $replace, $texte);
532
						}
533
					}
534
					// traiter aussi un eventuel mouseover
535
					if ($mouseover = extraire_attribut($reduit, 'onmouseover')) {
536
						if (preg_match(",this[.]src=['\"]([^'\"]+)['\"],ims", $mouseover, $match)) {
537
							$srcover = $match[1];
538
							array_shift($args);
539
							array_unshift($args, "<img src='" . $match[1] . "' />");
540
							$srcover_filter = call_user_func_array($filtre, $args);
541
							$srcover_filter = extraire_attribut($srcover_filter, 'src');
542
							$reduit = str_replace($srcover, $srcover_filter, $reduit);
543
						}
544
					}
545
					$texte = str_replace($tag[3], $reduit, $texte);
546
				}
547
				array_shift($args);
548
			}
549
		}
550
	}
551
	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...
552
	return $texte;
553
}
554
555
/**
556
 * Retourne les tailles d'une image
557
 *
558
 * Pour les filtres `largeur` et `hauteur`
559
 *
560
 * @param string $img
561
 *     Balise HTML `<img ... />` ou chemin de l'image (qui peut être une URL distante).
562
 * @return array
563
 *     Liste (hauteur, largeur) en pixels
564
 **/
565
function taille_image($img, $force_refresh = false) {
566
567
	static $largeur_img = array(), $hauteur_img = array();
568
	$srcWidth = 0;
569
	$srcHeight = 0;
570
571
	$src = extraire_attribut($img, 'src');
572
573
	if (!$src) {
574
		$src = $img;
575
	} else {
576
		$srcWidth = extraire_attribut($img, 'width');
577
		$srcHeight = extraire_attribut($img, 'height');
578
	}
579
580
	// ne jamais operer directement sur une image distante pour des raisons de perfo
581
	// la copie locale a toutes les chances d'etre la ou de resservir
582
	if (tester_url_absolue($src)) {
0 ignored issues
show
Bug introduced by
It seems like $src defined by extraire_attribut($img, 'src') on line 571 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...
583
		include_spip('inc/distant');
584
		$fichier = copie_locale($src);
0 ignored issues
show
Bug introduced by
It seems like $src defined by extraire_attribut($img, 'src') on line 571 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...
585
		$src = $fichier ? _DIR_RACINE . $fichier : $src;
586
	}
587 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...
588
		$src = substr($src, 0, $p);
589
	}
590
591
	$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...
592
	if (isset($largeur_img[$src]) and !$force_refresh) {
593
		$srcWidth = $largeur_img[$src];
594
	}
595
	if (isset($hauteur_img[$src]) and !$force_refresh) {
596
		$srcHeight = $hauteur_img[$src];
597
	}
598
	if (!$srcWidth or !$srcHeight) {
599
600
		if (file_exists($src)
601
			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...
602
		) {
603
			if (!$srcWidth) {
604
				$largeur_img[$src] = $srcWidth = $srcsize[0];
605
			}
606
			if (!$srcHeight) {
607
				$hauteur_img[$src] = $srcHeight = $srcsize[1];
608
			}
609
		}
610
		// $src peut etre une reference a une image temporaire dont a n'a que le log .src
611
		// on s'y refere, l'image sera reconstruite en temps utile si necessaire
612
		elseif (@file_exists($f = "$src.src")
613
			and lire_fichier($f, $valeurs)
614
			and $valeurs = unserialize($valeurs)
615
		) {
616
			if (!$srcWidth) {
617
				$largeur_img[$src] = $srcWidth = $valeurs["largeur_dest"];
618
			}
619
			if (!$srcHeight) {
620
				$hauteur_img[$src] = $srcHeight = $valeurs["hauteur_dest"];
621
			}
622
		}
623
	}
624
625
	return array($srcHeight, $srcWidth);
626
}
627
628
629
/**
630
 * Retourne la largeur d'une image
631
 *
632
 * @filtre
633
 * @link https://www.spip.net/4296
634
 * @uses taille_image()
635
 * @see  hauteur()
636
 *
637
 * @param string $img
638
 *     Balise HTML `<img ... />` ou chemin de l'image (qui peut être une URL distante).
639
 * @return int|null
640
 *     Largeur en pixels, NULL ou 0 si aucune image.
641
 **/
642
function largeur($img) {
643
	if (!$img) {
644
		return;
645
	}
646
	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...
647
648
	return $l;
649
}
650
651
/**
652
 * Retourne la hauteur d'une image
653
 *
654
 * @filtre
655
 * @link https://www.spip.net/4291
656
 * @uses taille_image()
657
 * @see  largeur()
658
 *
659
 * @param string $img
660
 *     Balise HTML `<img ... />` ou chemin de l'image (qui peut être une URL distante).
661
 * @return int|null
662
 *     Hauteur en pixels, NULL ou 0 si aucune image.
663
 **/
664
function hauteur($img) {
665
	if (!$img) {
666
		return;
667
	}
668
	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...
669
670
	return $h;
671
}
672
673
674
/**
675
 * Échappement des entités HTML avec correction des entités « brutes »
676
 *
677
 * Ces entités peuvent être générées par les butineurs lorsqu'on rentre des
678
 * caractères n'appartenant pas au charset de la page [iso-8859-1 par défaut]
679
 *
680
 * Attention on limite cette correction aux caracteres « hauts » (en fait > 99
681
 * pour aller plus vite que le > 127 qui serait logique), de manière à
682
 * préserver des eéhappements de caractères « bas » (par exemple `[` ou `"`)
683
 * et au cas particulier de `&amp;` qui devient `&amp;amp;` dans les URL
684
 *
685
 * @see corriger_toutes_entites_html()
686
 * @param string $texte
687
 * @return string
688
 **/
689
function corriger_entites_html($texte) {
690
	if (strpos($texte, '&amp;') === false) {
691
		return $texte;
692
	}
693
694
	return preg_replace(',&amp;(#[0-9][0-9][0-9]+;|amp;),iS', '&\1', $texte);
695
}
696
697
/**
698
 * Échappement des entités HTML avec correction des entités « brutes » ainsi
699
 * que les `&amp;eacute;` en `&eacute;`
700
 *
701
 * Identique à `corriger_entites_html()` en corrigeant aussi les
702
 * `&amp;eacute;` en `&eacute;`
703
 *
704
 * @see corriger_entites_html()
705
 * @param string $texte
706
 * @return string
707
 **/
708
function corriger_toutes_entites_html($texte) {
709
	if (strpos($texte, '&amp;') === false) {
710
		return $texte;
711
	}
712
713
	return preg_replace(',&amp;(#?[a-z0-9]+;),iS', '&\1', $texte);
714
}
715
716
/**
717
 * Échappe les `&` en `&amp;`
718
 *
719
 * @param string $texte
720
 * @return string
721
 **/
722
function proteger_amp($texte) {
723
	return str_replace('&', '&amp;', $texte);
724
}
725
726
727
/**
728
 * Échappe en entités HTML certains caractères d'un texte
729
 *
730
 * Traduira un code HTML en transformant en entités HTML les caractères
731
 * en dehors du charset de la page ainsi que les `"`, `<` et `>`.
732
 *
733
 * Ceci permet d’insérer le texte d’une balise dans un `<textarea> </textarea>`
734
 * sans dommages.
735
 *
736
 * @filtre
737
 * @link https://www.spip.net/4280
738
 *
739
 * @uses echappe_html()
740
 * @uses echappe_retour()
741
 * @uses proteger_amp()
742
 * @uses corriger_entites_html()
743
 * @uses corriger_toutes_entites_html()
744
 *
745
 * @param string $texte
746
 *   chaine a echapper
747
 * @param bool $tout
748
 *   corriger toutes les `&amp;xx;` en `&xx;`
749
 * @param bool $quote
750
 *   Échapper aussi les simples quotes en `&#039;`
751
 * @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...
752
 */
753
function entites_html($texte, $tout = false, $quote = true) {
754 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...
755
		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...
756
	) {
757
		return $texte;
758
	}
759
	include_spip('inc/texte');
760
	$flags = ($quote ? ENT_QUOTES : ENT_NOQUOTES);
761
	$flags |= ENT_HTML401;
762
	$texte = spip_htmlspecialchars(echappe_retour(echappe_html($texte, '', true), '', 'proteger_amp'), $flags);
763
	if ($tout) {
764
		return corriger_toutes_entites_html($texte);
765
	} else {
766
		return corriger_entites_html($texte);
767
	}
768
}
769
770
/**
771
 * Convertit les caractères spéciaux HTML dans le charset du site.
772
 *
773
 * @exemple
774
 *     Si le charset de votre site est `utf-8`, `&eacute;` ou `&#233;`
775
 *     sera transformé en `é`
776
 *
777
 * @filtre
778
 * @link https://www.spip.net/5513
779
 *
780
 * @param string $texte
781
 *     Texte à convertir
782
 * @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...
783
 *     Texte converti
784
 **/
785
function filtrer_entites($texte) {
786
	if (strpos($texte, '&') === false) {
787
		return $texte;
788
	}
789
	// filtrer
790
	$texte = html2unicode($texte);
791
	// remettre le tout dans le charset cible
792
	$texte = unicode2charset($texte);
793
	// cas particulier des " et ' qu'il faut filtrer aussi
794
	// (on le faisait deja avec un &quot;)
795 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...
796
		$texte = str_replace(array("&#039;", "&#39;", "&#034;", "&#34;"), array("'", "'", '"', '"'), $texte);
797
	}
798
799
	return $texte;
800
}
801
802
803
if (!function_exists('filtre_filtrer_entites_dist')) {
804
	/**
805
	 * Version sécurisée de filtrer_entites
806
	 * 
807
	 * @uses interdire_scripts()
808
	 * @uses filtrer_entites()
809
	 * 
810
	 * @param string $t
811
	 * @return string
812
	 */
813
	function filtre_filtrer_entites_dist($t) {
0 ignored issues
show
Best Practice introduced by
The function filtre_filtrer_entites_dist() has been defined more than once; this definition is ignored, only the first definition in config/ecran_securite.php (L658-661) is considered.

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

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

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

}

function getUser($id, $realm) {

}

See also the PhpDoc documentation for @ignore.

Loading history...
814
		include_spip('inc/texte');
815
		return interdire_scripts(filtrer_entites($t));
816
	}
817
}
818
819
820
/**
821
 * Supprime des caractères illégaux
822
 *
823
 * Remplace les caractères de controle par le caractère `-`
824
 *
825
 * @link http://www.w3.org/TR/REC-xml/#charsets
826
 *
827
 * @param string|array $texte
828
 * @return string|array
829
 **/
830
function supprimer_caracteres_illegaux($texte) {
831
	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";
832
	static $to = null;
833
834
	if (is_array($texte)) {
835
		return array_map('supprimer_caracteres_illegaux', $texte);
836
	}
837
838
	if (!$to) {
839
		$to = str_repeat('-', strlen($from));
840
	}
841
842
	return strtr($texte, $from, $to);
843
}
844
845
/**
846
 * Correction de caractères
847
 *
848
 * Supprimer les caracteres windows non conformes et les caracteres de controle illégaux
849
 *
850
 * @param string|array $texte
851
 * @return string|array
852
 **/
853
function corriger_caracteres($texte) {
854
	$texte = corriger_caracteres_windows($texte);
855
	$texte = supprimer_caracteres_illegaux($texte);
856
857
	return $texte;
858
}
859
860
/**
861
 * Encode du HTML pour transmission XML notamment dans les flux RSS
862
 *
863
 * Ce filtre transforme les liens en liens absolus, importe les entitées html et échappe les tags html.
864
 *
865
 * @filtre
866
 * @link https://www.spip.net/4287
867
 *
868
 * @param string $texte
869
 *     Texte à transformer
870
 * @return string
871
 *     Texte encodé pour XML
872
 */
873
function texte_backend($texte) {
874
875
	static $apostrophe = array("&#8217;", "'"); # n'allouer qu'une fois
876
877
	// si on a des liens ou des images, les passer en absolu
878
	$texte = liens_absolus($texte);
879
880
	// echapper les tags &gt; &lt;
881
	$texte = preg_replace(',&(gt|lt);,S', '&amp;\1;', $texte);
882
883
	// importer les &eacute;
884
	$texte = filtrer_entites($texte);
885
886
	// " -> &quot; et tout ce genre de choses
887
	$u = $GLOBALS['meta']['pcre_u'];
888
	$texte = str_replace("&nbsp;", " ", $texte);
889
	$texte = preg_replace('/\s{2,}/S' . $u, " ", $texte);
890
	// ne pas echapper les sinqle quotes car certains outils de syndication gerent mal
891
	$texte = entites_html($texte, false, false);
892
	// mais bien echapper les double quotes !
893
	$texte = str_replace('"', '&#034;', $texte);
894
895
	// verifier le charset
896
	$texte = charset2unicode($texte);
897
898
	// Caracteres problematiques en iso-latin 1
899
	if (isset($GLOBALS['meta']['charset']) and $GLOBALS['meta']['charset'] == 'iso-8859-1') {
900
		$texte = str_replace(chr(156), '&#156;', $texte);
901
		$texte = str_replace(chr(140), '&#140;', $texte);
902
		$texte = str_replace(chr(159), '&#159;', $texte);
903
	}
904
905
	// l'apostrophe curly pose probleme a certains lecteure de RSS
906
	// et le caractere apostrophe alourdit les squelettes avec PHP
907
	// ==> on les remplace par l'entite HTML
908
	return str_replace($apostrophe, "'", $texte);
909
}
910
911
/**
912
 * Encode et quote du HTML pour transmission XML notamment dans les flux RSS
913
 *
914
 * Comme texte_backend(), mais avec addslashes final pour squelettes avec PHP (rss)
915
 *
916
 * @uses texte_backend()
917
 * @filtre
918
 *
919
 * @param string $texte
920
 *     Texte à transformer
921
 * @return string
922
 *     Texte encodé et quote pour XML
923
 */
924
function texte_backendq($texte) {
925
	return addslashes(texte_backend($texte));
926
}
927
928
929
/**
930
 * Enlève un numéro préfixant un texte
931
 *
932
 * Supprime `10. ` dans la chaine `10. Titre`
933
 *
934
 * @filtre
935
 * @link https://www.spip.net/4314
936
 * @see recuperer_numero() Pour obtenir le numéro
937
 * @example
938
 *     ```
939
 *     [<h1>(#TITRE|supprimer_numero)</h1>]
940
 *     ```
941
 *
942
 * @param string $texte
943
 *     Texte
944
 * @return int|string
945
 *     Numéro de titre, sinon chaîne vide
946
 **/
947
function supprimer_numero($texte) {
948
	return preg_replace(
949
		",^[[:space:]]*([0-9]+)([.)]|" . chr(194) . '?' . chr(176) . ")[[:space:]]+,S",
950
		"", $texte);
951
}
952
953
/**
954
 * Récupère un numéro préfixant un texte
955
 *
956
 * Récupère le numéro `10` dans la chaine `10. Titre`
957
 *
958
 * @filtre
959
 * @link https://www.spip.net/5514
960
 * @see supprimer_numero() Pour supprimer le numéro
961
 * @see balise_RANG_dist() Pour obtenir un numéro de titre
962
 * @example
963
 *     ```
964
 *     [(#TITRE|recuperer_numero)]
965
 *     ```
966
 *
967
 * @param string $texte
968
 *     Texte
969
 * @return int|string
970
 *     Numéro de titre, sinon chaîne vide
971
 **/
972
function recuperer_numero($texte) {
973
	if (preg_match(
974
		",^[[:space:]]*([0-9]+)([.)]|" . chr(194) . '?' . chr(176) . ")[[:space:]]+,S",
975
		$texte, $regs)) {
976
		return strval($regs[1]);
977
	} else {
978
		return '';
979
	}
980
}
981
982
/**
983
 * Suppression basique et brutale de tous les tags
984
 *
985
 * Supprime tous les tags `<...>`.
986
 * Utilisé fréquemment pour écrire des RSS.
987
 *
988
 * @filtre
989
 * @link https://www.spip.net/4315
990
 * @example
991
 *     ```
992
 *     <title>[(#TITRE|supprimer_tags|texte_backend)]</title>
993
 *     ```
994
 *
995
 * @note
996
 *     Ce filtre supprime aussi les signes inférieurs `<` rencontrés.
997
 *
998
 * @param string $texte
999
 *     Texte à échapper
1000
 * @param string $rempl
1001
 *     Inutilisé.
1002
 * @return string
1003
 *     Texte converti
1004
 **/
1005
function supprimer_tags($texte, $rempl = "") {
1006
	$texte = preg_replace(",<(!--|\w|/|!\[endif|!\[if)[^>]*>,US", $rempl, $texte);
1007
	// ne pas oublier un < final non ferme car coupe
1008
	$texte = preg_replace(",<(!--|\w|/).*$,US", $rempl, $texte);
1009
	// mais qui peut aussi etre un simple signe plus petit que
1010
	$texte = str_replace('<', '&lt;', $texte);
1011
1012
	return $texte;
1013
}
1014
1015
/**
1016
 * Convertit les chevrons de tag en version lisible en HTML
1017
 *
1018
 * Transforme les chevrons de tag `<...>` en entité HTML.
1019
 *
1020
 * @filtre
1021
 * @link https://www.spip.net/5515
1022
 * @example
1023
 *     ```
1024
 *     <pre>[(#TEXTE|echapper_tags)]</pre>
1025
 *     ```
1026
 *
1027
 * @param string $texte
1028
 *     Texte à échapper
1029
 * @param string $rempl
1030
 *     Inutilisé.
1031
 * @return string
1032
 *     Texte converti
1033
 **/
1034
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...
1035
	$texte = preg_replace("/<([^>]*)>/", "&lt;\\1&gt;", $texte);
1036
1037
	return $texte;
1038
}
1039
1040
/**
1041
 * Convertit un texte HTML en texte brut
1042
 *
1043
 * Enlève les tags d'un code HTML, élimine les doubles espaces.
1044
 *
1045
 * @filtre
1046
 * @link https://www.spip.net/4317
1047
 * @example
1048
 *     ```
1049
 *     <title>[(#TITRE|textebrut) - ][(#NOM_SITE_SPIP|textebrut)]</title>
1050
 *     ```
1051
 *
1052
 * @param string $texte
1053
 *     Texte à convertir
1054
 * @return string
1055
 *     Texte converti
1056
 **/
1057
function textebrut($texte) {
1058
	$u = $GLOBALS['meta']['pcre_u'];
1059
	$texte = preg_replace('/\s+/S' . $u, " ", $texte);
1060
	$texte = preg_replace("/<(p|br)( [^>]*)?" . ">/iS", "\n\n", $texte);
1061
	$texte = preg_replace("/^\n+/", "", $texte);
1062
	$texte = preg_replace("/\n+$/", "", $texte);
1063
	$texte = preg_replace("/\n +/", "\n", $texte);
1064
	$texte = supprimer_tags($texte);
1065
	$texte = preg_replace("/(&nbsp;| )+/S", " ", $texte);
1066
	// nettoyer l'apostrophe curly qui pose probleme a certains rss-readers, lecteurs de mail...
1067
	$texte = str_replace("&#8217;", "'", $texte);
1068
1069
	return $texte;
1070
}
1071
1072
1073
/**
1074
 * Remplace les liens SPIP en liens ouvrant dans une nouvelle fenetre (target=blank)
1075
 *
1076
 * @filtre
1077
 * @link https://www.spip.net/4297
1078
 *
1079
 * @param string $texte
1080
 *     Texte avec des liens
1081
 * @return string
1082
 *     Texte avec liens ouvrants
1083
 **/
1084
function liens_ouvrants($texte) {
1085
	if (preg_match_all(",(<a\s+[^>]*https?://[^>]*class=[\"']spip_(out|url)\b[^>]+>),imsS",
1086
		$texte, $liens, PREG_PATTERN_ORDER)) {
1087
		foreach ($liens[0] as $a) {
1088
			$rel = 'noopener noreferrer ' . extraire_attribut($a, 'rel');
1089
			$ablank = inserer_attribut($a, 'rel', $rel);
1090
			$ablank = inserer_attribut($ablank, 'target', '_blank');
1091
			$texte = str_replace($a, $ablank, $texte);
1092
		}
1093
	}
1094
1095
	return $texte;
1096
}
1097
1098
/**
1099
 * Ajouter un attribut rel="nofollow" sur tous les liens d'un texte
1100
 *
1101
 * @param string $texte
1102
 * @return string
1103
 */
1104
function liens_nofollow($texte) {
1105
	if (stripos($texte, "<a") === false) {
1106
		return $texte;
1107
	}
1108
1109
	if (preg_match_all(",<a\b[^>]*>,UimsS", $texte, $regs, PREG_PATTERN_ORDER)) {
1110
		foreach ($regs[0] as $a) {
1111
			$rel = extraire_attribut($a, "rel");
1112
			if (strpos($rel, "nofollow") === false) {
1113
				$rel = "nofollow" . ($rel ? " $rel" : "");
1114
				$anofollow = inserer_attribut($a, "rel", $rel);
1115
				$texte = str_replace($a, $anofollow, $texte);
1116
			}
1117
		}
1118
	}
1119
1120
	return $texte;
1121
}
1122
1123
/**
1124
 * Transforme les sauts de paragraphe HTML `p` en simples passages à la ligne `br`
1125
 *
1126
 * @filtre
1127
 * @link https://www.spip.net/4308
1128
 * @example
1129
 *     ```
1130
 *     [<div>(#DESCRIPTIF|PtoBR)[(#NOTES|PtoBR)]</div>]
1131
 *     ```
1132
 *
1133
 * @param string $texte
1134
 *     Texte à transformer
1135
 * @return string
1136
 *     Texte sans paraghaphes
1137
 **/
1138
function PtoBR($texte) {
1139
	$u = $GLOBALS['meta']['pcre_u'];
1140
	$texte = preg_replace("@</p>@iS", "\n", $texte);
1141
	$texte = preg_replace("@<p\b.*>@UiS", "<br />", $texte);
1142
	$texte = preg_replace("@^\s*<br />@S" . $u, "", $texte);
1143
1144
	return $texte;
1145
}
1146
1147
1148
/**
1149
 * Assure qu'un texte ne vas pas déborder d'un bloc
1150
 * par la faute d'un mot trop long (souvent des URLs)
1151
 *
1152
 * Ne devrait plus être utilisé et fait directement en CSS par un style
1153
 * `word-wrap:break-word;`
1154
 *
1155
 * @note
1156
 *   Pour assurer la compatibilité du filtre, on encapsule le contenu par
1157
 *   un `div` ou `span` portant ce style CSS inline.
1158
 *
1159
 * @filtre
1160
 * @link https://www.spip.net/4298
1161
 * @link http://www.alsacreations.com/tuto/lire/1038-gerer-debordement-contenu-css.html
1162
 * @deprecated Utiliser le style CSS `word-wrap:break-word;`
1163
 *
1164
 * @param string $texte Texte
1165
 * @return string Texte encadré du style CSS
1166
 */
1167
function lignes_longues($texte) {
1168
	if (!strlen(trim($texte))) {
1169
		return $texte;
1170
	}
1171
	include_spip('inc/texte');
1172
	$tag = preg_match(',</?(' . _BALISES_BLOCS . ')[>[:space:]],iS', $texte) ?
1173
		'div' : 'span';
1174
1175
	return "<$tag style='word-wrap:break-word;'>$texte</$tag>";
1176
}
1177
1178
/**
1179
 * Passe un texte en majuscules, y compris les accents, en HTML
1180
 *
1181
 * Encadre le texte du style CSS `text-transform: uppercase;`.
1182
 * Le cas spécifique du i turc est géré.
1183
 *
1184
 * @filtre
1185
 * @example
1186
 *     ```
1187
 *     [(#EXTENSION|majuscules)]
1188
 *     ```
1189
 *
1190
 * @param string $texte Texte
1191
 * @return string Texte en majuscule
1192
 */
1193
function majuscules($texte) {
1194
	if (!strlen($texte)) {
1195
		return '';
1196
	}
1197
1198
	// Cas du turc
1199
	if ($GLOBALS['spip_lang'] == 'tr') {
1200
		# remplacer hors des tags et des entites
1201 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...
1202
			foreach ($regs as $n => $match) {
1203
				$texte = str_replace($match[0], "@@SPIP_TURC$n@@", $texte);
1204
			}
1205
		}
1206
1207
		$texte = str_replace('i', '&#304;', $texte);
1208
1209 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...
1210
			foreach ($regs as $n => $match) {
1211
				$texte = str_replace("@@SPIP_TURC$n@@", $match[0], $texte);
1212
			}
1213
		}
1214
	}
1215
1216
	// Cas general
1217
	return "<span style='text-transform: uppercase;'>$texte</span>";
1218
}
1219
1220
/**
1221
 * Retourne une taille en octets humainement lisible
1222
 *
1223
 * Tel que "127.4 ko" ou "3.1 Mo"
1224
 *
1225
 * @example
1226
 *     - `[(#TAILLE|taille_en_octets)]`
1227
 *     - `[(#VAL{123456789}|taille_en_octets)]` affiche `117.7 Mo`
1228
 *
1229
 * @filtre
1230
 * @link https://www.spip.net/4316
1231
 * @param int $taille
1232
 * @return string
1233
 **/
1234
function taille_en_octets($taille) {
1235
	if (!defined('_KILOBYTE')) {
1236
		/**
1237
		 * Définit le nombre d'octets dans un Kilobyte
1238
		 *
1239
		 * @var int
1240
		 **/
1241
		define('_KILOBYTE', 1024);
1242
	}
1243
1244
	if ($taille < 1) {
1245
		return '';
1246
	}
1247
	if ($taille < _KILOBYTE) {
1248
		$taille = _T('taille_octets', array('taille' => $taille));
1249 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...
1250
		$taille = _T('taille_ko', array('taille' => round($taille / _KILOBYTE, 1)));
1251
	} elseif ($taille < _KILOBYTE * _KILOBYTE * _KILOBYTE) {
1252
		$taille = _T('taille_mo', array('taille' => round($taille / _KILOBYTE / _KILOBYTE, 1)));
1253 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...
1254
		$taille = _T('taille_go', array('taille' => round($taille / _KILOBYTE / _KILOBYTE / _KILOBYTE, 2)));
1255
	}
1256
1257
	return $taille;
1258
}
1259
1260
1261
/**
1262
 * Rend une chaine utilisable sans dommage comme attribut HTML
1263
 *
1264
 * @example `<a href="#URL_ARTICLE" title="[(#TITRE|attribut_html)]">#TITRE</a>`
1265
 *
1266
 * @filtre
1267
 * @link https://www.spip.net/4282
1268
 * @uses textebrut()
1269
 * @uses texte_backend()
1270
 *
1271
 * @param string $texte
1272
 *     Texte à mettre en attribut
1273
 * @param bool $textebrut
1274
 *     Passe le texte en texte brut (enlève les balises html) ?
1275
 * @return string
1276
 *     Texte prêt pour être utilisé en attribut HTML
1277
 **/
1278
function attribut_html($texte, $textebrut = true) {
1279
	$u = $GLOBALS['meta']['pcre_u'];
1280
	if ($textebrut) {
1281
		$texte = preg_replace(array(",\n,", ",\s(?=\s),msS" . $u), array(" ", ""), textebrut($texte));
1282
	}
1283
	$texte = texte_backend($texte);
1284
	$texte = str_replace(array("'", '"'), array('&#039;', '&#034;'), $texte);
1285
1286
	return preg_replace(array("/&(amp;|#38;)/", "/&(?![A-Za-z]{0,4}\w{2,3};|#[0-9]{2,5};)/"), array("&", "&#38;"),
1287
		$texte);
1288
}
1289
1290
1291
/**
1292
 * Vider les URL nulles
1293
 *
1294
 * - Vide les URL vides comme `http://` ou `mailto:` (sans rien d'autre)
1295
 * - échappe les entités et gère les `&amp;`
1296
 *
1297
 * @uses entites_html()
1298
 *
1299
 * @param string $url
1300
 *     URL à vérifier et échapper
1301
 * @param bool $entites
1302
 *     `true` pour échapper les entités HTML.
1303
 * @return string
1304
 *     URL ou chaîne vide
1305
 **/
1306
function vider_url($url, $entites = true) {
1307
	# un message pour abs_url
1308
	$GLOBALS['mode_abs_url'] = 'url';
1309
	$url = trim($url);
1310
	$r = ",^(?:" . _PROTOCOLES_STD . '):?/?/?$,iS';
1311
1312
	return preg_match($r, $url) ? '' : ($entites ? entites_html($url) : $url);
1313
}
1314
1315
1316
/**
1317
 * Maquiller une adresse e-mail
1318
 *
1319
 * Remplace `@` par 3 caractères aléatoires.
1320
 *
1321
 * @uses creer_pass_aleatoire()
1322
 *
1323
 * @param string $texte Adresse email
1324
 * @return string Adresse email maquillée
1325
 **/
1326
function antispam($texte) {
1327
	include_spip('inc/acces');
1328
	$masque = creer_pass_aleatoire(3);
1329
1330
	return preg_replace("/@/", " $masque ", $texte);
1331
}
1332
1333
/**
1334
 * Vérifie un accès à faible sécurité
1335
 *
1336
 * Vérifie qu'un visiteur peut accéder à la page demandée,
1337
 * qui est protégée par une clé, calculée à partir du low_sec de l'auteur,
1338
 * et des paramètres le composant l'appel (op, args)
1339
 *
1340
 * @example
1341
 *     `[(#ID_AUTEUR|securiser_acces{#ENV{cle}, rss, #ENV{op}, #ENV{args}}|sinon_interdire_acces)]`
1342
 *
1343
 * @see  bouton_spip_rss() pour générer un lien de faible sécurité pour les RSS privés
1344
 * @see  afficher_low_sec() pour calculer une clé valide
1345
 * @uses verifier_low_sec()
1346
 *
1347
 * @filtre
1348
 * @param int $id_auteur
1349
 *     L'auteur qui demande la page
1350
 * @param string $cle
1351
 *     La clé à tester
1352
 * @param string $dir
1353
 *     Un type d'accès (nom du répertoire dans lequel sont rangés les squelettes demandés, tel que 'rss')
1354
 * @param string $op
1355
 *     Nom de l'opération éventuelle
1356
 * @param string $args
1357
 *     Nom de l'argument calculé
1358
 * @return bool
1359
 *     True si on a le droit d'accès, false sinon.
1360
 **/
1361
function securiser_acces($id_auteur, $cle, $dir, $op = '', $args = '') {
1362
	include_spip('inc/acces');
1363
	if ($op) {
1364
		$dir .= " $op $args";
1365
	}
1366
1367
	return verifier_low_sec($id_auteur, $cle, $dir);
1368
}
1369
1370
/**
1371
 * Retourne le second paramètre lorsque
1372
 * le premier est considere vide, sinon retourne le premier paramètre.
1373
 *
1374
 * En php `sinon($a, 'rien')` retourne `$a`, ou `'rien'` si `$a` est vide.
1375
 * En filtre SPIP `|sinon{#TEXTE, rien}` : affiche `#TEXTE` ou `rien` si `#TEXTE` est vide,
1376
 *
1377
 * @filtre
1378
 * @see filtre_logique() pour la compilation du filtre dans un squelette
1379
 * @link https://www.spip.net/4313
1380
 * @note
1381
 *     L'utilisation de `|sinon` en tant que filtre de squelette
1382
 *     est directement compilé dans `public/references` par la fonction `filtre_logique()`
1383
 *
1384
 * @param mixed $texte
1385
 *     Contenu de reference a tester
1386
 * @param mixed $sinon
1387
 *     Contenu a retourner si le contenu de reference est vide
1388
 * @return mixed
1389
 *     Retourne $texte, sinon $sinon.
1390
 **/
1391
function sinon($texte, $sinon = '') {
1392
	if ($texte or (!is_array($texte) and strlen($texte))) {
1393
		return $texte;
1394
	} else {
1395
		return $sinon;
1396
	}
1397
}
1398
1399
/**
1400
 * Filtre `|choixsivide{vide, pas vide}` alias de `|?{si oui, si non}` avec les arguments inversés
1401
 *
1402
 * @example
1403
 *     `[(#TEXTE|choixsivide{vide, plein})]` affiche vide si le `#TEXTE`
1404
 *     est considéré vide par PHP (chaîne vide, false, 0, tableau vide, etc…).
1405
 *     C'est l'équivalent de `[(#TEXTE|?{plein, vide})]`
1406
 *
1407
 * @filtre
1408
 * @see choixsiegal()
1409
 * @link https://www.spip.net/4189
1410
 *
1411
 * @param mixed $a
1412
 *     La valeur à tester
1413
 * @param mixed $vide
1414
 *     Ce qui est retourné si `$a` est considéré vide
1415
 * @param mixed $pasvide
1416
 *     Ce qui est retourné sinon
1417
 * @return mixed
1418
 **/
1419
function choixsivide($a, $vide, $pasvide) {
1420
	return $a ? $pasvide : $vide;
1421
}
1422
1423
/**
1424
 * Filtre `|choixsiegal{valeur, sioui, sinon}`
1425
 *
1426
 * @example
1427
 *     `#LANG_DIR|choixsiegal{ltr,left,right}` retourne `left` si
1428
 *      `#LANG_DIR` vaut `ltr` et `right` sinon.
1429
 *
1430
 * @filtre
1431
 * @link https://www.spip.net/4148
1432
 *
1433
 * @param mixed $a1
1434
 *     La valeur à tester
1435
 * @param mixed $a2
1436
 *     La valeur de comparaison
1437
 * @param mixed $v
1438
 *     Ce qui est retourné si la comparaison est vraie
1439
 * @param mixed $f
1440
 *     Ce qui est retourné sinon
1441
 * @return mixed
1442
 **/
1443
function choixsiegal($a1, $a2, $v, $f) {
1444
	return ($a1 == $a2) ? $v : $f;
1445
}
1446
1447
//
1448
// Export iCal
1449
//
1450
1451
/**
1452
 * Adapte un texte pour être inséré dans une valeur d'un export ICAL
1453
 *
1454
 * Passe le texte en utf8, enlève les sauts de lignes et échappe les virgules.
1455
 *
1456
 * @example `SUMMARY:[(#TITRE|filtrer_ical)]`
1457
 * @filtre
1458
 *
1459
 * @param string $texte
1460
 * @return string
1461
 **/
1462
function filtrer_ical($texte) {
1463
	#include_spip('inc/charsets');
1464
	$texte = html2unicode($texte);
1465
	$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...
1466
	$texte = preg_replace("/\n/", " ", $texte);
1467
	$texte = preg_replace("/,/", "\,", $texte);
1468
1469
	return $texte;
1470
}
1471
1472
1473
/**
1474
 * Transforme les sauts de ligne simples en sauts forcés avec `_ `
1475
 *
1476
 * Ne modifie pas les sauts de paragraphe (2 sauts consécutifs au moins),
1477
 * ou les retours à l'intérieur de modèles ou de certaines balises html.
1478
 *
1479
 * @note
1480
 *     Cette fonction pouvait être utilisée pour forcer les alinéas,
1481
 *     (retours à la ligne sans saut de paragraphe), mais ce traitement
1482
 *     est maintenant automatique.
1483
 *     Cf. plugin Textwheel et la constante _AUTOBR
1484
 *
1485
 * @uses echappe_html()
1486
 * @uses echappe_retour()
1487
 *
1488
 * @param string $texte
1489
 * @param string $delim
1490
 *      Ce par quoi sont remplacés les sauts
1491
 * @return string
1492
 **/
1493
function post_autobr($texte, $delim = "\n_ ") {
1494
	if (!function_exists('echappe_html')) {
1495
		include_spip('inc/texte_mini');
1496
	}
1497
	$texte = str_replace("\r\n", "\r", $texte);
1498
	$texte = str_replace("\r", "\n", $texte);
1499
1500 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...
1501
		$texte = substr($texte, 0, -strlen($fin = $fin[0]));
1502
	} else {
1503
		$fin = '';
1504
	}
1505
1506
	$texte = echappe_html($texte, '', true);
1507
1508
	// echapper les modeles
1509
	if (strpos($texte, "<") !== false) {
1510
		include_spip('inc/lien');
1511
		if (defined('_PREG_MODELE')) {
1512
			$preg_modeles = "@" . _PREG_MODELE . "@imsS";
1513
			$texte = echappe_html($texte, '', true, $preg_modeles);
1514
		}
1515
	}
1516
1517
	$debut = '';
1518
	$suite = $texte;
1519
	while ($t = strpos('-' . $suite, "\n", 1)) {
1520
		$debut .= substr($suite, 0, $t - 1);
1521
		$suite = substr($suite, $t);
1522
		$car = substr($suite, 0, 1);
1523
		if (($car <> '-') and ($car <> '_') and ($car <> "\n") and ($car <> "|") and ($car <> "}")
1524
			and !preg_match(',^\s*(\n|</?(quote|div|dl|dt|dd)|$),S', ($suite))
1525
			and !preg_match(',</?(quote|div|dl|dt|dd)> *$,iS', $debut)
1526
		) {
1527
			$debut .= $delim;
1528
		} else {
1529
			$debut .= "\n";
1530
		}
1531 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...
1532
			$debut .= $regs[0];
1533
			$suite = substr($suite, strlen($regs[0]));
1534
		}
1535
	}
1536
	$texte = $debut . $suite;
1537
1538
	$texte = echappe_retour($texte);
1539
1540
	return $texte . $fin;
1541
}
1542
1543
1544
/**
1545
 * Expression régulière pour obtenir le contenu des extraits idiomes `<:module:cle:>`
1546
 *
1547
 * @var string
1548
 */
1549
define('_EXTRAIRE_IDIOME', '@<:(?:([a-z0-9_]+):)?([a-z0-9_]+):>@isS');
1550
1551
/**
1552
 * Extrait une langue des extraits idiomes (`<:module:cle_de_langue:>`)
1553
 *
1554
 * Retrouve les balises `<:cle_de_langue:>` d'un texte et remplace son contenu
1555
 * par l'extrait correspondant à la langue demandée (si possible), sinon dans la
1556
 * langue par défaut du site.
1557
 *
1558
 * Ne pas mettre de span@lang=fr si on est déjà en fr.
1559
 *
1560
 * @filtre
1561
 * @uses inc_traduire_dist()
1562
 * @uses code_echappement()
1563
 * @uses echappe_retour()
1564
 *
1565
 * @param string $letexte
1566
 * @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...
1567
 *     Langue à retrouver (si vide, utilise la langue en cours).
1568
 * @param array $options Options {
1569
 * @type bool $echappe_span
1570
 *         True pour échapper les balises span (false par défaut)
1571
 * @type string $lang_defaut
1572
 *         Code de langue : permet de définir la langue utilisée par défaut,
1573
 *         en cas d'absence de traduction dans la langue demandée.
1574
 *         Par défaut la langue du site.
1575
 *         Indiquer 'aucune' pour ne pas retourner de texte si la langue
1576
 *         exacte n'a pas été trouvée.
1577
 * }
1578
 * @return string
1579
 **/
1580
function extraire_idiome($letexte, $lang = null, $options = array()) {
1581
	static $traduire = false;
1582
	if ($letexte
1583
		and preg_match_all(_EXTRAIRE_IDIOME, $letexte, $regs, PREG_SET_ORDER)
1584
	) {
1585
		if (!$traduire) {
1586
			$traduire = charger_fonction('traduire', 'inc');
1587
			include_spip('inc/lang');
1588
		}
1589
		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...
1590
			$lang = $GLOBALS['spip_lang'];
1591
		}
1592
		// Compatibilité avec le prototype de fonction précédente qui utilisait un boolean
1593
		if (is_bool($options)) {
1594
			$options = array('echappe_span' => $options);
1595
		}
1596 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...
1597
			$options = array_merge($options, array('echappe_span' => false));
1598
		}
1599
1600
		foreach ($regs as $reg) {
1601
			$cle = ($reg[1] ? $reg[1] . ':' : '') . $reg[2];
1602
			$desc = $traduire($cle, $lang, true);
1603
			$l = $desc->langue;
1604
			// si pas de traduction, on laissera l'écriture de l'idiome entier dans le texte.
1605
			if (strlen($desc->texte)) {
1606
				$trad = code_echappement($desc->texte, 'idiome', false);
1607
				if ($l !== $lang) {
1608
					$trad = str_replace("'", '"', inserer_attribut($trad, 'lang', $l));
1609
				}
1610 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...
1611
					$trad = str_replace("'", '"', inserer_attribut($trad, 'dir', lang_dir($l)));
1612
				}
1613
				if (!$options['echappe_span']) {
1614
					$trad = echappe_retour($trad, 'idiome');
1615
				}
1616
				$letexte = str_replace($reg[0], $trad, $letexte);
1617
			}
1618
		}
1619
	}
1620
	return $letexte;
1621
}
1622
1623
/**
1624
 * Expression régulière pour obtenir le contenu des extraits polyglottes `<multi>`
1625
 *
1626
 * @var string
1627
 */
1628
define('_EXTRAIRE_MULTI', "@<multi>(.*?)</multi>@sS");
1629
1630
1631
/**
1632
 * Extrait une langue des extraits polyglottes (`<multi>`)
1633
 *
1634
 * Retrouve les balises `<multi>` d'un texte et remplace son contenu
1635
 * par l'extrait correspondant à la langue demandée.
1636
 *
1637
 * Si la langue demandée n'est pas trouvée dans le multi, ni une langue
1638
 * approchante (exemple `fr` si on demande `fr_TU`), on retourne l'extrait
1639
 * correspondant à la langue par défaut (option 'lang_defaut'), qui est
1640
 * par défaut la langue du site. Et si l'extrait n'existe toujours pas
1641
 * dans cette langue, ça utilisera la première langue utilisée
1642
 * dans la balise `<multi>`.
1643
 *
1644
 * Ne pas mettre de span@lang=fr si on est déjà en fr.
1645
 *
1646
 * @filtre
1647
 * @link https://www.spip.net/5332
1648
 *
1649
 * @uses extraire_trads()
1650
 * @uses approcher_langue()
1651
 * @uses lang_typo()
1652
 * @uses code_echappement()
1653
 * @uses echappe_retour()
1654
 *
1655
 * @param string $letexte
1656
 * @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...
1657
 *     Langue à retrouver (si vide, utilise la langue en cours).
1658
 * @param array $options Options {
1659
 * @type bool $echappe_span
1660
 *         True pour échapper les balises span (false par défaut)
1661
 * @type string $lang_defaut
1662
 *         Code de langue : permet de définir la langue utilisée par défaut,
1663
 *         en cas d'absence de traduction dans la langue demandée.
1664
 *         Par défaut la langue du site.
1665
 *         Indiquer 'aucune' pour ne pas retourner de texte si la langue
1666
 *         exacte n'a pas été trouvée.
1667
 * }
1668
 * @return string
1669
 **/
1670
function extraire_multi($letexte, $lang = null, $options = array()) {
1671
1672
	if ($letexte
1673
		and preg_match_all(_EXTRAIRE_MULTI, $letexte, $regs, PREG_SET_ORDER)
1674
	) {
1675
		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...
1676
			$lang = $GLOBALS['spip_lang'];
1677
		}
1678
1679
		// Compatibilité avec le prototype de fonction précédente qui utilisait un boolean
1680
		if (is_bool($options)) {
1681
			$options = array('echappe_span' => $options, 'lang_defaut' => _LANGUE_PAR_DEFAUT);
1682
		}
1683 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...
1684
			$options = array_merge($options, array('echappe_span' => false));
1685
		}
1686
		if (!isset($options['lang_defaut'])) {
1687
			$options = array_merge($options, array('lang_defaut' => _LANGUE_PAR_DEFAUT));
1688
		}
1689
1690
		include_spip('inc/lang');
1691
		foreach ($regs as $reg) {
1692
			// chercher la version de la langue courante
1693
			$trads = extraire_trads($reg[1]);
1694
			if ($l = approcher_langue($trads, $lang)) {
1695
				$trad = $trads[$l];
1696
			} else {
1697
				if ($options['lang_defaut'] == 'aucune') {
1698
					$trad = '';
1699
				} else {
1700
					// langue absente, prendre le fr ou une langue précisée (meme comportement que inc/traduire.php)
1701
					// ou la premiere dispo
1702
					// mais typographier le texte selon les regles de celle-ci
1703
					// Attention aux blocs multi sur plusieurs lignes
1704
					if (!$l = approcher_langue($trads, $options['lang_defaut'])) {
1705
						$l = key($trads);
1706
					}
1707
					$trad = $trads[$l];
1708
					$typographie = charger_fonction(lang_typo($l), 'typographie');
1709
					$trad = $typographie($trad);
1710
					// Tester si on echappe en span ou en div
1711
					// il ne faut pas echapper en div si propre produit un seul paragraphe
1712
					include_spip('inc/texte');
1713
					$trad_propre = preg_replace(",(^<p[^>]*>|</p>$),Uims", "", propre($trad));
1714
					$mode = preg_match(',</?(' . _BALISES_BLOCS . ')[>[:space:]],iS', $trad_propre) ? 'div' : 'span';
1715
					$trad = code_echappement($trad, 'multi', false, $mode);
1716
					$trad = str_replace("'", '"', inserer_attribut($trad, 'lang', $l));
1717 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...
1718
						$trad = str_replace("'", '"', inserer_attribut($trad, 'dir', lang_dir($l)));
1719
					}
1720
					if (!$options['echappe_span']) {
1721
						$trad = echappe_retour($trad, 'multi');
1722
					}
1723
				}
1724
			}
1725
			$letexte = str_replace($reg[0], $trad, $letexte);
1726
		}
1727
	}
1728
1729
	return $letexte;
1730
}
1731
1732
/**
1733
 * Convertit le contenu d'une balise `<multi>` en un tableau
1734
 *
1735
 * Exemple de blocs.
1736
 * - `texte par défaut [fr] en français [en] en anglais`
1737
 * - `[fr] en français [en] en anglais`
1738
 *
1739
 * @param string $bloc
1740
 *     Le contenu intérieur d'un bloc multi
1741
 * @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...
1742
 *     Peut retourner un code de langue vide, lorsqu'un texte par défaut est indiqué.
1743
 **/
1744
function extraire_trads($bloc) {
1745
	$lang = '';
1746
// ce reg fait planter l'analyse multi s'il y a de l'{italique} dans le champ
1747
//	while (preg_match("/^(.*?)[{\[]([a-z_]+)[}\]]/siS", $bloc, $regs)) {
1748
	while (preg_match("/^(.*?)[\[]([a-z_]+)[\]]/siS", $bloc, $regs)) {
1749
		$texte = trim($regs[1]);
1750
		if ($texte or $lang) {
1751
			$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...
1752
		}
1753
		$bloc = substr($bloc, strlen($regs[0]));
1754
		$lang = $regs[2];
1755
	}
1756
	$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...
1757
1758
	return $trads;
1759
}
1760
1761
1762
/**
1763
 * Calculer l'initiale d'un nom
1764
 *
1765
 * @param string $nom
1766
 * @return string L'initiale en majuscule
1767
 */
1768
function filtre_initiale($nom) {
1769
	return spip_substr(trim(strtoupper(extraire_multi($nom))), 0, 1);
1770
}
1771
1772
1773
/**
1774
 * Retourne la donnée si c'est la première fois qu'il la voit
1775
 *
1776
 * Il est possible de gérer différentes "familles" de données avec
1777
 * le second paramètre.
1778
 *
1779
 * @filtre
1780
 * @link https://www.spip.net/4320
1781
 * @example
1782
 *     ```
1783
 *     [(#ID_SECTEUR|unique)]
1784
 *     [(#ID_SECTEUR|unique{tete})] n'a pas d'incidence sur
1785
 *     [(#ID_SECTEUR|unique{pied})]
1786
 *     [(#ID_SECTEUR|unique{pied,1})] affiche le nombre d'éléments.
1787
 *     Préférer totefois #TOTAL_UNIQUE{pied}
1788
 *     ```
1789
 *
1790
 * @todo
1791
 *    Ameliorations possibles :
1792
 *
1793
 *    1) si la donnée est grosse, mettre son md5 comme clé
1794
 *    2) purger $mem quand on change de squelette (sinon bug inclusions)
1795
 *
1796
 * @param string $donnee
1797
 *      Donnée que l'on souhaite unique
1798
 * @param string $famille
1799
 *      Famille de stockage (1 unique donnée par famille)
1800
 *
1801
 *      - _spip_raz_ : (interne) Vide la pile de mémoire et la retourne
1802
 *      - _spip_set_ : (interne) Affecte la pile de mémoire avec la donnée
1803
 * @param bool $cpt
1804
 *      True pour obtenir le nombre d'éléments différents stockés
1805
 * @return string|int|array|null|void
1806
 *
1807
 *      - string : Donnée si c'est la première fois qu'elle est vue
1808
 *      - void : si la donnée a déjà été vue
1809
 *      - int : si l'on demande le nombre d'éléments
1810
 *      - array (interne) : si on dépile
1811
 *      - null (interne) : si on empile
1812
 **/
1813
function unique($donnee, $famille = '', $cpt = false) {
1814
	static $mem = array();
1815
	// permettre de vider la pile et de la restaurer
1816
	// pour le calcul de introduction...
1817
	if ($famille == '_spip_raz_') {
1818
		$tmp = $mem;
1819
		$mem = array();
1820
1821
		return $tmp;
1822
	} elseif ($famille == '_spip_set_') {
1823
		$mem = $donnee;
1824
1825
		return;
1826
	}
1827
	// eviter une notice
1828
	if (!isset($mem[$famille])) {
1829
		$mem[$famille] = array();
1830
	}
1831
	if ($cpt) {
1832
		return count($mem[$famille]);
1833
	}
1834
	// eviter une notice
1835
	if (!isset($mem[$famille][$donnee])) {
1836
		$mem[$famille][$donnee] = 0;
1837
	}
1838
	if (!($mem[$famille][$donnee]++)) {
1839
		return $donnee;
1840
	}
1841
}
1842
1843
1844
/**
1845
 * Filtre qui alterne des valeurs en fonction d'un compteur
1846
 *
1847
 * Affiche à tour de rôle et dans l'ordre, un des arguments transmis
1848
 * à chaque incrément du compteur.
1849
 *
1850
 * S'il n'y a qu'un seul argument, et que c'est un tableau,
1851
 * l'alternance se fait sur les valeurs du tableau.
1852
 *
1853
 * Souvent appliqué à l'intérieur d'une boucle, avec le compteur `#COMPTEUR_BOUCLE`
1854
 *
1855
 * @example
1856
 *     - `[(#COMPTEUR_BOUCLE|alterner{bleu,vert,rouge})]`
1857
 *     - `[(#COMPTEUR_BOUCLE|alterner{#LISTE{bleu,vert,rouge}})]`
1858
 *
1859
 * @filtre
1860
 * @link https://www.spip.net/4145
1861
 *
1862
 * @param int $i
1863
 *     Le compteur
1864
 * @return mixed
1865
 *     Une des valeurs en fonction du compteur.
1866
 **/
1867
function alterner($i) {
1868
	// recuperer les arguments (attention fonctions un peu space)
1869
	$num = func_num_args();
1870
	$args = func_get_args();
1871
1872
	if ($num == 2 && is_array($args[1])) {
1873
		$args = $args[1];
1874
		array_unshift($args, '');
1875
		$num = count($args);
1876
	}
1877
1878
	// renvoyer le i-ieme argument, modulo le nombre d'arguments
1879
	return $args[(intval($i) - 1) % ($num - 1) + 1];
1880
}
1881
1882
1883
/**
1884
 * Récupérer un attribut d'une balise HTML
1885
 *
1886
 * la regexp est mortelle : cf. `tests/unit/filtres/extraire_attribut.php`
1887
 * Si on a passé un tableau de balises, renvoyer un tableau de résultats
1888
 * (dans ce cas l'option `$complet` n'est pas disponible)
1889
 *
1890
 * @param string|array $balise
1891
 *     Texte ou liste de textes dont on veut extraire des balises
1892
 * @param string $attribut
1893
 *     Nom de l'attribut désiré
1894
 * @param bool $complet
1895
 *     True pour retourner un tableau avec
1896
 *     - le texte de la balise
1897
 *     - l'ensemble des résultats de la regexp ($r)
1898
 * @return string|array
1899
 *     - Texte de l'attribut retourné, ou tableau des texte d'attributs
1900
 *       (si 1er argument tableau)
1901
 *     - Tableau complet (si 2e argument)
1902
 **/
1903
function extraire_attribut($balise, $attribut, $complet = false) {
1904
	if (is_array($balise)) {
1905
		array_walk(
1906
			$balise,
1907
			function(&$a, $key, $t){
1908
				$a = extraire_attribut($a, $t);
1909
			},
1910
			$attribut
1911
		);
1912
1913
		return $balise;
1914
	}
1915
	if (preg_match(
1916
		',(^.*?<(?:(?>\s*)(?>[\w:.-]+)(?>(?:=(?:"[^"]*"|\'[^\']*\'|[^\'"]\S*))?))*?)(\s+'
1917
		. $attribut
1918
		. '(?:=\s*("[^"]*"|\'[^\']*\'|[^\'"]\S*))?)()((?:[\s/][^>]*)?>.*),isS',
1919
1920
		$balise, $r)) {
1921
		if (isset($r[3][0]) and ($r[3][0] == '"' || $r[3][0] == "'")) {
1922
			$r[4] = substr($r[3], 1, -1);
1923
			$r[3] = $r[3][0];
1924
		} elseif ($r[3] !== '') {
1925
			$r[4] = $r[3];
1926
			$r[3] = '';
1927
		} else {
1928
			$r[4] = trim($r[2]);
1929
		}
1930
		$att = $r[4];
1931 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...
1932
			$att = str_replace(array("&#039;", "&#39;", "&#034;", "&#34;"), array("'", "'", '"', '"'), $att);
1933
		}
1934
		$att = filtrer_entites($att);
1935
	} else {
1936
		$att = null;
1937
	}
1938
1939
	if ($complet) {
1940
		return array($att, $r);
1941
	} else {
1942
		return $att;
1943
	}
1944
}
1945
1946
/**
1947
 * Insérer (ou modifier) un attribut html dans une balise
1948
 *
1949
 * @example
1950
 *     - `[(#LOGO_ARTICLE|inserer_attribut{class, logo article})]`
1951
 *     - `[(#LOGO_ARTICLE|inserer_attribut{alt, #TTTRE|attribut_html|couper{60}})]`
1952
 *     - `[(#FICHIER|image_reduire{40}|inserer_attribut{data-description, #DESCRIPTIF})]`
1953
 *       Laissera les balises HTML de la valeur (ici `#DESCRIPTIF`) si on n'applique pas le
1954
 *       filtre `attribut_html` dessus.
1955
 *
1956
 * @filtre
1957
 * @link https://www.spip.net/4294
1958
 * @uses attribut_html()
1959
 * @uses extraire_attribut()
1960
 *
1961
 * @param string $balise
1962
 *     Code html de la balise (ou contenant une balise)
1963
 * @param string $attribut
1964
 *     Nom de l'attribut html à modifier
1965
 * @param string $val
1966
 *     Valeur de l'attribut à appliquer
1967
 * @param bool $proteger
1968
 *     Prépare la valeur en tant qu'attribut de balise (mais conserve les balises html).
1969
 * @param bool $vider
1970
 *     True pour vider l'attribut. Une chaîne vide pour `$val` fera pareil.
1971
 * @return string
1972
 *     Code html modifié
1973
 **/
1974
function inserer_attribut($balise, $attribut, $val, $proteger = true, $vider = false) {
1975
	// preparer l'attribut
1976
	// supprimer les &nbsp; etc mais pas les balises html
1977
	// qui ont un sens dans un attribut value d'un input
1978
	if ($proteger) {
1979
		$val = attribut_html($val, false);
1980
	}
1981
1982
	// echapper les ' pour eviter tout bug
1983
	$val = str_replace("'", "&#039;", $val);
1984
	if ($vider and strlen($val) == 0) {
1985
		$insert = '';
1986
	} else {
1987
		$insert = " $attribut='$val'";
1988
	}
1989
1990
	list($old, $r) = extraire_attribut($balise, $attribut, true);
1991
1992
	if ($old !== null) {
1993
		// Remplacer l'ancien attribut du meme nom
1994
		$balise = $r[1] . $insert . $r[5];
1995
	} else {
1996
		// preferer une balise " />" (comme <img />)
1997
		if (preg_match(',/>,', $balise)) {
1998
			$balise = preg_replace(",\s?/>,S", $insert . " />", $balise, 1);
1999
		} // sinon une balise <a ...> ... </a>
2000
		else {
2001
			$balise = preg_replace(",\s?>,S", $insert . ">", $balise, 1);
2002
		}
2003
	}
2004
2005
	return $balise;
2006
}
2007
2008
/**
2009
 * Supprime un attribut HTML
2010
 *
2011
 * @example `[(#LOGO_ARTICLE|vider_attribut{class})]`
2012
 *
2013
 * @filtre
2014
 * @link https://www.spip.net/4142
2015
 * @uses inserer_attribut()
2016
 * @see  extraire_attribut()
2017
 *
2018
 * @param string $balise Code HTML de l'élément
2019
 * @param string $attribut Nom de l'attribut à enlever
2020
 * @return string Code HTML sans l'attribut
2021
 **/
2022
function vider_attribut($balise, $attribut) {
2023
	return inserer_attribut($balise, $attribut, '', false, true);
2024
}
2025
2026
/**
2027
 * Fonction support pour les filtres |ajouter_class |supprimer_class |commuter_class
2028
 *
2029
 * @param string $balise
2030
 * @param string|array $class
2031
 * @param string $operation
2032
 * @return string
2033
 */
2034
function modifier_class($balise, $class, $operation='ajouter') {
2035
	if (is_string($class)) {
2036
		$class = explode(' ', trim($class));
2037
	}
2038
	$class = array_filter($class);
2039
2040
	// si la ou les classes ont des caracteres invalides on ne fait rien
2041
	if (preg_match(",[^\w-],", implode('', $class))) {
2042
		return $balise;
2043
	}
2044
2045
	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...
2046
		$class = array_unique($class);
2047
		$class_courante = extraire_attribut($balise, 'class');
2048
2049
		$class_new = $class_courante;
2050
		foreach ($class as $c) {
2051
			if ($c) {
2052
				$is_class_presente = false;
2053
				if (strpos($class_courante, $c) !== false
2054
					and preg_match("/(^|\s)".preg_quote($c)."($|\s)/", $class_courante)) {
2055
					$is_class_presente = true;
2056
				}
2057
				if (in_array($operation, ['ajouter', 'commuter'])
2058
					and !$is_class_presente) {
2059
					$class_new = rtrim($class_new) . " " . $c;
2060
				}
2061
				elseif (in_array($operation, ['supprimer', 'commuter'])
2062
					and $is_class_presente) {
2063
					$class_new = trim(preg_replace("/(^|\s)".preg_quote($c)."($|\s)/", "\\1", $class_new));
2064
				}
2065
			}
2066
		}
2067
2068
		if ($class_new !== $class_courante) {
2069
			if (strlen($class_new)) {
2070
				$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 2049 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...
2071
			}
2072
			elseif ($class_courante) {
2073
				$balise = vider_attribut($balise, 'class');
2074
			}
2075
		}
2076
	}
2077
2078
	return $balise;
2079
}
2080
2081
/**
2082
 * Ajoute une ou plusieurs classes sur une balise (si pas deja presentes)
2083
 * @param string $balise
2084
 * @param string|array $class
2085
 * @return string
2086
 */
2087
function ajouter_class($balise, $class){
2088
	return modifier_class($balise, $class, 'ajouter');
2089
}
2090
2091
/**
2092
 * Supprime une ou plusieurs classes sur une balise (si presentes)
2093
 * @param string $balise
2094
 * @param string|array $class
2095
 * @return string
2096
 */
2097
function supprimer_class($balise, $class){
2098
	return modifier_class($balise, $class, 'supprimer');
2099
}
2100
2101
/**
2102
 * Bascule une ou plusieurs classes sur une balise : ajoutees si absentes, supprimees si presentes
2103
 *
2104
 * @param string $balise
2105
 * @param string|array $class
2106
 * @return string
2107
 */
2108
function commuter_class($balise, $class){
2109
	return modifier_class($balise, $class, 'commuter');
2110
}
2111
2112
/**
2113
 * Un filtre pour déterminer le nom du statut des inscrits
2114
 *
2115
 * @param void|int $id
2116
 * @param string $mode
2117
 * @return string
2118
 */
2119
function tester_config($id, $mode = '') {
2120
	include_spip('action/inscrire_auteur');
2121
2122
	return tester_statut_inscription($mode, $id);
2123
}
2124
2125
//
2126
// Quelques fonctions de calcul arithmetique
2127
//
2128
function floatstr($a) { return str_replace(',','.',(string)floatval($a)); }
2129
function strize($f, $a, $b) { return floatstr($f(floatstr($a),floatstr($b))); }
2130
2131
/**
2132
 * Additionne 2 nombres
2133
 *
2134
 * @filtre
2135
 * @link https://www.spip.net/4307
2136
 * @see moins()
2137
 * @example
2138
 *     ```
2139
 *     [(#VAL{28}|plus{14})]
2140
 *     ```
2141
 *
2142
 * @param int $a
2143
 * @param int $b
2144
 * @return int $a+$b
2145
 **/
2146
function plus($a, $b) {
2147
	return $a + $b;
2148
}
2149
function strplus($a, $b) {return strize('plus', $a, $b);}
2150
/**
2151
 * Soustrait 2 nombres
2152
 *
2153
 * @filtre
2154
 * @link https://www.spip.net/4302
2155
 * @see plus()
2156
 * @example
2157
 *     ```
2158
 *     [(#VAL{28}|moins{14})]
2159
 *     ```
2160
 *
2161
 * @param int $a
2162
 * @param int $b
2163
 * @return int $a-$b
2164
 **/
2165
function moins($a, $b) {
2166
	return $a - $b;
2167
}
2168
function strmoins($a, $b) {return strize('moins', $a, $b);}
2169
2170
/**
2171
 * Multiplie 2 nombres
2172
 *
2173
 * @filtre
2174
 * @link https://www.spip.net/4304
2175
 * @see div()
2176
 * @see modulo()
2177
 * @example
2178
 *     ```
2179
 *     [(#VAL{28}|mult{14})]
2180
 *     ```
2181
 *
2182
 * @param int $a
2183
 * @param int $b
2184
 * @return int $a*$b
2185
 **/
2186
function mult($a, $b) {
2187
	return $a * $b;
2188
}
2189
function strmult($a, $b) {return strize('mult', $a, $b);}
2190
2191
/**
2192
 * Divise 2 nombres
2193
 *
2194
 * @filtre
2195
 * @link https://www.spip.net/4279
2196
 * @see mult()
2197
 * @see modulo()
2198
 * @example
2199
 *     ```
2200
 *     [(#VAL{28}|div{14})]
2201
 *     ```
2202
 *
2203
 * @param int $a
2204
 * @param int $b
2205
 * @return int $a/$b (ou 0 si $b est nul)
2206
 **/
2207
function div($a, $b) {
2208
	return $b ? $a / $b : 0;
2209
}
2210
function strdiv($a, $b) {return strize('div', $a, $b);}
2211
2212
/**
2213
 * Retourne le modulo 2 nombres
2214
 *
2215
 * @filtre
2216
 * @link https://www.spip.net/4301
2217
 * @see mult()
2218
 * @see div()
2219
 * @example
2220
 *     ```
2221
 *     [(#VAL{28}|modulo{14})]
2222
 *     ```
2223
 *
2224
 * @param int $nb
2225
 * @param int $mod
2226
 * @param int $add
2227
 * @return int ($nb % $mod) + $add
2228
 **/
2229
function modulo($nb, $mod, $add = 0) {
2230
	return ($mod ? $nb % $mod : 0) + $add;
2231
}
2232
2233
2234
/**
2235
 * Vérifie qu'un nom (d'auteur) ne comporte pas d'autres tags que <multi>
2236
 * et ceux volontairement spécifiés dans la constante
2237
 *
2238
 * @param string $nom
2239
 *      Nom (signature) proposé
2240
 * @return bool
2241
 *      - false si pas conforme,
2242
 *      - true sinon
2243
 **/
2244
function nom_acceptable($nom) {
2245
	if (!is_string($nom)) {
2246
		return false;
2247
	}
2248
	if (!defined('_TAGS_NOM_AUTEUR')) {
2249
		define('_TAGS_NOM_AUTEUR', '');
2250
	}
2251
	$tags_acceptes = array_unique(explode(',', 'multi,' . _TAGS_NOM_AUTEUR));
2252
	foreach ($tags_acceptes as $tag) {
2253
		if (strlen($tag)) {
2254
			$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...
2255
			$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...
2256
			$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...
2257
			$remp2[] = '\x60/' . trim($tag) . '\x61';
2258
		}
2259
	}
2260
	$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...
2261
2262
	return str_replace('&lt;', '<', $v_nom) == $nom;
2263
}
2264
2265
2266
/**
2267
 * Vérifier la conformité d'une ou plusieurs adresses email (suivant RFC 822)
2268
 *
2269
 * @param string|array $adresses
2270
 *      Adresse ou liste d'adresse
2271
 *      si on fournit un tableau, il est filtre et la fonction renvoie avec uniquement les adresses email valides (donc possiblement vide)
2272
 * @return bool|string|array
2273
 *      - false si une des adresses n'est pas conforme,
2274
 *      - la normalisation de la dernière adresse donnée sinon
2275
 *      - renvoie un tableau si l'entree est un tableau
2276
 **/
2277
function email_valide($adresses) {
2278
	if (is_array($adresses)) {
2279
		$adresses = array_map('email_valide', $adresses);
2280
		$adresses = array_filter($adresses);
2281
		return $adresses;
2282
	}
2283
2284
	$email_valide = charger_fonction('email_valide', 'inc');
2285
	return $email_valide($adresses);
2286
}
2287
2288
/**
2289
 * Permet d'afficher un symbole à côté des liens pointant vers les
2290
 * documents attachés d'un article (liens ayant `rel=enclosure`).
2291
 *
2292
 * @filtre
2293
 * @link https://www.spip.net/4134
2294
 *
2295
 * @param string $tags Texte
2296
 * @return string Texte
2297
 **/
2298
function afficher_enclosures($tags) {
2299
	$s = array();
2300
	foreach (extraire_balises($tags, 'a') as $tag) {
2301
		if (extraire_attribut($tag, 'rel') == 'enclosure'
2302
			and $t = extraire_attribut($tag, 'href')
2303
		) {
2304
			$s[] = preg_replace(',>[^<]+</a>,S',
2305
				'>'
2306
				. 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 2302 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...
2307
					'title="' . attribut_html($t) . '"')
0 ignored issues
show
Bug introduced by
It seems like $t defined by extraire_attribut($tag, 'href') on line 2302 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...
2308
				. '</a>', $tag);
2309
		}
2310
	}
2311
2312
	return join('&nbsp;', $s);
2313
}
2314
2315
/**
2316
 * Filtre des liens HTML `<a>` selon la valeur de leur attribut `rel`
2317
 * et ne retourne que ceux là.
2318
 *
2319
 * @filtre
2320
 * @link https://www.spip.net/4187
2321
 *
2322
 * @param string $tags Texte
2323
 * @param string $rels Attribut `rel` à capturer (ou plusieurs séparés par des virgules)
2324
 * @return string Liens trouvés
2325
 **/
2326
function afficher_tags($tags, $rels = 'tag,directory') {
2327
	$s = array();
2328
	foreach (extraire_balises($tags, 'a') as $tag) {
2329
		$rel = extraire_attribut($tag, 'rel');
2330
		if (strstr(",$rels,", ",$rel,")) {
2331
			$s[] = $tag;
2332
		}
2333
	}
2334
2335
	return join(', ', $s);
2336
}
2337
2338
2339
/**
2340
 * Convertir les médias fournis par un flux RSS (podcasts)
2341
 * en liens conformes aux microformats
2342
 *
2343
 * Passe un `<enclosure url="fichier" length="5588242" type="audio/mpeg"/>`
2344
 * au format microformat `<a rel="enclosure" href="fichier" ...>fichier</a>`.
2345
 *
2346
 * Peut recevoir un `<link` ou un `<media:content` parfois.
2347
 *
2348
 * Attention : `length="zz"` devient `title="zz"`, pour rester conforme.
2349
 *
2350
 * @filtre
2351
 * @see microformat2enclosure() Pour l'inverse
2352
 *
2353
 * @param string $e Tag RSS `<enclosure>`
2354
 * @return string Tag HTML `<a>` avec microformat.
2355
 **/
2356
function enclosure2microformat($e) {
2357
	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...
2358
		$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...
2359
	}
2360
	$type = extraire_attribut($e, 'type');
2361
	if (!$length = extraire_attribut($e, 'length')) {
2362
		# <media:content : longeur dans fileSize. On tente.
2363
		$length = extraire_attribut($e, 'fileSize');
2364
	}
2365
	$fichier = basename($url);
2366
2367
	return '<a rel="enclosure"'
2368
	. ($url ? ' href="' . spip_htmlspecialchars($url) . '"' : '')
2369
	. ($type ? ' type="' . spip_htmlspecialchars($type) . '"' : '')
0 ignored issues
show
Bug introduced by
It seems like $type defined by extraire_attribut($e, 'type') on line 2360 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...
2370
	. ($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...
2371
	. '>' . $fichier . '</a>';
2372
}
2373
2374
/**
2375
 * Convertir les liens conformes aux microformats en médias pour flux RSS,
2376
 * par exemple pour les podcasts
2377
 *
2378
 * Passe un texte ayant des liens avec microformat
2379
 * `<a rel="enclosure" href="fichier" ...>fichier</a>`
2380
 * au format RSS `<enclosure url="fichier" ... />`.
2381
 *
2382
 * @filtre
2383
 * @see enclosure2microformat() Pour l'inverse
2384
 *
2385
 * @param string $tags Texte HTML ayant des tag `<a>` avec microformat
2386
 * @return string Tags RSS `<enclosure>`.
2387
 **/
2388
function microformat2enclosure($tags) {
2389
	$enclosures = array();
2390
	foreach (extraire_balises($tags, 'a') as $e) {
2391
		if (extraire_attribut($e, 'rel') == 'enclosure') {
2392
			$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...
2393
			$type = extraire_attribut($e, 'type');
2394
			if (!$length = intval(extraire_attribut($e, 'title'))) {
2395
				$length = intval(extraire_attribut($e, 'length'));
2396
			} # vieux data
2397
			$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...
2398
			$enclosures[] = '<enclosure'
2399
				. ($url ? ' url="' . spip_htmlspecialchars($url) . '"' : '')
2400
				. ($type ? ' type="' . spip_htmlspecialchars($type) . '"' : '')
0 ignored issues
show
Bug introduced by
It seems like $type defined by extraire_attribut($e, 'type') on line 2393 can also be of type array; however, spip_htmlspecialchars() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
2401
				. ($length ? ' length="' . $length . '"' : '')
2402
				. ' />';
2403
		}
2404
	}
2405
2406
	return join("\n", $enclosures);
2407
}
2408
2409
2410
/**
2411
 * Créer les éléments ATOM `<dc:subject>` à partir des tags
2412
 *
2413
 * Convertit les liens avec attribut `rel="tag"`
2414
 * en balise `<dc:subject></dc:subject>` pour les flux RSS au format Atom.
2415
 *
2416
 * @filtre
2417
 *
2418
 * @param string $tags Texte
2419
 * @return string Tags RSS Atom `<dc:subject>`.
2420
 **/
2421
function tags2dcsubject($tags) {
2422
	$subjects = '';
2423
	foreach (extraire_balises($tags, 'a') as $e) {
2424
		if (extraire_attribut($e, rel) == 'tag') {
2425
			$subjects .= '<dc:subject>'
2426
				. texte_backend(textebrut($e))
2427
				. '</dc:subject>' . "\n";
2428
		}
2429
	}
2430
2431
	return $subjects;
2432
}
2433
2434
/**
2435
 * Retourne la premiere balise html du type demandé
2436
 *
2437
 * Retourne le contenu d'une balise jusqu'à la première fermeture rencontrée
2438
 * du même type.
2439
 * Si on a passe un tableau de textes, retourne un tableau de resultats.
2440
 *
2441
 * @example `[(#DESCRIPTIF|extraire_balise{img})]`
2442
 *
2443
 * @filtre
2444
 * @link https://www.spip.net/4289
2445
 * @see extraire_balises()
2446
 * @note
2447
 *     Attention : les résultats peuvent être incohérents sur des balises imbricables,
2448
 *     tel que demander à extraire `div` dans le texte `<div> un <div> mot </div> absent </div>`,
2449
 *     ce qui retournerait `<div> un <div> mot </div>` donc.
2450
 *
2451
 * @param string|array $texte
2452
 *     Texte(s) dont on souhaite extraire une balise html
2453
 * @param string $tag
2454
 *     Nom de la balise html à extraire
2455
 * @return void|string|array
2456
 *     - Code html de la balise, sinon rien
2457
 *     - Tableau de résultats, si tableau en entrée.
2458
 **/
2459 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...
2460
	if (is_array($texte)) {
2461
		array_walk(
2462
			$texte,
2463
			function(&$a, $key, $t){
2464
				$a = extraire_balise($a, $t);
2465
			},
2466
			$tag
2467
		);
2468
2469
		return $texte;
2470
	}
2471
2472
	if (preg_match(
2473
		",<$tag\b[^>]*(/>|>.*</$tag\b[^>]*>|>),UimsS",
2474
		$texte, $regs)) {
2475
		return $regs[0];
2476
	}
2477
}
2478
2479
/**
2480
 * Extrait toutes les balises html du type demandé
2481
 *
2482
 * Retourne dans un tableau le contenu de chaque balise jusqu'à la première
2483
 * fermeture rencontrée du même type.
2484
 * Si on a passe un tableau de textes, retourne un tableau de resultats.
2485
 *
2486
 * @example `[(#TEXTE|extraire_balises{img}|implode{" - "})]`
2487
 *
2488
 * @filtre
2489
 * @link https://www.spip.net/5618
2490
 * @see extraire_balise()
2491
 * @note
2492
 *     Attention : les résultats peuvent être incohérents sur des balises imbricables,
2493
 *     tel que demander à extraire `div` dans un texte.
2494
 *
2495
 * @param string|array $texte
2496
 *     Texte(s) dont on souhaite extraire une balise html
2497
 * @param string $tag
2498
 *     Nom de la balise html à extraire
2499
 * @return array
2500
 *     - Liste des codes html des occurrences de la balise, sinon tableau vide
2501
 *     - Tableau de résultats, si tableau en entrée.
2502
 **/
2503 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...
2504
	if (is_array($texte)) {
2505
		array_walk(
2506
			$texte,
2507
			function(&$a, $key, $t){
2508
				$a = extraire_balises($a, $t);
2509
			},
2510
			$tag
2511
		);
2512
2513
		return $texte;
2514
	}
2515
2516
	if (preg_match_all(
2517
		",<${tag}\b[^>]*(/>|>.*</${tag}\b[^>]*>|>),UimsS",
2518
		$texte, $regs, PREG_PATTERN_ORDER)) {
2519
		return $regs[0];
2520
	} else {
2521
		return array();
2522
	}
2523
}
2524
2525
/**
2526
 * Indique si le premier argument est contenu dans le second
2527
 *
2528
 * Cette fonction est proche de `in_array()` en PHP avec comme principale
2529
 * différence qu'elle ne crée pas d'erreur si le second argument n'est pas
2530
 * un tableau (dans ce cas elle tentera de le désérialiser, et sinon retournera
2531
 * la valeur par défaut transmise).
2532
 *
2533
 * @example `[(#VAL{deux}|in_any{#LISTE{un,deux,trois}}|oui) ... ]`
2534
 *
2535
 * @filtre
2536
 * @see filtre_find() Assez proche, avec les arguments valeur et tableau inversés.
2537
 *
2538
 * @param string $val
2539
 *     Valeur à chercher dans le tableau
2540
 * @param array|string $vals
2541
 *     Tableau des valeurs. S'il ce n'est pas un tableau qui est transmis,
2542
 *     la fonction tente de la désérialiser.
2543
 * @param string $def
2544
 *     Valeur par défaut retournée si `$vals` n'est pas un tableau.
2545
 * @return string
2546
 *     - ' ' si la valeur cherchée est dans le tableau
2547
 *     - '' si la valeur n'est pas dans le tableau
2548
 *     - `$def` si on n'a pas transmis de tableau
2549
 **/
2550
function in_any($val, $vals, $def = '') {
2551
	if (!is_array($vals) and $v = unserialize($vals)) {
2552
		$vals = $v;
2553
	}
2554
2555
	return (!is_array($vals) ? $def : (in_array($val, $vals) ? ' ' : ''));
2556
}
2557
2558
2559
/**
2560
 * Retourne le résultat d'une expression mathématique simple
2561
 *
2562
 * N'accepte que les *, + et - (à ameliorer si on l'utilise vraiment).
2563
 *
2564
 * @filtre
2565
 * @example
2566
 *      ```
2567
 *      valeur_numerique("3*2") retourne 6
2568
 *      ```
2569
 *
2570
 * @param string $expr
2571
 *     Expression mathématique `nombre operateur nombre` comme `3*2`
2572
 * @return int
2573
 *     Résultat du calcul
2574
 **/
2575
function valeur_numerique($expr) {
2576
	$a = 0;
2577
	if (preg_match(',^[0-9]+(\s*[+*-]\s*[0-9]+)*$,S', trim($expr))) {
2578
		eval("\$a = $expr;");
2579
	}
2580
2581
	return intval($a);
2582
}
2583
2584
/**
2585
 * Retourne un calcul de règle de trois
2586
 *
2587
 * @filtre
2588
 * @example
2589
 *     ```
2590
 *     [(#VAL{6}|regledetrois{4,3})] retourne 8
2591
 *     ```
2592
 *
2593
 * @param int $a
2594
 * @param int $b
2595
 * @param int $c
2596
 * @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...
2597
 *      Retourne `$a*$b/$c`
2598
 **/
2599
function regledetrois($a, $b, $c) {
2600
	return round($a * $b / $c);
2601
}
2602
2603
2604
/**
2605
 * Crée des tags HTML input hidden pour chaque paramètre et valeur d'une URL
2606
 *
2607
 * Fournit la suite de Input-Hidden correspondant aux paramètres de
2608
 * l'URL donnée en argument, compatible avec les types_urls
2609
 *
2610
 * @filtre
2611
 * @link https://www.spip.net/4286
2612
 * @see balise_ACTION_FORMULAIRE()
2613
 *     Également pour transmettre les actions à un formulaire
2614
 * @example
2615
 *     ```
2616
 *     [(#ENV{action}|form_hidden)] dans un formulaire
2617
 *     ```
2618
 *
2619
 * @param string $action URL
2620
 * @return string Suite de champs input hidden
2621
 **/
2622
function form_hidden($action) {
2623
2624
	$contexte = array();
2625
	include_spip('inc/urls');
2626
	if ($p = urls_decoder_url($action, '')
2627
		and reset($p)
2628
	) {
2629
		$fond = array_shift($p);
2630
		if ($fond != '404') {
2631
			$contexte = array_shift($p);
2632
			$contexte['page'] = $fond;
2633
			$action = preg_replace('/([?]' . preg_quote($fond) . '[^&=]*[0-9]+)(&|$)/', '?&', $action);
2634
		}
2635
	}
2636
	// defaire ce qu'a injecte urls_decoder_url : a revoir en modifiant la signature de urls_decoder_url
2637
	if (defined('_DEFINIR_CONTEXTE_TYPE') and _DEFINIR_CONTEXTE_TYPE) {
2638
		unset($contexte['type']);
2639
	}
2640
	if (defined('_DEFINIR_CONTEXTE_TYPE_PAGE') and _DEFINIR_CONTEXTE_TYPE_PAGE) {
2641
		unset($contexte['type-page']);
2642
	}
2643
2644
	// on va remplir un tableau de valeurs en prenant bien soin de ne pas
2645
	// ecraser les elements de la forme mots[]=1&mots[]=2
2646
	$values = array();
2647
2648
	// d'abord avec celles de l'url
2649
	if (false !== ($p = strpos($action, '?'))) {
2650
		foreach (preg_split('/&(amp;)?/S', substr($action, $p + 1)) as $c) {
2651
			$c = explode('=', $c, 2);
2652
			$var = array_shift($c);
2653
			$val = array_shift($c);
2654
			if ($var) {
2655
				$val = rawurldecode($val);
2656
				$var = rawurldecode($var); // decoder les [] eventuels
2657 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...
2658
					$values[] = array($var, $val);
2659
				} else {
2660
					if (!isset($values[$var])) {
2661
						$values[$var] = array($var, $val);
2662
					}
2663
				}
2664
			}
2665
		}
2666
	}
2667
2668
	// ensuite avec celles du contexte, sans doublonner !
2669
	foreach ($contexte as $var => $val) {
2670 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...
2671
			$values[] = array($var, $val);
2672
		} else {
2673
			if (!isset($values[$var])) {
2674
				$values[$var] = array($var, $val);
2675
			}
2676
		}
2677
	}
2678
2679
	// puis on rassemble le tout
2680
	$hidden = array();
2681
	foreach ($values as $value) {
2682
		list($var, $val) = $value;
2683
		$hidden[] = '<input name="'
2684
			. entites_html($var)
2685
			. '"'
2686
			. (is_null($val)
2687
				? ''
2688
				: ' value="' . entites_html($val) . '"'
2689
			)
2690
			. ' type="hidden"' . "\n/>";
2691
	}
2692
2693
	return join("", $hidden);
2694
}
2695
2696
/**
2697
 * Calcule les bornes d'une pagination
2698
 *
2699
 * @filtre
2700
 *
2701
 * @param int $courante
2702
 *     Page courante
2703
 * @param int $nombre
2704
 *     Nombre de pages
2705
 * @param int $max
2706
 *     Nombre d'éléments par page
2707
 * @return int[]
0 ignored issues
show
Documentation introduced by
Should the return type not be array<integer|double>?

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

Loading history...
2708
 *     Liste (première page, dernière page).
2709
 **/
2710
function filtre_bornes_pagination_dist($courante, $nombre, $max = 10) {
2711
	if ($max <= 0 or $max >= $nombre) {
2712
		return array(1, $nombre);
2713
	}
2714
2715
	$premiere = max(1, $courante - floor(($max - 1) / 2));
2716
	$derniere = min($nombre, $premiere + $max - 2);
2717
	$premiere = $derniere == $nombre ? $derniere - $max + 1 : $premiere;
2718
2719
	return array($premiere, $derniere);
2720
}
2721
2722
2723
/**
2724
 * Retourne la première valeur d'un tableau
2725
 *
2726
 * Plus précisément déplace le pointeur du tableau sur la première valeur et la retourne.
2727
 *
2728
 * @example `[(#LISTE{un,deux,trois}|reset)]` retourne 'un'
2729
 *
2730
 * @filtre
2731
 * @link http://php.net/manual/fr/function.reset.php
2732
 * @see filtre_end()
2733
 *
2734
 * @param array $array
2735
 * @return mixed|null|false
2736
 *    - null si $array n'est pas un tableau,
2737
 *    - false si le tableau est vide
2738
 *    - la première valeur du tableau sinon.
2739
 **/
2740
function filtre_reset($array) {
2741
	return !is_array($array) ? null : reset($array);
2742
}
2743
2744
/**
2745
 * Retourne la dernière valeur d'un tableau
2746
 *
2747
 * Plus précisément déplace le pointeur du tableau sur la dernière valeur et la retourne.
2748
 *
2749
 * @example `[(#LISTE{un,deux,trois}|end)]` retourne 'trois'
2750
 *
2751
 * @filtre
2752
 * @link http://php.net/manual/fr/function.end.php
2753
 * @see filtre_reset()
2754
 *
2755
 * @param array $array
2756
 * @return mixed|null|false
2757
 *    - null si $array n'est pas un tableau,
2758
 *    - false si le tableau est vide
2759
 *    - la dernière valeur du tableau sinon.
2760
 **/
2761
function filtre_end($array) {
2762
	return !is_array($array) ? null : end($array);
2763
}
2764
2765
/**
2766
 * Empile une valeur à la fin d'un tableau
2767
 *
2768
 * @example `[(#LISTE{un,deux,trois}|push{quatre}|print)]`
2769
 *
2770
 * @filtre
2771
 * @link https://www.spip.net/4571
2772
 * @link http://php.net/manual/fr/function.array-push.php
2773
 *
2774
 * @param array $array
2775
 * @param mixed $val
2776
 * @return array|string
2777
 *     - '' si $array n'est pas un tableau ou si echec.
2778
 *     - le tableau complété de la valeur sinon.
2779
 *
2780
 **/
2781
function filtre_push($array, $val) {
2782
	if (!is_array($array) or !array_push($array, $val)) {
2783
		return '';
2784
	}
2785
2786
	return $array;
2787
}
2788
2789
/**
2790
 * Indique si une valeur est contenue dans un tableau
2791
 *
2792
 * @example `[(#LISTE{un,deux,trois}|find{quatre}|oui) ... ]`
2793
 *
2794
 * @filtre
2795
 * @link https://www.spip.net/4575
2796
 * @see in_any() Assez proche, avec les paramètres tableau et valeur inversés.
2797
 *
2798
 * @param array $array
2799
 * @param mixed $val
2800
 * @return bool
2801
 *     - `false` si `$array` n'est pas un tableau
2802
 *     - `true` si la valeur existe dans le tableau, `false` sinon.
2803
 **/
2804
function filtre_find($array, $val) {
2805
	return (is_array($array) and in_array($val, $array));
2806
}
2807
2808
2809
/**
2810
 * Passer les url relatives à la css d'origine en url absolues
2811
 *
2812
 * @uses suivre_lien()
2813
 *
2814
 * @param string $contenu
2815
 *     Contenu du fichier CSS
2816
 * @param string $source
2817
 *     Chemin du fichier CSS
2818
 * @return string
2819
 *     Contenu avec urls en absolus
2820
 **/
2821
function urls_absolues_css($contenu, $source) {
2822
	$path = suivre_lien(url_absolue($source), './');
2823
2824
	return preg_replace_callback(
2825
		",url\s*\(\s*['\"]?([^'\"/#\s][^:]*)['\"]?\s*\),Uims",
2826
		function($x) use ($path) {
2827
			return "url('" . suivre_lien($path, $x[1]) . "')";
2828
		},
2829
		$contenu
2830
	);
2831
}
2832
2833
2834
/**
2835
 * Inverse le code CSS (left <--> right) d'une feuille de style CSS
2836
 *
2837
 * Récupère le chemin d'une CSS existante et :
2838
 *
2839
 * 1. regarde si une CSS inversée droite-gauche existe dans le meme répertoire
2840
 * 2. sinon la crée (ou la recrée) dans `_DIR_VAR/cache_css/`
2841
 *
2842
 * Si on lui donne à manger une feuille nommée `*_rtl.css` il va faire l'inverse.
2843
 *
2844
 * @filtre
2845
 * @example
2846
 *     ```
2847
 *     [<link rel="stylesheet" href="(#CHEMIN{css/perso.css}|direction_css)" type="text/css" />]
2848
 *     ```
2849
 * @param string $css
2850
 *     Chemin vers le fichier CSS
2851
 * @param string $voulue
2852
 *     Permet de forcer le sens voulu (en indiquant `ltr`, `rtl` ou un
2853
 *     code de langue). En absence, prend le sens de la langue en cours.
2854
 *
2855
 * @return string
2856
 *     Chemin du fichier CSS inversé
2857
 **/
2858
function direction_css($css, $voulue = '') {
2859
	if (!preg_match(',(_rtl)?\.css$,i', $css, $r)) {
2860
		return $css;
2861
	}
2862
2863
	// si on a precise le sens voulu en argument, le prendre en compte
2864
	if ($voulue = strtolower($voulue)) {
2865
		if ($voulue != 'rtl' and $voulue != 'ltr') {
2866
			$voulue = lang_dir($voulue);
2867
		}
2868
	} else {
2869
		$voulue = lang_dir();
2870
	}
2871
2872
	$r = count($r) > 1;
2873
	$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...
2874
	$dir = $r ? 'rtl' : 'ltr';
2875
	$ndir = $r ? 'ltr' : 'rtl';
2876
2877
	if ($voulue == $dir) {
2878
		return $css;
2879
	}
2880
2881
	if (
2882
		// url absolue
2883
		preg_match(",^https?:,i", $css)
2884
		// ou qui contient un ?
2885
		or (($p = strpos($css, '?')) !== false)
2886
	) {
2887
		$distant = true;
2888
		$cssf = parse_url($css);
2889
		$cssf = $cssf['path'] . ($cssf['query'] ? "?" . $cssf['query'] : "");
2890
		$cssf = preg_replace(',[?:&=],', "_", $cssf);
2891
	} else {
2892
		$distant = false;
2893
		$cssf = $css;
2894
		// 1. regarder d'abord si un fichier avec la bonne direction n'est pas aussi
2895
		//propose (rien a faire dans ce cas)
2896
		$f = preg_replace(',(_rtl)?\.css$,i', '_' . $ndir . '.css', $css);
2897
		if (@file_exists($f)) {
2898
			return $f;
2899
		}
2900
	}
2901
2902
	// 2.
2903
	$dir_var = sous_repertoire(_DIR_VAR, 'cache-css');
2904
	$f = $dir_var
2905
		. preg_replace(',.*/(.*?)(_rtl)?\.css,', '\1', $cssf)
2906
		. '.' . substr(md5($cssf), 0, 4) . '_' . $ndir . '.css';
2907
2908
	// la css peut etre distante (url absolue !)
2909
	if ($distant) {
2910
		include_spip('inc/distant');
2911
		$res = recuperer_url($css);
2912
		if (!$res or !$contenu = $res['page']) {
2913
			return $css;
2914
		}
2915
	} else {
2916 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...
2917
			and (_VAR_MODE != 'recalcul')
2918
		) {
2919
			return $f;
2920
		}
2921
		if (!lire_fichier($css, $contenu)) {
2922
			return $css;
2923
		}
2924
	}
2925
2926
2927
	// Inverser la direction gauche-droite en utilisant CSSTidy qui gere aussi les shorthands
2928
	include_spip("lib/csstidy/class.csstidy");
2929
	$parser = new csstidy();
2930
	$parser->set_cfg('optimise_shorthands', 0);
2931
	$parser->set_cfg('reverse_left_and_right', true);
2932
	$parser->parse($contenu);
2933
2934
	$contenu = $parser->print->plain();
2935
2936
2937
	// reperer les @import auxquels il faut propager le direction_css
2938
	preg_match_all(",\@import\s*url\s*\(\s*['\"]?([^'\"/][^:]*)['\"]?\s*\),Uims", $contenu, $regs);
2939
	$src = array();
2940
	$src_direction_css = array();
2941
	$src_faux_abs = array();
2942
	$d = dirname($css);
2943
	foreach ($regs[1] as $k => $import_css) {
2944
		$css_direction = direction_css("$d/$import_css", $voulue);
2945
		// si la css_direction est dans le meme path que la css d'origine, on tronque le path, elle sera passee en absolue
2946
		if (substr($css_direction, 0, strlen($d) + 1) == "$d/") {
2947
			$css_direction = substr($css_direction, strlen($d) + 1);
2948
		} // si la css_direction commence par $dir_var on la fait passer pour une absolue
2949
		elseif (substr($css_direction, 0, strlen($dir_var)) == $dir_var) {
2950
			$css_direction = substr($css_direction, strlen($dir_var));
2951
			$src_faux_abs["/@@@@@@/" . $css_direction] = $css_direction;
2952
			$css_direction = "/@@@@@@/" . $css_direction;
2953
		}
2954
		$src[] = $regs[0][$k];
2955
		$src_direction_css[] = str_replace($import_css, $css_direction, $regs[0][$k]);
2956
	}
2957
	$contenu = str_replace($src, $src_direction_css, $contenu);
2958
2959
	$contenu = urls_absolues_css($contenu, $css);
2960
2961
	// virer les fausses url absolues que l'on a mis dans les import
2962
	if (count($src_faux_abs)) {
2963
		$contenu = str_replace(array_keys($src_faux_abs), $src_faux_abs, $contenu);
2964
	}
2965
2966
	if (!ecrire_fichier($f, $contenu)) {
2967
		return $css;
2968
	}
2969
2970
	return $f;
2971
}
2972
2973
2974
/**
2975
 * Transforme les urls relatives d'un fichier CSS en absolues
2976
 *
2977
 * Récupère le chemin d'une css existante et crée (ou recrée) dans `_DIR_VAR/cache_css/`
2978
 * une css dont les url relatives sont passées en url absolues
2979
 *
2980
 * Le calcul n'est pas refait si le fichier cache existe déjà et que
2981
 * la source n'a pas été modifiée depuis.
2982
 *
2983
 * @uses recuperer_page() si l'URL source n'est pas sur le même site
2984
 * @uses urls_absolues_css()
2985
 *
2986
 * @param string $css
2987
 *     Chemin ou URL du fichier CSS source
2988
 * @return string
2989
 *     - Chemin du fichier CSS transformé (si source lisible et mise en cache réussie)
2990
 *     - Chemin ou URL du fichier CSS source sinon.
2991
 **/
2992
function url_absolue_css($css) {
2993
	if (!preg_match(',\.css$,i', $css, $r)) {
2994
		return $css;
2995
	}
2996
2997
	$url_absolue_css = url_absolue($css);
2998
2999
	$f = basename($css, '.css');
3000
	$f = sous_repertoire(_DIR_VAR, 'cache-css')
3001
		. preg_replace(",(.*?)(_rtl|_ltr)?$,", "\\1-urlabs-" . substr(md5("$css-urlabs"), 0, 4) . "\\2", $f)
3002
		. '.css';
3003
3004 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...
3005
		return $f;
3006
	}
3007
3008
	if ($url_absolue_css == $css) {
3009
		if (strncmp($GLOBALS['meta']['adresse_site'], $css, $l = strlen($GLOBALS['meta']['adresse_site'])) != 0
3010
			or !lire_fichier(_DIR_RACINE . substr($css, $l), $contenu)
3011
		) {
3012
			include_spip('inc/distant');
3013
			if (!$contenu = recuperer_page($css)) {
0 ignored issues
show
Deprecated Code introduced by
The function recuperer_page() has been deprecated.

This function has been deprecated.

Loading history...
3014
				return $css;
3015
			}
3016
		}
3017
	} elseif (!lire_fichier($css, $contenu)) {
3018
		return $css;
3019
	}
3020
3021
	// passer les url relatives a la css d'origine en url absolues
3022
	$contenu = urls_absolues_css($contenu, $css);
0 ignored issues
show
Bug introduced by
It seems like $contenu can also be of type boolean; however, urls_absolues_css() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
3023
3024
	// ecrire la css
3025
	if (!ecrire_fichier($f, $contenu)) {
3026
		return $css;
3027
	}
3028
3029
	return $f;
3030
}
3031
3032
3033
/**
3034
 * Récupère la valeur d'une clé donnée
3035
 * dans un tableau (ou un objet).
3036
 *
3037
 * @filtre
3038
 * @link https://www.spip.net/4572
3039
 * @example
3040
 *     ```
3041
 *     [(#VALEUR|table_valeur{cle/sous/element})]
3042
 *     ```
3043
 *
3044
 * @param mixed $table
3045
 *     Tableau ou objet PHP
3046
 *     (ou chaîne serialisée de tableau, ce qui permet d'enchaîner le filtre)
3047
 * @param string $cle
3048
 *     Clé du tableau (ou paramètre public de l'objet)
3049
 *     Cette clé peut contenir des caractères / pour sélectionner
3050
 *     des sous éléments dans le tableau, tel que `sous/element/ici`
3051
 *     pour obtenir la valeur de `$tableau['sous']['element']['ici']`
3052
 * @param mixed $defaut
3053
 *     Valeur par defaut retournée si la clé demandée n'existe pas
3054
 * @param bool  $conserver_null
3055
 *     Permet de forcer la fonction à renvoyer la valeur null d'un index
3056
 *     et non pas $defaut comme cela est fait naturellement par la fonction
3057
 *     isset. On utilise alors array_key_exists() à la place de isset().
3058
 * 
3059
 * @return mixed
3060
 *     Valeur trouvée ou valeur par défaut.
3061
 **/
3062
function table_valeur($table, $cle, $defaut = '', $conserver_null = false) {
3063
	foreach (explode('/', $cle) as $k) {
3064
3065
		$table = is_string($table) ? @unserialize($table) : $table;
3066
3067
		if (is_object($table)) {
3068
			$table = (($k !== "") and isset($table->$k)) ? $table->$k : $defaut;
3069
		} elseif (is_array($table)) {
3070
			if ($conserver_null) {
3071
				$table = array_key_exists($k, $table) ? $table[$k] : $defaut;
3072
			} else {
3073
				$table = isset($table[$k]) ? $table[$k] : $defaut;
3074
			}
3075
		} else {
3076
			$table = $defaut;
3077
		}
3078
	}
3079
3080
	return $table;
3081
}
3082
3083
/**
3084
 * Retrouve un motif dans un texte à partir d'une expression régulière
3085
 *
3086
 * S'appuie sur la fonction `preg_match()` en PHP
3087
 *
3088
 * @example
3089
 *    - `[(#TITRE|match{toto})]`
3090
 *    - `[(#TEXTE|match{^ceci$,Uims})]`
3091
 *    - `[(#TEXTE|match{truc(...)$, UimsS, 1})]` Capture de la parenthèse indiquée
3092
 *    - `[(#TEXTE|match{truc(...)$, 1})]` Équivalent, sans indiquer les modificateurs
3093
 *
3094
 * @filtre
3095
 * @link https://www.spip.net/4299
3096
 * @link http://php.net/manual/fr/function.preg-match.php Pour des infos sur `preg_match()`
3097
 *
3098
 * @param string $texte
3099
 *     Texte dans lequel chercher
3100
 * @param string|int $expression
3101
 *     Expression régulière de recherche, sans le délimiteur
3102
 * @param string $modif
3103
 *     - string : Modificateurs de l'expression régulière
3104
 *     - int : Numéro de parenthèse capturante
3105
 * @param int $capte
3106
 *     Numéro de parenthèse capturante
3107
 * @return bool|string
3108
 *     - false : l'expression n'a pas été trouvée
3109
 *     - true : expression trouvée, mais pas la parenthèse capturante
3110
 *     - string : expression trouvée.
3111
 **/
3112
function filtre_match_dist($texte, $expression, $modif = "UimsS", $capte = 0) {
3113
	if (intval($modif) and $capte == 0) {
3114
		$capte = $modif;
3115
		$modif = "UimsS";
3116
	}
3117
	$expression = str_replace("\/", "/", $expression);
3118
	$expression = str_replace("/", "\/", $expression);
3119
3120
	if (preg_match('/' . $expression . '/' . $modif, $texte, $r)) {
3121
		if (isset($r[$capte])) {
3122
			return $r[$capte];
3123
		} else {
3124
			return true;
3125
		}
3126
	}
3127
3128
	return false;
3129
}
3130
3131
3132
/**
3133
 * Remplacement de texte à base d'expression régulière
3134
 *
3135
 * @filtre
3136
 * @link https://www.spip.net/4309
3137
 * @see match()
3138
 * @example
3139
 *     ```
3140
 *     [(#TEXTE|replace{^ceci$,cela,UimsS})]
3141
 *     ```
3142
 *
3143
 * @param string $texte
3144
 *     Texte
3145
 * @param string $expression
3146
 *     Expression régulière
3147
 * @param string $replace
3148
 *     Texte de substitution des éléments trouvés
3149
 * @param string $modif
3150
 *     Modificateurs pour l'expression régulière.
3151
 * @return string
3152
 *     Texte
3153
 **/
3154
function replace($texte, $expression, $replace = '', $modif = "UimsS") {
3155
	$expression = str_replace("\/", "/", $expression);
3156
	$expression = str_replace("/", "\/", $expression);
3157
3158
	return preg_replace('/' . $expression . '/' . $modif, $replace, $texte);
3159
}
3160
3161
3162
/**
3163
 * Cherche les documents numerotés dans un texte traite par `propre()`
3164
 *
3165
 * Affecte la liste des doublons['documents']
3166
 *
3167
 * @param array $doublons
3168
 *     Liste des doublons
3169
 * @param string $letexte
3170
 *     Le texte
3171
 * @return string
3172
 *     Le texte
3173
 **/
3174
function traiter_doublons_documents(&$doublons, $letexte) {
3175
3176
	// Verifier dans le texte & les notes (pas beau, helas)
3177
	$t = $letexte . $GLOBALS['les_notes'];
3178
3179
	if (strstr($t, 'spip_document_') // evite le preg_match_all si inutile
3180
		and preg_match_all(
3181
			',<[^>]+\sclass=["\']spip_document_([0-9]+)[\s"\'],imsS',
3182
			$t, $matches, PREG_PATTERN_ORDER)
3183
	) {
3184
		if (!isset($doublons['documents'])) {
3185
			$doublons['documents'] = "";
3186
		}
3187
		$doublons['documents'] .= "," . join(',', $matches[1]);
3188
	}
3189
3190
	return $letexte;
3191
}
3192
3193
/**
3194
 * Filtre vide qui ne renvoie rien
3195
 *
3196
 * @example
3197
 *     `[(#CALCUL|vide)]` n'affichera pas le résultat du calcul
3198
 * @filtre
3199
 *
3200
 * @param mixed $texte
3201
 * @return string Chaîne vide
3202
 **/
3203
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...
3204
	return "";
3205
}
3206
3207
//
3208
// Filtres pour le modele/emb (embed document)
3209
//
3210
3211
/**
3212
 * Écrit des balises HTML `<param...>` à partir d'un tableau de données tel que `#ENV`
3213
 *
3214
 * Permet d'écrire les balises `<param>` à indiquer dans un `<object>`
3215
 * en prenant toutes les valeurs du tableau transmis.
3216
 *
3217
 * Certaines clés spécifiques à SPIP et aux modèles embed sont omises :
3218
 * id, lang, id_document, date, date_redac, align, fond, recurs, emb, dir_racine
3219
 *
3220
 * @example `[(#ENV*|env_to_params)]`
3221
 *
3222
 * @filtre
3223
 * @link https://www.spip.net/4005
3224
 *
3225
 * @param array|string $env
3226
 *      Tableau cle => valeur des paramètres à écrire, ou chaine sérialisée de ce tableau
3227
 * @param array $ignore_params
3228
 *      Permet de compléter les clés ignorées du tableau.
3229
 * @return string
3230
 *      Code HTML résultant
3231
 **/
3232 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...
3233
	$ignore_params = array_merge(
3234
		array('id', 'lang', 'id_document', 'date', 'date_redac', 'align', 'fond', '', 'recurs', 'emb', 'dir_racine'),
3235
		$ignore_params
3236
	);
3237
	if (!is_array($env)) {
3238
		$env = unserialize($env);
3239
	}
3240
	$texte = "";
3241
	if ($env) {
3242
		foreach ($env as $i => $j) {
3243
			if (is_string($j) and !in_array($i, $ignore_params)) {
3244
				$texte .= "<param name='" . attribut_html($i) . "'\n\tvalue='" . attribut_html($j) . "' />";
3245
			}
3246
		}
3247
	}
3248
3249
	return $texte;
3250
}
3251
3252
/**
3253
 * Écrit des attributs HTML à partir d'un tableau de données tel que `#ENV`
3254
 *
3255
 * Permet d'écrire des attributs d'une balise HTML en utilisant les données du tableau transmis.
3256
 * Chaque clé deviendra le nom de l'attribut (et la valeur, sa valeur)
3257
 *
3258
 * Certaines clés spécifiques à SPIP et aux modèles embed sont omises :
3259
 * id, lang, id_document, date, date_redac, align, fond, recurs, emb, dir_racine
3260
 *
3261
 * @example `<embed src='#URL_DOCUMENT' [(#ENV*|env_to_attributs)] width='#GET{largeur}' height='#GET{hauteur}'></embed>`
3262
 * @filtre
3263
 *
3264
 * @param array|string $env
3265
 *      Tableau cle => valeur des attributs à écrire, ou chaine sérialisée de ce tableau
3266
 * @param array $ignore_params
3267
 *      Permet de compléter les clés ignorées du tableau.
3268
 * @return string
3269
 *      Code HTML résultant
3270
 **/
3271 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...
3272
	$ignore_params = array_merge(
3273
		array('id', 'lang', 'id_document', 'date', 'date_redac', 'align', 'fond', '', 'recurs', 'emb', 'dir_racine'),
3274
		$ignore_params
3275
	);
3276
	if (!is_array($env)) {
3277
		$env = unserialize($env);
3278
	}
3279
	$texte = "";
3280
	if ($env) {
3281
		foreach ($env as $i => $j) {
3282
			if (is_string($j) and !in_array($i, $ignore_params)) {
3283
				$texte .= attribut_html($i) . "='" . attribut_html($j) . "' ";
3284
			}
3285
		}
3286
	}
3287
3288
	return $texte;
3289
}
3290
3291
3292
/**
3293
 * Concatène des chaînes
3294
 *
3295
 * @filtre
3296
 * @link https://www.spip.net/4150
3297
 * @example
3298
 *     ```
3299
 *     #TEXTE|concat{texte1,texte2,...}
3300
 *     ```
3301
 *
3302
 * @return string Chaînes concaténés
3303
 **/
3304
function concat() {
3305
	$args = func_get_args();
3306
3307
	return join('', $args);
3308
}
3309
3310
3311
/**
3312
 * Retourne le contenu d'un ou plusieurs fichiers
3313
 *
3314
 * Les chemins sont cherchés dans le path de SPIP
3315
 *
3316
 * @see balise_INCLURE_dist() La balise `#INCLURE` peut appeler cette fonction
3317
 *
3318
 * @param array|string $files
3319
 *     - array : Liste de fichiers
3320
 *     - string : fichier ou fichiers séparés par `|`
3321
 * @param bool $script
3322
 *     - si true, considère que c'est un fichier js à chercher `javascript/`
3323
 * @return string
3324
 *     Contenu du ou des fichiers, concaténé
3325
 **/
3326
function charge_scripts($files, $script = true) {
3327
	$flux = "";
3328
	foreach (is_array($files) ? $files : explode("|", $files) as $file) {
3329
		if (!is_string($file)) {
3330
			continue;
3331
		}
3332
		if ($script) {
3333
			$file = preg_match(",^\w+$,", $file) ? "javascript/$file.js" : '';
3334
		}
3335
		if ($file) {
3336
			$path = find_in_path($file);
3337
			if ($path) {
3338
				$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 3336 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...
3339
			}
3340
		}
3341
	}
3342
3343
	return $flux;
3344
}
3345
3346
3347
/**
3348
 * Produit une balise img avec un champ alt d'office si vide
3349
 *
3350
 * Attention le htmlentities et la traduction doivent être appliqués avant.
3351
 *
3352
 * @param string $img
3353
 * @param string $alt
3354
 * @param string $atts
3355
 * @param string $title
3356
 * @param array $options
3357
 *   chemin_image : utiliser chemin_image sur $img fourni, ou non (oui par dafaut)
3358
 *   utiliser_suffixe_size : utiliser ou non le suffixe de taille dans le nom de fichier de l'image
3359
 *   sous forme -xx.png (pour les icones essentiellement) (oui par defaut)
3360
 *   variante_svg_si_possible: utiliser l'image -xx.svg au lieu de -32.png par exemple (si la variante svg est disponible)
3361
 * @return string
3362
 */
3363
function http_img_pack($img, $alt, $atts = '', $title = '', $options = array()) {
3364
3365
	$img_file = $img;
3366
	if ($p = strpos($img_file, '?')) {
3367
		$img_file = substr($img_file,0, $p);
3368
	}
3369
	if (!isset($options['chemin_image']) or $options['chemin_image'] == true) {
3370
		$img_file = chemin_image($img);
3371
	}
3372
	else {
3373
		if (!isset($options['variante_svg_si_possible']) or $options['variante_svg_si_possible'] == true){
3374
			// on peut fournir une icone generique -xx.svg qui fera le job dans toutes les tailles, et qui est prioritaire sur le png
3375
			// si il y a un .svg a la bonne taille (-16.svg) a cote, on l'utilise en remplacement du -16.png
3376
			if (preg_match(',-(\d+)[.](png|gif|svg)$,', $img_file, $m)
3377
			  and $variante_svg_generique = substr($img_file, 0, -strlen($m[0])) . "-xx.svg"
3378
			  and file_exists($variante_svg_generique)) {
3379
				if ($variante_svg_size = substr($variante_svg_generique,0,-6) . $m[1] . ".svg" and file_exists($variante_svg_size)) {
3380
					$img_file = $variante_svg_size;
3381
				}
3382
				else {
3383
					$img_file = $variante_svg_generique;
3384
				}
3385
			}
3386
		}
3387
	}
3388
	if (stripos($atts, 'width') === false) {
3389
		// utiliser directement l'info de taille presente dans le nom
3390
		if ((!isset($options['utiliser_suffixe_size'])
3391
				or $options['utiliser_suffixe_size'] == true
3392
			  or strpos($img_file, '-xx.svg') !== false)
3393
			and (preg_match(',-([0-9]+)[.](png|gif|svg)$,', $img, $regs)
3394
					 or preg_match(',\?([0-9]+)px$,', $img, $regs))
3395
		) {
3396
			$largeur = $hauteur = intval($regs[1]);
3397
		} else {
3398
			$taille = taille_image($img_file);
3399
			list($hauteur, $largeur) = $taille;
3400
			if (!$hauteur or !$largeur) {
3401
				return "";
3402
			}
3403
		}
3404
		$atts .= " width='" . $largeur . "' height='" . $hauteur . "'";
3405
	}
3406
3407
	if (file_exists($img_file)) {
3408
		$img_file = timestamp($img_file);
3409
	}
3410
	if ($alt === false) {
3411
		$alt = '';
3412
	}
3413
	elseif($alt or $alt==='') {
3414
		$alt = " alt='".attribut_html($alt)."'";
3415
	}
3416
	else {
3417
		$alt = " alt='".attribut_html($title)."'";
3418
	}
3419
	return "<img src='$img_file'$alt"
3420
	. ($title ? ' title="' . attribut_html($title) . '"' : '')
3421
	. " " . ltrim($atts)
3422
	. " />";
3423
}
3424
3425
/**
3426
 * Générer une directive `style='background:url()'` à partir d'un fichier image
3427
 *
3428
 * @param string $img
3429
 * @param string $att
3430
 * @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...
3431
 * @return string
3432
 */
3433
function http_style_background($img, $att = '', $size=null) {
3434
	if ($size and is_numeric($size)){
3435
		$size = trim($size) . "px";
3436
	}
3437
	return " style='background" .
3438
		($att ? "" : "-image") . ": url(\"" . chemin_image($img) . "\")" . ($att ? (' ' . $att) : '') . ";"
3439
		. ($size ? "background-size:{$size};" : '')
3440
		. "'";
3441
}
3442
3443
/**
3444
 * Générer une balise HTML `img` à partir d'un nom de fichier
3445
 *
3446
 * @uses http_img_pack()
3447
 *
3448
 * @param string $img
3449
 * @param string $alt
3450
 * @param string $class
3451
 * @param string $width
0 ignored issues
show
Documentation introduced by
Should the type for parameter $width not be string|null?

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

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

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

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