Completed
Push — master ( 27dd6e...9ef4c0 )
by cam
04:23
created

filtres.php ➔ http_img_pack()   C

Complexity

Conditions 16
Paths 28

Size

Total Lines 35

Duplication

Lines 0
Ratio 0 %

Importance

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