Completed
Push — master ( 29c643...be10ed )
by cam
04:15
created

filtres.php ➔ modifier_class()   C

Complexity

Conditions 15
Paths 12

Size

Total Lines 46

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 15
nc 12
nop 3
dl 0
loc 46
rs 5.9166
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
 *
3902
 * @filtre
3903
 * @link https://www.spip.net/4004
3904
 * @example
3905
 *   ```
3906
 *   [(#URL_RUBRIQUE|lien_ou_expose{#TITRE, #ENV{test}|=={en_cours}})]
3907
 *   ```
3908
 *
3909
 * @param string $url
3910
 *   URL du lien
3911
 * @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...
3912
 *   Texte du lien
3913
 * @param bool $on
3914
 *   État exposé (génère un strong) ou non (génère un lien)
3915
 * @param string $class
3916
 *   Classes CSS ajoutées au lien
3917
 * @param string $title
3918
 *   Title ajouté au lien
3919
 * @param string $rel
3920
 *   Attribut `rel` ajouté au lien
3921
 * @param string $evt
3922
 *   Complement à la balise `a` pour gérer un événement javascript,
3923
 *   de la forme ` onclick='...'`
3924
 * @return string
3925
 *   Code HTML
3926
 */
3927
function lien_ou_expose($url, $libelle = null, $on = false, $class = "", $title = "", $rel = "", $evt = '') {
3928
	if ($on) {
3929
		$bal = "strong";
3930
		$att = "class='on'";
3931
	} else {
3932
		$bal = 'a';
3933
		$att = "href='$url'"
3934
			. ($title ? " title='" . attribut_html($title) . "'" : '')
3935
			. ($class ? " class='" . attribut_html($class) . "'" : '')
3936
			. ($rel ? " rel='" . attribut_html($rel) . "'" : '')
3937
			. $evt;
3938
	}
3939
	if ($libelle === null) {
3940
		$libelle = $url;
3941
	}
3942
3943
	return "<$bal $att>$libelle</$bal>";
3944
}
3945
3946
3947
/**
3948
 * Afficher un message "un truc"/"N trucs"
3949
 * Les items sont à indiquer comme pour la fonction _T() sous la forme :
3950
 * "module:chaine"
3951
 *
3952
 * @param int $nb : le nombre
3953
 * @param string $chaine_un : l'item de langue si $nb vaut un
3954
 * @param string $chaine_plusieurs : l'item de lanque si $nb >= 2
3955
 * @param string $var : La variable à remplacer par $nb dans l'item de langue (facultatif, défaut "nb")
3956
 * @param array $vars : Les autres variables nécessaires aux chaines de langues (facultatif)
3957
 * @return string : la chaine de langue finale en utilisant la fonction _T()
3958
 */
3959
function singulier_ou_pluriel($nb, $chaine_un, $chaine_plusieurs, $var = 'nb', $vars = array()) {
3960
	static $local_singulier_ou_pluriel = array();
3961
3962
	// si nb=0 ou pas de $vars valide on retourne une chaine vide, a traiter par un |sinon
3963
	if (!is_numeric($nb) or $nb == 0) {
3964
		return "";
3965
	}
3966
	if (!is_array($vars)) {
3967
		return "";
3968
	}
3969
3970
	$langue = $GLOBALS['spip_lang'];
3971
	if (!isset($local_singulier_ou_pluriel[$langue])) {
3972
		$local_singulier_ou_pluriel[$langue] = false;
3973
		if ($f = charger_fonction("singulier_ou_pluriel_${langue}", 'inc', true)
3974
		  or $f = charger_fonction("singulier_ou_pluriel", 'inc', true)) {
3975
			$local_singulier_ou_pluriel[$langue] = $f;
3976
		}
3977
	}
3978
3979
	// si on a une surcharge on l'utilise
3980
	if ($local_singulier_ou_pluriel[$langue]) {
3981
		return ($local_singulier_ou_pluriel[$langue])($nb, $chaine_un, $chaine_plusieurs, $var, $vars);
3982
	}
3983
3984
	// sinon traitement par defaut
3985
	$vars[$var] = $nb;
3986
	if ($nb >= 2) {
3987
		return _T($chaine_plusieurs, $vars);
3988
	} else {
3989
		return _T($chaine_un, $vars);
3990
	}
3991
}
3992
3993
3994
/**
3995
 * Fonction de base pour une icone dans un squelette
3996
 * structure html : `<span><a><img><b>texte</b></span>`
3997
 *
3998
 * @param string $type
3999
 *  'lien' ou 'bouton'
4000
 * @param string $lien
4001
 *  url
4002
 * @param string $texte
4003
 *  texte du lien / alt de l'image
4004
 * @param string $fond
4005
 *  objet avec ou sans son extension et sa taille (article, article-24, article-24.png)
4006
 * @param string $fonction
4007
 *  new/del/edit
4008
 * @param string $class
4009
 *  classe supplementaire (horizontale, verticale, ajax ...)
4010
 * @param string $javascript
4011
 *  "onclick='...'" par exemple
4012
 * @return string
4013
 */
4014
function prepare_icone_base($type, $lien, $texte, $fond, $fonction = "", $class = "", $javascript = "") {
4015
	if (in_array($fonction, array("del", "supprimer.gif"))) {
4016
		$class .= ' danger';
4017
	} elseif ($fonction == "rien.gif") {
4018
		$fonction = "";
4019
	} elseif ($fonction == "delsafe") {
4020
		$fonction = "del";
4021
	}
4022
4023
	$fond_origine = $fond;
4024
	// remappage des icone : article-24.png+new => article-new-24.png
4025 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...
4026
		list($fond, $fonction) = $icone_renommer($fond, $fonction);
4027
	}
4028
4029
	// ajouter le type d'objet dans la class de l'icone
4030
	$class .= " " . substr(basename($fond), 0, -4);
4031
4032
	$alt = attribut_html($texte);
4033
	$title = " title=\"$alt\""; // est-ce pertinent de doubler le alt par un title ?
4034
4035
	$ajax = "";
4036
	if (strpos($class, "ajax") !== false) {
4037
		$ajax = "ajax";
4038
		if (strpos($class, "preload") !== false) {
4039
			$ajax .= " preload";
4040
		}
4041
		if (strpos($class, "nocache") !== false) {
4042
			$ajax .= " nocache";
4043
		}
4044
		$ajax = " class='$ajax'";
4045
	}
4046
4047
	$size = 24;
4048
	if (preg_match("/-([0-9]{1,3})[.](gif|png|svg)$/i", $fond, $match)
4049
	  or preg_match("/-([0-9]{1,3})([.](gif|png|svg))?$/i", $fond_origine, $match)) {
4050
		$size = $match[1];
4051
	}
4052
4053
	$icone = http_img_pack($fond, $alt, "width='$size' height='$size'");
4054
	$icone = "<span class=\"icone-image".($fonction ? " icone-fonction icone-fonction-$fonction" : "") . "\">$icone</span>";
4055
4056
	if ($type == 'lien') {
4057
		return "<span class='icone s$size $class'>"
4058
		. "<a href='$lien'$title$ajax$javascript>"
4059
		. $icone
4060
		. "<b>$texte</b>"
4061
		. "</a></span>\n";
4062
	} else {
4063
		return bouton_action("$icone<b>$texte</b>", $lien, "icone s$size $class", $javascript, $alt);
4064
	}
4065
}
4066
4067
/**
4068
 * Crée un lien ayant une icone
4069
 *
4070
 * @uses prepare_icone_base()
4071
 *
4072
 * @param string $lien
4073
 *     URL du lien
4074
 * @param string $texte
4075
 *     Texte du lien
4076
 * @param string $fond
4077
 *     Objet avec ou sans son extension et sa taille (article, article-24, article-24.png)
4078
 * @param string $fonction
4079
 *     Fonction du lien (`edit`, `new`, `del`)
4080
 * @param string $class
4081
 *     Classe CSS, tel que `left`, `right` pour définir un alignement
4082
 * @param string $javascript
4083
 *     Javascript ajouté sur le lien
4084
 * @return string
4085
 *     Code HTML du lien
4086
 **/
4087
function icone_base($lien, $texte, $fond, $fonction = "", $class = "", $javascript = "") {
4088
	return prepare_icone_base('lien', $lien, $texte, $fond, $fonction, $class, $javascript);
4089
}
4090
4091
/**
4092
 * Crée un lien précédé d'une icone au dessus du texte
4093
 *
4094
 * @uses icone_base()
4095
 * @see  icone_verticale() Pour un usage dans un code PHP.
4096
 *
4097
 * @filtre
4098
 * @example
4099
 *     ```
4100
 *     [(#AUTORISER{voir,groupemots,#ID_GROUPE})
4101
 *         [(#URL_ECRIRE{groupe_mots,id_groupe=#ID_GROUPE}
4102
 *            |icone_verticale{<:mots:icone_voir_groupe_mots:>,groupe_mots-24.png,'',left})]
4103
 *    ]
4104
 *     ```
4105
 *
4106
 * @param string $lien
4107
 *     URL du lien
4108
 * @param string $texte
4109
 *     Texte du lien
4110
 * @param string $fond
4111
 *     Objet avec ou sans son extension et sa taille (article, article-24, article-24.png)
4112
 * @param string $fonction
4113
 *     Fonction du lien (`edit`, `new`, `del`)
4114
 * @param string $class
4115
 *     Classe CSS à ajouter, tel que `left`, `right`, `center` pour définir un alignement.
4116
 *     Il peut y en avoir plusieurs : `left ajax`
4117
 * @param string $javascript
4118
 *     Javascript ajouté sur le lien
4119
 * @return string
4120
 *     Code HTML du lien
4121
 **/
4122
function filtre_icone_verticale_dist($lien, $texte, $fond, $fonction = "", $class = "", $javascript = "") {
4123
	return icone_base($lien, $texte, $fond, $fonction, "verticale $class", $javascript);
4124
}
4125
4126
/**
4127
 * Crée un lien précédé d'une icone horizontale
4128
 *
4129
 * @uses icone_base()
4130
 * @see  icone_horizontale() Pour un usage dans un code PHP.
4131
 *
4132
 * @filtre
4133
 * @example
4134
 *     En tant que filtre dans un squelettes :
4135
 *     ```
4136
 *     [(#URL_ECRIRE{sites}|icone_horizontale{<:sites:icone_voir_sites_references:>,site-24.png})]
4137
 *
4138
 *     [(#AUTORISER{supprimer,groupemots,#ID_GROUPE}|oui)
4139
 *         [(#URL_ACTION_AUTEUR{supprimer_groupe_mots,#ID_GROUPE,#URL_ECRIRE{mots}}
4140
 *             |icone_horizontale{<:mots:icone_supprimer_groupe_mots:>,groupe_mots,del})]
4141
 *     ]
4142
 *     ```
4143
 *
4144
 *     En tant que filtre dans un code php :
4145
 *     ```
4146
 *     $icone_horizontale=chercher_filtre('icone_horizontale');
4147
 *     $icone = $icone_horizontale(generer_url_ecrire("stats_visites","id_article=$id_article"),
4148
 *         _T('statistiques:icone_evolution_visites', array('visites' => $visites)),
4149
 *         "statistique-24.png");
4150
 *     ```
4151
 *
4152
 * @param string $lien
4153
 *     URL du lien
4154
 * @param string $texte
4155
 *     Texte du lien
4156
 * @param string $fond
4157
 *     Objet avec ou sans son extension et sa taille (article, article-24, article-24.png)
4158
 * @param string $fonction
4159
 *     Fonction du lien (`edit`, `new`, `del`)
4160
 * @param string $class
4161
 *     Classe CSS à ajouter
4162
 * @param string $javascript
4163
 *     Javascript ajouté sur le lien
4164
 * @return string
4165
 *     Code HTML du lien
4166
 **/
4167
function filtre_icone_horizontale_dist($lien, $texte, $fond, $fonction = "", $class = "", $javascript = "") {
4168
	return icone_base($lien, $texte, $fond, $fonction, "horizontale $class", $javascript);
4169
}
4170
4171
/**
4172
 * Crée un bouton d'action intégrant une icone horizontale
4173
 *
4174
 * @uses prepare_icone_base()
4175
 *
4176
 * @filtre
4177
 * @example
4178
 *     ```
4179
 *     [(#URL_ACTION_AUTEUR{supprimer_mot, #ID_MOT, #URL_ECRIRE{groupe_mots,id_groupe=#ID_GROUPE}}
4180
 *         |bouton_action_horizontal{<:mots:info_supprimer_mot:>,mot-24.png,del})]
4181
 *     ```
4182
 *
4183
 * @param string $lien
4184
 *     URL de l'action
4185
 * @param string $texte
4186
 *     Texte du bouton
4187
 * @param string $fond
4188
 *     Objet avec ou sans son extension et sa taille (article, article-24, article-24.png)
4189
 * @param string $fonction
4190
 *     Fonction du bouton (`edit`, `new`, `del`)
4191
 * @param string $class
4192
 *     Classe CSS à ajouter
4193
 * @param string $confirm
4194
 *     Message de confirmation à ajouter en javascript sur le bouton
4195
 * @return string
4196
 *     Code HTML du lien
4197
 **/
4198
function filtre_bouton_action_horizontal_dist($lien, $texte, $fond, $fonction = "", $class = "", $confirm = "") {
4199
	return prepare_icone_base('bouton', $lien, $texte, $fond, $fonction, "horizontale $class", $confirm);
4200
}
4201
4202
/**
4203
 * Filtre `icone` pour compatibilité mappé sur `icone_base`
4204
 *
4205
 * @uses icone_base()
4206
 * @see  filtre_icone_verticale_dist()
4207
 *
4208
 * @filtre
4209
 * @deprecated Utiliser le filtre `icone_verticale`
4210
 *
4211
 * @param string $lien
4212
 *     URL du lien
4213
 * @param string $texte
4214
 *     Texte du lien
4215
 * @param string $fond
4216
 *     Nom de l'image utilisée
4217
 * @param string $align
4218
 *     Classe CSS d'alignement (`left`, `right`, `center`)
4219
 * @param string $fonction
4220
 *     Fonction du lien (`edit`, `new`, `del`)
4221
 * @param string $class
4222
 *     Classe CSS à ajouter
4223
 * @param string $javascript
4224
 *     Javascript ajouté sur le lien
4225
 * @return string
4226
 *     Code HTML du lien
4227
 */
4228
function filtre_icone_dist($lien, $texte, $fond, $align = "", $fonction = "", $class = "", $javascript = "") {
4229
	return icone_base($lien, $texte, $fond, $fonction, "verticale $align $class", $javascript);
4230
}
4231
4232
4233
/**
4234
 * Explose un texte en tableau suivant un séparateur
4235
 *
4236
 * @note
4237
 *     Inverse l'écriture de la fonction PHP de même nom
4238
 *     pour que le filtre soit plus pratique dans les squelettes
4239
 *
4240
 * @filtre
4241
 * @example
4242
 *     ```
4243
 *     [(#GET{truc}|explode{-})]
4244
 *     ```
4245
 *
4246
 * @param string $a Texte
4247
 * @param string $b Séparateur
4248
 * @return array Liste des éléments
4249
 */
4250
function filtre_explode_dist($a, $b) { return explode($b, $a); }
4251
4252
/**
4253
 * Implose un tableau en chaine en liant avec un séparateur
4254
 *
4255
 * @note
4256
 *     Inverse l'écriture de la fonction PHP de même nom
4257
 *     pour que le filtre soit plus pratique dans les squelettes
4258
 *
4259
 * @filtre
4260
 * @example
4261
 *     ```
4262
 *     [(#GET{truc}|implode{-})]
4263
 *     ```
4264
 *
4265
 * @param array $a Tableau
4266
 * @param string $b Séparateur
4267
 * @return string Texte
4268
 */
4269
function filtre_implode_dist($a, $b) { return is_array($a) ? implode($b, $a) : $a; }
4270
4271
/**
4272
 * Produire les styles privés qui associent item de menu avec icone en background
4273
 *
4274
 * @return string Code CSS
4275
 */
4276
function bando_images_background() {
4277
	include_spip('inc/bandeau');
4278
	// recuperer tous les boutons et leurs images
4279
	$boutons = definir_barre_boutons(definir_barre_contexte(), true, false);
4280
4281
	$res = "";
4282
	foreach ($boutons as $page => $detail) {
4283
		if ($detail->icone and strlen(trim($detail->icone))) {
4284
			$res .= "\n.navigation_avec_icones #bando1_$page {background-image:url(" . $detail->icone . ");}";
4285
		}
4286
		$selecteur = (in_array($page, array('outils_rapides', 'outils_collaboratifs')) ? "" : ".navigation_avec_icones ");
4287
		if (is_array($detail->sousmenu)) {
4288
			foreach ($detail->sousmenu as $souspage => $sousdetail) {
4289
				if ($sousdetail->icone and strlen(trim($sousdetail->icone))) {
4290
					$res .= "\n$selecteur.bando2_$souspage {background-image:url(" . $sousdetail->icone . ");}";
4291
				}
4292
			}
4293
		}
4294
	}
4295
4296
	return $res;
4297
}
4298
4299
/**
4300
 * Generer un bouton_action
4301
 * utilise par #BOUTON_ACTION
4302
 *
4303
 * @param string $libelle
4304
 * @param string $url
4305
 * @param string $class
4306
 * @param string $confirm
4307
 *   message de confirmation oui/non avant l'action
4308
 * @param string $title
4309
 * @param string $callback
4310
 *   callback js a appeler lors de l'evenement action (apres confirmation eventuelle si $confirm est non vide)
4311
 *   et avant execution de l'action. Si la callback renvoie false, elle annule le declenchement de l'action
4312
 * @return string
4313
 */
4314
function bouton_action($libelle, $url, $class = "", $confirm = "", $title = "", $callback = "") {
4315
	if ($confirm) {
4316
		$confirm = "confirm(\"" . attribut_html($confirm) . "\")";
4317
		if ($callback) {
4318
			$callback = "$confirm?($callback):false";
4319
		} else {
4320
			$callback = $confirm;
4321
		}
4322
	}
4323
	$onclick = $callback ? " onclick='return " . addcslashes($callback, "'") . "'" : "";
4324
	$title = $title ? " title='$title'" : "";
4325
4326
	return "<form class='bouton_action_post $class' method='post' action='$url'><div>" . form_hidden($url)
4327
	. "<button type='submit' class='submit'$title$onclick>$libelle</button></div></form>";
4328
}
4329
4330
/**
4331
 * Donner n'importe quelle information sur un objet de maniere generique.
4332
 *
4333
 * La fonction va gerer en interne deux cas particuliers les plus utilises :
4334
 * l'URL et le titre (qui n'est pas forcemment le champ SQL "titre").
4335
 *
4336
 * On peut ensuite personnaliser les autres infos en creant une fonction
4337
 * generer_<nom_info>_entite($id_objet, $type_objet, $ligne).
4338
 * $ligne correspond a la ligne SQL de tous les champs de l'objet, les fonctions
4339
 * de personnalisation n'ont donc pas a refaire de requete.
4340
 *
4341
 * @param int $id_objet
4342
 * @param string $type_objet
4343
 * @param string $info
4344
 * @param string $etoile
4345
 * @return string
4346
 */
4347
function generer_info_entite($id_objet, $type_objet, $info, $etoile = "") {
4348
	static $trouver_table = null;
4349
	static $objets;
4350
4351
	// On verifie qu'on a tout ce qu'il faut
4352
	$id_objet = intval($id_objet);
4353
	if (!($id_objet and $type_objet and $info)) {
4354
		return '';
4355
	}
4356
4357
	// si on a deja note que l'objet n'existe pas, ne pas aller plus loin
4358
	if (isset($objets[$type_objet]) and $objets[$type_objet] === false) {
4359
		return '';
4360
	}
4361
4362
	// Si on demande l'url, on retourne direct la fonction
4363
	if ($info == 'url') {
4364
		return generer_url_entite($id_objet, $type_objet);
4365
	}
4366
4367
	// Sinon on va tout chercher dans la table et on garde en memoire
4368
	$demande_titre = ($info == 'titre');
4369
4370
	// 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
4371
	if (!isset($objets[$type_objet][$id_objet])
4372
		or
4373
		($demande_titre and !isset($objets[$type_objet][$id_objet]['titre']))
4374
	) {
4375
		if (!$trouver_table) {
4376
			$trouver_table = charger_fonction('trouver_table', 'base');
4377
		}
4378
		$desc = $trouver_table(table_objet_sql($type_objet));
4379
		if (!$desc) {
4380
			return $objets[$type_objet] = false;
4381
		}
4382
4383
		// Si on demande le titre, on le gere en interne
4384
		$champ_titre = "";
4385
		if ($demande_titre) {
4386
			// si pas de titre declare mais champ titre, il sera peuple par le select *
4387
			$champ_titre = (!empty($desc['titre'])) ? ', ' . $desc['titre'] : '';
4388
		}
4389
		include_spip('base/abstract_sql');
4390
		include_spip('base/connect_sql');
4391
		$objets[$type_objet][$id_objet] = sql_fetsel(
4392
			'*' . $champ_titre,
4393
			$desc['table_sql'],
4394
			id_table_objet($type_objet) . ' = ' . intval($id_objet)
4395
		);
4396
	}
4397
4398
	// Si la fonction generer_TRUC_TYPE existe, on l'utilise pour formater $info_generee
4399
	if ($generer = charger_fonction("generer_${info}_${type_objet}", '', true)) {
4400
		$info_generee = $generer($id_objet, $objets[$type_objet][$id_objet]);
4401
	} // Si la fonction generer_TRUC_entite existe, on l'utilise pour formater $info_generee
4402
	else {
4403
		if ($generer = charger_fonction("generer_${info}_entite", '', true)) {
4404
			$info_generee = $generer($id_objet, $type_objet, $objets[$type_objet][$id_objet]);
4405
		} // Sinon on prend directement le champ SQL tel quel
4406
		else {
4407
			$info_generee = (isset($objets[$type_objet][$id_objet][$info]) ? $objets[$type_objet][$id_objet][$info] : '');
4408
		}
4409
	}
4410
4411
	// On va ensuite appliquer les traitements automatiques si besoin
4412
	if (!$etoile) {
4413
		// FIXME: on fournit un ENV minimum avec id et type et connect=''
4414
		// mais ce fonctionnement est a ameliorer !
4415
		$info_generee = appliquer_traitement_champ($info_generee, $info, table_objet($type_objet),
4416
			array('id_objet' => $id_objet, 'objet' => $type_objet, ''));
4417
	}
4418
4419
	return $info_generee;
4420
}
4421
4422
/**
4423
 * Appliquer a un champ SQL le traitement qui est configure pour la balise homonyme dans les squelettes
4424
 *
4425
 * @param string $texte
4426
 * @param string $champ
4427
 * @param string $table_objet
4428
 * @param array $env
4429
 * @param string $connect
4430
 * @return string
4431
 */
4432
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...
4433
	if (!$champ) {
4434
		return $texte;
4435
	}
4436
	
4437
	// On charge toujours les filtres de texte car la majorité des traitements les utilisent
4438
	// et il ne faut pas partir du principe que c'est déjà chargé (form ajax, etc)
4439
	include_spip('inc/texte');
4440
	
4441
	$champ = strtoupper($champ);
4442
	$traitements = isset($GLOBALS['table_des_traitements'][$champ]) ? $GLOBALS['table_des_traitements'][$champ] : false;
4443
	if (!$traitements or !is_array($traitements)) {
4444
		return $texte;
4445
	}
4446
4447
	$traitement = '';
4448
	if ($table_objet and (!isset($traitements[0]) or count($traitements) > 1)) {
4449
		// necessaire pour prendre en charge les vieux appels avec un table_objet_sql en 3e arg
4450
		$table_objet = table_objet($table_objet);
4451
		if (isset($traitements[$table_objet])) {
4452
			$traitement = $traitements[$table_objet];
4453
		}
4454
	}
4455
	if (!$traitement and isset($traitements[0])) {
4456
		$traitement = $traitements[0];
4457
	}
4458
	// (sinon prendre le premier de la liste par defaut ?)
4459
4460
	if (!$traitement) {
4461
		return $texte;
4462
	}
4463
4464
	$traitement = str_replace('%s', "'" . texte_script($texte) . "'", $traitement);
4465
4466
	// Fournir $connect et $Pile[0] au traitement si besoin
4467
	$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...
4468
	eval("\$texte = $traitement;");
4469
4470
	return $texte;
4471
}
4472
4473
4474
/**
4475
 * Generer un lien (titre clicable vers url) vers un objet
4476
 *
4477
 * @param int $id_objet
4478
 * @param $objet
4479
 * @param int $longueur
4480
 * @param null|string $connect
4481
 * @return string
4482
 */
4483
function generer_lien_entite($id_objet, $objet, $longueur = 80, $connect = null) {
4484
	include_spip('inc/liens');
4485
	$titre = traiter_raccourci_titre($id_objet, $objet, $connect);
4486
	// lorsque l'objet n'est plus declare (plugin desactive par exemple)
4487
	// le raccourcis n'est plus valide
4488
	$titre = isset($titre['titre']) ? typo($titre['titre']) : '';
4489
	// on essaye avec generer_info_entite ?
4490
	if (!strlen($titre) and !$connect) {
4491
		$titre = generer_info_entite($id_objet, $objet, 'titre');
4492
	}
4493
	if (!strlen($titre)) {
4494
		$titre = _T('info_sans_titre');
4495
	}
4496
	$url = generer_url_entite($id_objet, $objet, '', '', $connect);
4497
4498
	return "<a href='$url' class='$objet'>" . couper($titre, $longueur) . "</a>";
4499
}
4500
4501
4502
/**
4503
 * Englobe (Wrap) un texte avec des balises
4504
 *
4505
 * @example `wrap('mot','<b>')` donne `<b>mot</b>'`
4506
 *
4507
 * @filtre
4508
 * @uses extraire_balises()
4509
 *
4510
 * @param string $texte
4511
 * @param string $wrap
4512
 * @return string
4513
 */
4514
function wrap($texte, $wrap) {
4515
	$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...
4516
	if (preg_match_all(",<([a-z]\w*)\b[^>]*>,UimsS", $wrap, $regs, PREG_PATTERN_ORDER)) {
4517
		$texte = $wrap . $texte;
4518
		$regs = array_reverse($regs[1]);
4519
		$wrap = "</" . implode("></", $regs) . ">";
4520
		$texte = $texte . $wrap;
4521
	}
4522
4523
	return $texte;
4524
}
4525
4526
4527
/**
4528
 * afficher proprement n'importe quoi
4529
 * On affiche in fine un pseudo-yaml qui premet de lire humainement les tableaux et de s'y reperer
4530
 *
4531
 * Les textes sont retournes avec simplement mise en forme typo
4532
 *
4533
 * 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
4534
 * les tableaux-listes (qui n'ont que des cles numeriques), sont affiches sous forme de liste separee par des virgules :
4535
 * c'est VOULU !
4536
 *
4537
 * @param $u
4538
 * @param string $join
4539
 * @param int $indent
4540
 * @return array|mixed|string
4541
 */
4542
function filtre_print_dist($u, $join = "<br />", $indent = 0) {
4543
	if (is_string($u)) {
4544
		$u = typo($u);
4545
4546
		return $u;
4547
	}
4548
4549
	// caster $u en array si besoin
4550
	if (is_object($u)) {
4551
		$u = (array)$u;
4552
	}
4553
4554
	if (is_array($u)) {
4555
		$out = "";
4556
		// toutes les cles sont numeriques ?
4557
		// et aucun enfant n'est un tableau
4558
		// liste simple separee par des virgules
4559
		$numeric_keys = array_map('is_numeric', array_keys($u));
4560
		$array_values = array_map('is_array', $u);
4561
		$object_values = array_map('is_object', $u);
4562
		if (array_sum($numeric_keys) == count($numeric_keys)
4563
			and !array_sum($array_values)
4564
			and !array_sum($object_values)
4565
		) {
4566
			return join(", ", array_map('filtre_print_dist', $u));
4567
		}
4568
4569
		// sinon on passe a la ligne et on indente
4570
		$i_str = str_pad("", $indent, " ");
4571
		foreach ($u as $k => $v) {
4572
			$out .= $join . $i_str . "$k: " . filtre_print_dist($v, $join, $indent + 2);
4573
		}
4574
4575
		return $out;
4576
	}
4577
4578
	// on sait pas quoi faire...
4579
	return $u;
4580
}
4581
4582
4583
/**
4584
 * Renvoyer l'info d'un objet
4585
 * telles que definies dans declarer_tables_objets_sql
4586
 *
4587
 * @param string $objet
4588
 * @param string $info
4589
 * @return string|array
4590
 */
4591
function objet_info($objet, $info) {
4592
	$table = table_objet_sql($objet);
4593
	$infos = lister_tables_objets_sql($table);
4594
4595
	return (isset($infos[$info]) ? $infos[$info] : '');
4596
}
4597
4598
/**
4599
 * Filtre pour afficher 'Aucun truc' ou '1 truc' ou 'N trucs'
4600
 * avec la bonne chaîne de langue en fonction de l'objet utilisé
4601
 *
4602
 * @param int $nb
4603
 *     Nombre d'éléments
4604
 * @param string $objet
4605
 *     Objet
4606
 * @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...
4607
 *     Texte traduit du comptage, tel que '3 articles'
4608
 */
4609
function objet_afficher_nb($nb, $objet) {
4610
	if (!$nb) {
4611
		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...
4612
	} else {
4613
		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...
4614
	}
4615
}
4616
4617
/**
4618
 * Filtre pour afficher l'img icone d'un objet
4619
 *
4620
 * @param string $objet
4621
 * @param int $taille
4622
 * @param string $class
4623
 * @return string
4624
 */
4625
function objet_icone($objet, $taille = 24, $class='') {
4626
	$icone = objet_info($objet, 'icone_objet') . "-" . $taille . ".png";
4627
	$icone = chemin_image($icone);
4628
	$balise_img = charger_filtre('balise_img');
4629
4630
	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...
4631
}
4632
4633
/**
4634
 * Renvoyer une traduction d'une chaine de langue contextuelle à un objet si elle existe,
4635
 * la traduction de la chaine generique
4636
 *
4637
 * Ex : [(#ENV{objet}|objet_label{trad_reference})]
4638
 * va chercher si une chaine objet:trad_reference existe et renvoyer sa trad le cas echeant
4639
 * sinon renvoie la trad de la chaine trad_reference
4640
 * Si la chaine fournie contient un prefixe il est remplacé par celui de l'objet pour chercher la chaine contextuelle
4641
 *
4642
 * Les arguments $args et $options sont ceux de la fonction _T
4643
 *
4644
 * @param string $objet
4645
 * @param string $chaine
4646
 * @param array $args
4647
 * @param array $options
4648
 * @return string
4649
 */
4650
function objet_T($objet, $chaine, $args = array(), $options = array()){
4651
	$chaine = explode(':',$chaine);
4652
	if ($t = _T($objet . ':' . end($chaine), $args, array_merge($options, array('force'=>false)))) {
4653
		return $t;
4654
	}
4655
	$chaine = implode(':',$chaine);
4656
	return _T($chaine, $args, $options);
4657
}
4658
4659
/**
4660
 * Fonction de secours pour inserer le head_css de facon conditionnelle
4661
 *
4662
 * Appelée en filtre sur le squelette qui contient #INSERT_HEAD,
4663
 * elle vérifie l'absence éventuelle de #INSERT_HEAD_CSS et y suplée si besoin
4664
 * pour assurer la compat avec les squelettes qui n'utilisent pas.
4665
 *
4666
 * @param string $flux Code HTML
4667
 * @return string      Code HTML
4668
 */
4669
function insert_head_css_conditionnel($flux) {
4670
	if (strpos($flux, '<!-- insert_head_css -->') === false
4671
		and $p = strpos($flux, '<!-- insert_head -->')
4672
	) {
4673
		// plutot avant le premier js externe (jquery) pour etre non bloquant
4674
		if ($p1 = stripos($flux, '<script src=') and $p1 < $p) {
4675
			$p = $p1;
4676
		}
4677
		$flux = substr_replace($flux, pipeline('insert_head_css', '<!-- insert_head_css -->'), $p, 0);
4678
	}
4679
4680
	return $flux;
4681
}
4682
4683
/**
4684
 * Produire un fichier statique à partir d'un squelette dynamique
4685
 *
4686
 * Permet ensuite à Apache de le servir en statique sans repasser
4687
 * par spip.php à chaque hit sur le fichier.
4688
 *
4689
 * Si le format (css ou js) est passe dans `contexte['format']`, on l'utilise
4690
 * sinon on regarde si le fond finit par .css ou .js, sinon on utilie "html"
4691
 *
4692
 * @uses urls_absolues_css()
4693
 *
4694
 * @param string $fond
4695
 * @param array $contexte
4696
 * @param array $options
4697
 * @param string $connect
4698
 * @return string
4699
 */
4700
function produire_fond_statique($fond, $contexte = array(), $options = array(), $connect = '') {
4701
	if (isset($contexte['format'])) {
4702
		$extension = $contexte['format'];
4703
		unset($contexte['format']);
4704
	} else {
4705
		$extension = "html";
4706
		if (preg_match(',[.](css|js|json)$,', $fond, $m)) {
4707
			$extension = $m[1];
4708
		}
4709
	}
4710
	// recuperer le contenu produit par le squelette
4711
	$options['raw'] = true;
4712
	$cache = recuperer_fond($fond, $contexte, $options, $connect);
4713
4714
	// calculer le nom de la css
4715
	$dir_var = sous_repertoire(_DIR_VAR, 'cache-' . $extension);
4716
	$nom_safe = preg_replace(",\W,", '_', str_replace('.', '_', $fond));
4717
	$contexte_implicite = calculer_contexte_implicite();
4718
4719
	// par defaut on hash selon les contextes qui sont a priori moins variables
4720
	// mais on peut hasher selon le contenu a la demande, si plusieurs contextes produisent un meme contenu
4721
	// reduit la variabilite du nom et donc le nombre de css concatenees possibles in fine
4722
	if (isset($options['hash_on_content']) and $options['hash_on_content']) {
4723
		$hash = md5($contexte_implicite['host'] . '::'. $cache);
4724
	}
4725
	else {
4726
		unset($contexte_implicite['notes']); // pas pertinent pour signaler un changeemnt de contenu pour des css/js
4727
		ksort($contexte);
4728
		$hash = md5($fond . json_encode($contexte_implicite) . json_encode($contexte) . $connect);
4729
	}
4730
	$filename = $dir_var . $extension . "dyn-$nom_safe-" . substr($hash, 0, 8) . ".$extension";
4731
4732
	// mettre a jour le fichier si il n'existe pas
4733
	// ou trop ancien
4734
	// le dernier fichier produit est toujours suffixe par .last
4735
	// et recopie sur le fichier cible uniquement si il change
4736
	if (!file_exists($filename)
4737
		or !file_exists($filename . ".last")
4738
		or (isset($cache['lastmodified']) and $cache['lastmodified'] and filemtime($filename . ".last") < $cache['lastmodified'])
4739
		or (defined('_VAR_MODE') and _VAR_MODE == 'recalcul')
4740
	) {
4741
		$contenu = $cache['texte'];
4742
		// passer les urls en absolu si c'est une css
4743
		if ($extension == "css") {
4744
			$contenu = urls_absolues_css($contenu,
4745
				test_espace_prive() ? generer_url_ecrire('accueil') : generer_url_public($fond));
4746
		}
4747
4748
		$comment = '';
4749
		// ne pas insérer de commentaire si c'est du json
4750
		if ($extension != "json") {
4751
			$comment = "/* #PRODUIRE{fond=$fond";
4752
			foreach ($contexte as $k => $v) {
4753
				$comment .= ",$k=$v";
4754
			}
4755
			// pas de date dans le commentaire car sinon ca invalide le md5 et force la maj
4756
			// mais on peut mettre un md5 du contenu, ce qui donne un aperu rapide si la feuille a change ou non
4757
			$comment .= "}\n   md5:" . md5($contenu) . " */\n";
4758
		}
4759
		// et ecrire le fichier si il change
4760
		ecrire_fichier_calcule_si_modifie($filename, $comment . $contenu, false, true);
4761
	}
4762
4763
	return timestamp($filename);
4764
}
4765
4766
/**
4767
 * Ajouter un timestamp a une url de fichier
4768
 * [(#CHEMIN{monfichier}|timestamp)]
4769
 *
4770
 * @param string $fichier
4771
 *    Le chemin du fichier sur lequel on souhaite ajouter le timestamp
4772
 * @return string
4773
 *    $fichier auquel on a ajouté le timestamp
4774
 */
4775
function timestamp($fichier) {
4776
	if (!$fichier
4777
		or !file_exists($fichier)
4778
		or !$m = filemtime($fichier)
4779
	) {
4780
		return $fichier;
4781
	}
4782
4783
	return "$fichier?$m";
4784
}
4785
4786
/**
4787
 * Supprimer le timestamp d'une url
4788
 *
4789
 * @param string $url
4790
 * @return string
4791
 */
4792
function supprimer_timestamp($url) {
4793
	if (strpos($url, "?") === false) {
4794
		return $url;
4795
	}
4796
4797
	return preg_replace(",\?[[:digit:]]+$,", "", $url);
4798
}
4799
4800
/**
4801
 * Nettoyer le titre d'un email
4802
 *
4803
 * Éviter une erreur lorsqu'on utilise `|nettoyer_titre_email` dans un squelette de mail
4804
 *
4805
 * @filtre
4806
 * @uses nettoyer_titre_email()
4807
 *
4808
 * @param string $titre
4809
 * @return string
4810
 */
4811
function filtre_nettoyer_titre_email_dist($titre) {
4812
	include_spip('inc/envoyer_mail');
4813
4814
	return nettoyer_titre_email($titre);
4815
}
4816
4817
/**
4818
 * Afficher le sélecteur de rubrique
4819
 *
4820
 * Il permet de placer un objet dans la hiérarchie des rubriques de SPIP
4821
 *
4822
 * @uses chercher_rubrique()
4823
 *
4824
 * @param string $titre
4825
 * @param int $id_objet
4826
 * @param int $id_parent
4827
 * @param string $objet
4828
 * @param int $id_secteur
4829
 * @param bool $restreint
4830
 * @param bool $actionable
4831
 *   true : fournit le selecteur dans un form directement postable
4832
 * @param bool $retour_sans_cadre
4833
 * @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...
4834
 */
4835
function filtre_chercher_rubrique_dist(
4836
	$titre,
4837
	$id_objet,
4838
	$id_parent,
4839
	$objet,
4840
	$id_secteur,
4841
	$restreint,
4842
	$actionable = false,
4843
	$retour_sans_cadre = false
4844
) {
4845
	include_spip('inc/filtres_ecrire');
4846
4847
	return chercher_rubrique($titre, $id_objet, $id_parent, $objet, $id_secteur, $restreint, $actionable,
4848
		$retour_sans_cadre);
4849
}
4850
4851
/**
4852
 * Rediriger une page suivant une autorisation,
4853
 * et ce, n'importe où dans un squelette, même dans les inclusions.
4854
 *
4855
 * En l'absence de redirection indiquée, la fonction redirige par défaut
4856
 * sur une 403 dans l'espace privé et 404 dans l'espace public.
4857
 *
4858
 * @example
4859
 *     ```
4860
 *     [(#AUTORISER{non}|sinon_interdire_acces)]
4861
 *     [(#AUTORISER{non}|sinon_interdire_acces{#URL_PAGE{login}, 401})]
4862
 *     ```
4863
 *
4864
 * @filtre
4865
 * @param bool $ok
4866
 *     Indique si l'on doit rediriger ou pas
4867
 * @param string $url
4868
 *     Adresse eventuelle vers laquelle rediriger
4869
 * @param int $statut
4870
 *     Statut HTML avec lequel on redirigera
4871
 * @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...
4872
 *     message d'erreur
4873
 * @return string|void
4874
 *     Chaîne vide si l'accès est autorisé
4875
 */
4876
function sinon_interdire_acces($ok = false, $url = '', $statut = 0, $message = null) {
4877
	if ($ok) {
4878
		return '';
4879
	}
4880
4881
	// Vider tous les tampons
4882
	$level = @ob_get_level();
4883
	while ($level--) {
4884
		@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...
4885
	}
4886
4887
	include_spip('inc/headers');
4888
4889
	// S'il y a une URL, on redirige (si pas de statut, la fonction mettra 302 par défaut)
4890
	if ($url) {
4891
		redirige_par_entete($url, '', $statut);
4892
	}
4893
4894
	// ecriture simplifiee avec message en 3eme argument (= statut 403)
4895
	if (!is_numeric($statut) and is_null($message)) {
4896
		$message = $statut;
4897
		$statut = 0;
4898
	}
4899
	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...
4900
		$message = '';
4901
	}
4902
	$statut = intval($statut);
4903
4904
	// Si on est dans l'espace privé, on génère du 403 Forbidden par defaut ou du 404
4905
	if (test_espace_prive()) {
4906
		if (!$statut or !in_array($statut, array(404, 403))) {
4907
			$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...
4908
		}
4909
		http_status(403);
4910
		$echec = charger_fonction('403', 'exec');
4911
		$echec($message);
4912
	} else {
4913
		// Sinon dans l'espace public on redirige vers une 404 par défaut, car elle toujours présente normalement
4914
		if (!$statut) {
4915
			$statut = 404;
4916
		}
4917
		// Dans tous les cas on modifie l'entité avec ce qui est demandé
4918
		http_status($statut);
4919
		// Si le statut est une erreur et qu'il n'y a pas de redirection on va chercher le squelette du même nom
4920
		if ($statut >= 400) {
4921
			echo recuperer_fond("$statut", array('erreur' => $message));
4922
		}
4923
	}
4924
4925
4926
	exit;
4927
}
4928
4929
/**
4930
 * Assurer le fonctionnement de |compacte meme sans l'extension compresseur
4931
 *
4932
 * @param string $source
4933
 * @param null|string $format
4934
 * @return string
4935
 */
4936
function filtre_compacte_dist($source, $format = null) {
4937
	if (function_exists('compacte')) {
4938
		return compacte($source, $format);
4939
	}
4940
4941
	return $source;
4942
}
4943
4944
4945
/**
4946
 * Afficher partiellement un mot de passe que l'on ne veut pas rendre lisible par un champ hidden
4947
 * @param string $passe
4948
 * @param bool $afficher_partiellement
4949
 * @param int|null $portion_pourcent
4950
 * @return string
4951
 */
4952
function spip_affiche_mot_de_passe_masque($passe, $afficher_partiellement = false, $portion_pourcent = null) {
4953
	$l = strlen($passe);
4954
4955
	if ($l<=8 or !$afficher_partiellement){
4956
		if (!$l) {
4957
			return ''; // montrer qu'il y a pas de mot de passe si il y en a pas
4958
		}
4959
		return str_pad('',$afficher_partiellement ? $l : 16,'*');
4960
	}
4961
4962
	if (is_null($portion_pourcent)) {
4963
		if (!defined('_SPIP_AFFICHE_MOT_DE_PASSE_MASQUE_PERCENT')) {
4964
			define('_SPIP_AFFICHE_MOT_DE_PASSE_MASQUE_PERCENT', 20); // 20%
4965
		}
4966
		$portion_pourcent = _SPIP_AFFICHE_MOT_DE_PASSE_MASQUE_PERCENT;
4967
	}
4968
	if ($portion_pourcent >= 100) {
4969
		return $passe;
4970
	}
4971
	$e = intval(ceil($l * $portion_pourcent / 100 / 2));
4972
	$e = max($e, 0);
4973
	$mid = str_pad('',$l-2*$e,'*');
4974
	if ($e>0 and strlen($mid)>8){
4975
		$mid = '***...***';
4976
	}
4977
	return substr($passe,0,$e) . $mid . ($e > 0 ? substr($passe,-$e) : '');
4978
}