Skip to content
Extraits de code Groupes Projets
utils.php 107 ko
Newer Older
<?php

/***************************************************************************\
 *  SPIP, Système de publication pour l'internet                           *
 *  Copyright © avec tendresse depuis 2001                                 *
 *  Arnaud Martin, Antoine Pitrou, Philippe Rivière, Emmanuel Saint-James  *
 *  Ce programme est un logiciel libre distribué sous licence GNU/GPL.     *
\***************************************************************************/

marcimat's avatar
marcimat a validé
/**
 * Utilitaires indispensables autour du serveur Http.
 *
 * @package SPIP\Core\Utilitaires
marcimat's avatar
marcimat a validé

if (!defined('_ECRIRE_INC_VERSION')) {
	return;
}
 * Cherche une fonction surchargeable et en retourne le nom exact,
 * après avoir chargé le fichier la contenant si nécessaire.
 * Charge un fichier (suivant les chemins connus) et retourne si elle existe
 * le nom de la fonction homonyme `$dir_$nom`, ou suffixé `$dir_$nom_dist`
 * Peut être appelé plusieurs fois, donc optimisé.
 * @api
 * @uses include_spip() Pour charger le fichier
 * @example
 *     ```
 *     $envoyer_mail = charger_fonction('envoyer_mail', 'inc');
 *     $envoyer_mail($email, $sujet, $texte);
 *     ```
 * @param string $nom
 *     Nom de la fonction (et du fichier)
 * @param string $dossier
 * @param bool $continue
 *     true pour ne pas râler si la fonction n'est pas trouvée
 * @return string
 *     Nom de la fonction, ou false.
function charger_fonction($nom, $dossier = 'exec', $continue = false) {
JamesRezo's avatar
JamesRezo a validé
	static $echecs = [];
	if (strlen($dossier) && !str_ends_with($dossier, '/')) {
		$dossier .= '/';
	}
	$f = str_replace('/', '_', $dossier) . $nom;
	// Sinon charger le fichier de declaration si plausible

	if (!preg_match(',^\w+$,', $f)) {
		if ($continue) {
			return false;
		} //appel interne, on passe
		include_spip('inc/minipres');
		echo minipres();
		exit;
	// passer en minuscules (cf les balises de formulaires)
JamesRezo's avatar
JamesRezo a validé
	if (
		!($inc = include_spip($dossier . ($d = strtolower($nom))))
		&& strlen(dirname($dossier))
		&& dirname($dossier) != '.'
	) {
		include_spip(substr($dossier, 0, -1));
	}
	if (function_exists($f)) {
		return $f;
	}
	if (function_exists($g)) {
		return $g;
	}
	// Echec : message d'erreur
	spip_log("fonction $nom ($f ou $g) indisponible" .
JamesRezo's avatar
JamesRezo a validé
		($inc ? '' : " (fichier $d absent de $dossier)"));
Fil's avatar
Fil a validé
	include_spip('inc/minipres');
	include_spip('inc/filtres_mini');
JamesRezo's avatar
JamesRezo a validé
	echo minipres(
		_T('forum_titre_erreur'),
marcimat's avatar
marcimat a validé
			_T('fonction_introuvable', ['fonction' => '<code>' . spip_htmlentities($f) . '</code>'])
			. '<br />'
			. _T('fonction_introuvable', ['fonction' => '<code>' . spip_htmlentities($g) . '</code>'])
marcimat's avatar
marcimat a validé
			_T('fichier_introuvable', ['fichier' => '<code>' . spip_htmlentities($d) . '</code>']),
		['all_inline' => true,'status' => 404]
JamesRezo's avatar
JamesRezo a validé
	);
/**
 * Inclusion unique avec verification d'existence du fichier + log en crash sinon
 * @param string $file
 * @return bool
 */
function include_once_check($file) {
	if (file_exists($file)) {
		include_once $file;

		return true;
	}
	$crash = (isset($GLOBALS['meta']['message_crash_plugins']) ? unserialize($GLOBALS['meta']['message_crash_plugins']) : '');
	$crash = ($crash ?: []);
	ecrire_meta('message_crash_plugins', serialize($crash));


/**
 * Inclut un fichier PHP (en le cherchant dans les chemins)
 * @api
 * @uses find_in_path()
 * @example
 *     ```
 *     include_spip('inc/texte');
 *     ```
 * @param string $f
 *     Nom du fichier (sans l'extension)
 * @param bool $include
 *     - true pour inclure le fichier,
 *     - false ne fait que le chercher
 * @return string|bool
 *     - false : fichier introuvable
 *     - string : chemin du fichier trouvé
function include_spip($f, $include = true) {
	return find_in_path($f . '.php', '', $include);
/**
 * Requiert un fichier PHP (en le cherchant dans les chemins)
 * @example
 *     ```
 *     require_spip('inc/texte');
 *     ```
 * @param string $f
 *     Nom du fichier (sans l'extension)
 * @return string|bool
 *     - false : fichier introuvable
 *     - string : chemin du fichier trouvé
function require_spip($f) {
	return find_in_path($f . '.php', '', 'required');
}


/**
 * Raccourci pour inclure mes_fonctions.php et tous les fichiers _fonctions.php des plugin
 * quand on a besoin dans le PHP de filtres/fonctions qui y sont definis
 */
function include_fichiers_fonctions() {
	static $done = false;
	if (!$done) {
		include_spip('inc/lang');

		// NB: mes_fonctions peut initialiser $dossier_squelettes (old-style)
		// donc il faut l'inclure "en globals"
		if ($f = find_in_path('mes_fonctions.php')) {
			global $dossier_squelettes;
			include_once(_ROOT_CWD . $f);
		}

		if (@is_readable(_CACHE_PLUGINS_FCT)) {
			// chargement optimise precompile
			include_once(_CACHE_PLUGINS_FCT);
		}
		if (test_espace_prive()) {
			include_spip('inc/filtres_ecrire');
		}
cerdic's avatar
cerdic a validé
		include_spip('public/fonctions'); // charger les fichiers fonctions associes aux criteres, balises..
marcimat's avatar
marcimat a validé
 * Exécute une fonction (appellée par un pipeline) avec la donnée transmise.
 * Un pipeline est lie a une action et une valeur
 * chaque element du pipeline est autorise a modifier la valeur
 * le pipeline execute les elements disponibles pour cette action,
 * les uns apres les autres, et retourne la valeur finale
 * Cf. compose_filtres dans references.php, qui est la
 * version compilee de cette fonctionnalite
 * appel unitaire d'une fonction du pipeline
 * utilisee dans le script pipeline precompile
 * on passe $val par reference pour limiter les allocations memoire
marcimat's avatar
marcimat a validé
 * @param string $fonc
 *     Nom de la fonction appelée par le pipeline
marcimat's avatar
marcimat a validé
 *     Les paramètres du pipeline, son environnement
marcimat's avatar
marcimat a validé
 *     Les paramètres du pipeline modifiés
		$val = $fonc($val);
JamesRezo's avatar
JamesRezo a validé
		if (
			preg_match('/^(\w*)::(\w*)$/S', $fonc, $regs)
			&& ($methode = [$regs[1], $regs[2]])
			&& is_callable($methode)
			$val = $methode($val);
marcimat's avatar
marcimat a validé
/**
 * Appel d’un pipeline
 *
 * Exécute le pipeline souhaité, éventuellement avec des données initiales.
 * Chaque plugin qui a demandé à voir ce pipeline vera sa fonction spécifique appelée.
 * Les fonctions (des plugins) appelées peuvent modifier à leur guise le contenu.
 *
 * Deux types de retours. Si `$val` est un tableau de 2 éléments, avec une clé `data`
 * on retourne uniquement ce contenu (`$val['data']`) sinon on retourne tout `$val`.
 *
 *
 * @example
 *     Appel du pipeline `pre_insertion`
 *     ```
 *     $champs = pipeline('pre_insertion', array(
 *         'args' => array('table' => 'spip_articles'),
 *         'data' => $champs
 *     ));
 *     ```
 *
 * @param string $action
 *     Nom du pipeline
 * @param mixed $val
marcimat's avatar
marcimat a validé
 *     Données à l’entrée du pipeline
 * @return mixed|null
 *     Résultat
 */
function pipeline($action, $val = null) {
	static $charger;

	// chargement initial des fonctions mises en cache, ou generation du cache
	if (!$charger) {
		if (!($ok = @is_readable($charger = _CACHE_PIPELINES))) {
			include_spip('inc/plugin');
			// generer les fichiers php precompiles
			// de chargement des plugins et des pipelines
			actualise_plugins_actifs();
				spip_log("fichier $charger pas cree");
Fil's avatar
Fil a validé
		}
			include_once $charger;
	}

	// appliquer notre fonction si elle existe
	$fonc = 'execute_pipeline_' . strtolower($action);
	if (function_exists($fonc)) {
		$val = $fonc($val);
		spip_log("fonction $fonc absente : pipeline desactive", _LOG_ERREUR);
	// si le flux est une table avec 2 cle args&data
	// on ne ressort du pipe que les donnees dans 'data'
	// array_key_exists pour php 4.1.0
JamesRezo's avatar
JamesRezo a validé
	if (
		is_array($val)
		&& count($val) == 2
		&& array_key_exists('data', $val)
		$val = $val['data'];
marcimat's avatar
marcimat a validé
 * Enregistrement des événements
 *
 * Signature : `spip_log(message[,niveau|type|type.niveau])`
 *
 * Le niveau de log par défaut est la valeur de la constante `_LOG_INFO`
amemo's avatar
amemo a validé
 * Les différents niveaux possibles sont :
 *
 * - `_LOG_HS` : écrira 'HS' au début de la ligne logguée
 * - `_LOG_ALERTE_ROUGE` : 'ALERTE'
 * - `_LOG_CRITIQUE` :  'CRITIQUE'
 * - `_LOG_ERREUR` : 'ERREUR'
 * - `_LOG_AVERTISSEMENT` : 'WARNING'
 * - `_LOG_INFO_IMPORTANTE` : '!INFO'
 * - `_LOG_INFO` : 'info'
 * - `_LOG_DEBUG` : 'debug'
 *
marcimat's avatar
marcimat a validé
 * @example
 *   ```
 *   spip_log($message)
amemo's avatar
amemo a validé
 *   spip_log($message, 'recherche')
 *   spip_log($message, _LOG_DEBUG)
 *   spip_log($message, 'recherche.'._LOG_DEBUG)
marcimat's avatar
marcimat a validé
 *   ```
 * @link https://programmer.spip.net/spip_log
marcimat's avatar
marcimat a validé
 *     Message à loger
marcimat's avatar
marcimat a validé
 *     - int indique le niveau de log, tel que `_LOG_DEBUG`
 *     - string indique le type de log
 *     - `string.int` indique les 2 éléments.
 *     Cette dernière notation est controversée mais le 3ème
amemo's avatar
amemo a validé
 *     paramètre est planté pour cause de compatibilité ascendante.
function spip_log($message = null, $name = null) {
JamesRezo's avatar
JamesRezo a validé
	static $pre = [];
	static $log;
	preg_match('/^([a-z_]*)\.?(\d)?$/iS', (string)$name, $regs);
	if (!isset($regs[1]) || !$logname = $regs[1]) {
	if ($niveau <= (defined('_LOG_FILTRE_GRAVITE') ? _LOG_FILTRE_GRAVITE : _LOG_INFO_IMPORTANTE)) {
JamesRezo's avatar
JamesRezo a validé
			$pre = [
				_LOG_HS => 'HS:',
				_LOG_ALERTE_ROUGE => 'ALERTE:',
				_LOG_CRITIQUE => 'CRITIQUE:',
				_LOG_ERREUR => 'ERREUR:',
				_LOG_AVERTISSEMENT => 'WARNING:',
				_LOG_INFO_IMPORTANTE => '!INFO:',
				_LOG_INFO => 'info:',
				_LOG_DEBUG => 'debug:'
JamesRezo's avatar
JamesRezo a validé
			];
			$log = charger_fonction('log', 'inc');
		}
		if (!is_string($message)) {
			$message = print_r($message, true);
		}
		$log($pre[$niveau] . ' ' . $message, $logname);
/**
 * Enregistrement des journaux
 *
 * @uses inc_journal_dist()
 * @param string $phrase texte du journal
 * @param array $opt Tableau d'options
JamesRezo's avatar
JamesRezo a validé
function journal($phrase, $opt = []) {
	$journal = charger_fonction('journal', 'inc');
	$journal($phrase, $opt);
}


/**
 * Renvoie le `$_GET` ou le `$_POST` émis par l'utilisateur
 * ou pioché dans un tableau transmis
 *
 * @api
 * @param string $var
 *     Clé souhaitée
 *     Tableau transmis (sinon cherche dans GET ou POST)
 * @return mixed|null
 *     - null si la clé n'a pas été trouvée
 *     - la valeur de la clé sinon.
		return $c[$var] ?? null;
	if (isset($_GET[$var])) {
		$a = $_GET[$var];
	} elseif (isset($_POST[$var])) {
		$a = $_POST[$var];
	} else {
		return null;
	}
	// Si on est en ajax et en POST tout a ete encode
	// via encodeURIComponent, il faut donc repasser
	// dans le charset local...
JamesRezo's avatar
JamesRezo a validé
	if (
		defined('_AJAX')
		&& _AJAX
		&& isset($GLOBALS['meta']['charset'])
		&& $GLOBALS['meta']['charset'] != 'utf-8'
		&& is_string($a)
		&& preg_match(',[\x80-\xFF],', $a)
		&& include_spip('inc/charsets')
		&& is_utf8($a)
		return importer_charset($a, 'utf-8');

/**
 * Affecte une valeur à une clé (pour usage avec `_request()`)
 *
 * @see _request() Pour obtenir la valeur
 * @note Attention au cas ou l'on fait `set_request('truc', NULL);`
 * @param string $var Nom de la clé
 * @param string $val Valeur à affecter
b_b's avatar
b_b a validé
 * @param bool|array $c Tableau de données (sinon utilise `$_GET` et `$_POST`)
 * @return array|bool
 *     - array $c complété si un $c est transmis,
 *     - false sinon
 **/
function set_request($var, $val = null, $c = false) {
	if (is_array($c)) {
		unset($c[$var]);
	unset($_POST[$var]);
		$_GET[$var] = $val;
	return false; # n'affecte pas $c
/**
 * Sanitizer une valeur *SI* elle provient du GET ou POST
 * Utile dans les squelettes pour les valeurs qu'on attrape dans le env,
 * dont on veut permettre à un squelette de confiance appelant de fournir une valeur complexe
 * mais qui doit etre nettoyee si elle provient de l'URL
 *
 * On peut sanitizer
 * - une valeur simple : `$where = spip_sanitize_from_request($value, 'where')`
 * - un tableau en partie : `$env = spip_sanitize_from_request($env, ['key1','key2'])`
 * - un tableau complet : `$env = spip_sanitize_from_request($env, '*')`
 *
 * @param string|array $value
 * @param string|array $key
 * @param string $sanitize_function
 * @return array|mixed|string
 */
JamesRezo's avatar
JamesRezo a validé
function spip_sanitize_from_request($value, $key, $sanitize_function = 'entites_html') {
	if (is_array($value)) {
JamesRezo's avatar
JamesRezo a validé
		if ($key == '*') {
			$key = array_keys($value);
		}
		if (!is_array($key)) {
			$key = [$key];
		}
		foreach ($key as $k) {
			if (!empty($value[$k])) {
				$value[$k] = spip_sanitize_from_request($value[$k], $k, $sanitize_function);
			}
		}
		return $value;
	}
	// si la valeur vient des GET ou POST on la sanitize
	if (!empty($value) && $value == _request($key)) {
		$value = $sanitize_function($value);
	}
	return $value;
}
 * Tester si une URL est absolue
JamesRezo's avatar
JamesRezo a validé
 *
 * On est sur le web, on exclut certains protocoles,
 * notamment 'file://', 'php://' et d'autres…

 * @return bool
 */
	$url = trim($url ?? '');
	if ($url && preg_match(';^([a-z]{3,7}:)?//;Uims', $url, $m)) {
			&& ($p = strtolower(rtrim($m[1], ':')))
			&& in_array($p, ['file', 'php', 'zlib', 'glob', 'phar', 'ssh2', 'rar', 'ogg', 'expect', 'zip'])
JamesRezo's avatar
JamesRezo a validé
		) {
 * Prend une URL et lui ajoute/retire un paramètre
 * @link https://www.spip.net/4255
 * @example
 *     ```
 *     [(#SELF|parametre_url{suite,18})] (ajout)
 *     [(#SELF|parametre_url{suite,''})] (supprime)
 *     [(#SELF|parametre_url{suite[],1})] (tableaux valeurs multiples)
 *     ```
 * @param string $url URL
 * @param string $c Nom du paramètre
 * @param string|array|null $v Valeur du paramètre
 * @param string $sep Séparateur entre les paramètres
 * @return string URL
function parametre_url($url, $c, $v = null, $sep = '&amp;') {
	// requete erronnee : plusieurs variable dans $c et aucun $v
	if (str_contains($c, '|') && is_null($v)) {

	// lever l'#ancre
	if (preg_match(',^([^#]*)(#.*)$,', $url, $r)) {
		$url = $r[1];
		$ancre = $r[2];

	// eclater
	$url = preg_split(',[?]|&amp;|&,', $url);

	// recuperer la base
	$a = array_shift($url);
	// preparer la regexp de maniere securisee
	$regexp = explode('|', $c);
	foreach ($regexp as $r => $e) {
		$regexp[$r] = str_replace('[]', '\[\]', preg_replace(',[^\w\d\[\]-],', '', $e));
	}
	$regexp = ',^(' . implode('|', $regexp) . '[[]?[]]?)(=.*)?$,';
	$u = is_array($v) ? $v : rawurlencode((string) $v);
	$testv = (is_array($v) ? count($v) : strlen((string) $v));
	// lire les variables et agir
	foreach ($url as $n => $val) {
		if (preg_match($regexp, urldecode($val), $r)) {
marcimat's avatar
marcimat a validé
			$r = array_pad($r, 3, null);
				// c'est un tableau, on memorise les valeurs
				if (str_ends_with($r[1], '[]')) {
JamesRezo's avatar
JamesRezo a validé
						$v_read = [];
					}
					$v_read[] = $r[2] ? substr($r[2], 1) : '';
				} // c'est un scalaire, on retourne direct
b_b's avatar
b_b a validé
			// Ajout. Pour une variable, remplacer au meme endroit,
			// pour un tableau ce sera fait dans la prochaine boucle
			elseif (!str_ends_with($r[1], '[]')) {
b_b's avatar
b_b a validé
			// Pour les tableaux on laisse tomber les valeurs de
			// départ, on remplira à l'étape suivante
b_b's avatar
b_b a validé
				unset($url[$n]);
			}
	// traiter les parametres pas encore trouves
JamesRezo's avatar
JamesRezo a validé
	if (
		$v === null
		&& ($args = func_get_args())
		&& count($args) == 2
		return $v_read; // rien trouve ou un tableau
marcimat's avatar
marcimat a validé
	} elseif ($testv) {
		foreach ($ajouts as $k => $n) {
			if (!is_array($v)) {
				$url[] = $k . '=' . $u;
			} else {
				$id = (str_ends_with($k, '[]')) ? $k : ($k . '[]');
marcimat's avatar
marcimat a validé
				foreach ($v as $w) {
					$url[] = $id . '=' . (is_array($w) ? 'Array' : rawurlencode($w));
marcimat's avatar
marcimat a validé
				}
			}

	// eliminer les vides
	$url = array_filter($url);

	// recomposer l'adresse
marcimat's avatar
marcimat a validé
/**
 * Ajoute (ou retire) une ancre sur une URL
 *
 * L’ancre est nettoyée : on translitère, vire les non alphanum du début,
 * et on remplace ceux à l'interieur ou au bout par `-`
 *
 * @example
 *     - `$url = ancre_url($url, 'navigation'); // => mettra l’ancre #navigation
 *     - `$url = ancre_url($url, ''); // => enlèvera une éventuelle ancre
 * @uses translitteration()
 */
function ancre_url(string $url, ?string $ancre = ''): string {
	$ancre ??= '';
	// lever l'#ancre
	if (preg_match(',^([^#]*)(#.*)$,', $url, $r)) {
		$url = $r[1];
	}
	if (preg_match('/[^-_a-zA-Z0-9]+/S', $ancre)) {
		if (!function_exists('translitteration')) {
marcimat's avatar
marcimat a validé
		$ancre = preg_replace(
JamesRezo's avatar
JamesRezo a validé
			['/^[^-_a-zA-Z0-9]+/', '/[^-_a-zA-Z0-9]/'],
			['', '-'],
marcimat's avatar
marcimat a validé
			translitteration($ancre)
		);
	return $url . (strlen($ancre) ? '#' . $ancre : '');
amemo's avatar
amemo a validé
 * Pour le nom du cache, les `types_urls` et `self`
 *
 * @param string|null $reset
 * @return string
 */
	static $done = false;
	static $propre = '';
	if (!is_null($reset)) {
		return $propre = $reset;
	}
	if ($done) {
		return $propre;
	}
	return $propre = nettoyer_uri_var($GLOBALS['REQUEST_URI']);
}
 * Nettoie une URI de certains paramètres (var_xxx, utm_xxx, etc.)
 *
 * La regexp des paramètres nettoyés est calculée à partir de la constante `_CONTEXTE_IGNORE_LISTE_VARIABLES`
 * (qui peut être redéfinie dans mes_options.php)
JamesRezo's avatar
JamesRezo a validé
 *
 * @uses _CONTEXTE_IGNORE_LISTE_VARIABLES
JamesRezo's avatar
JamesRezo a validé
 *
marcimat's avatar
marcimat a validé
 * @param string $request_uri
 * @return string
 */
function nettoyer_uri_var($request_uri) {
	static $preg_nettoyer;
	if (!defined('_CONTEXTE_IGNORE_LISTE_VARIABLES')) {
		/** @var array<string> Liste (regexp) de noms de variables à ignorer d’une URI */
		define('_CONTEXTE_IGNORE_LISTE_VARIABLES', ['^var_', '^PHPSESSID$', '^fbclid$', '^utm_']);
	}
	if (empty($preg_nettoyer)) {
		$preg_nettoyer_vars = _CONTEXTE_IGNORE_LISTE_VARIABLES;
		foreach ($preg_nettoyer_vars as &$var) {
			if (str_starts_with($var, '^')) {
				$var = substr($var, 1);
			} else {
				$var = '[^=&]*' . $var;
			}
			if (str_ends_with($var, '$')) {
				$var = substr($var, 0, -1);
			} else {
				$var .= '[^=&]*';
			}
		}
		$preg_nettoyer = ',([?&])(' . implode('|', $preg_nettoyer_vars) . ')=[^&]*(&|$),i';
	}
	if (empty($request_uri)) {
		return $request_uri;
	}
		$uri1 = preg_replace($preg_nettoyer, '\1', $uri);

/**
 * Donner l'URL de base d'un lien vers "soi-meme", modulo les trucs inutiles
 *
 * @param string $amp
 *    Style des esperluettes
 * @param bool $root
 * @return string
 *    URL vers soi-même
function self($amp = '&amp;', $root = false) {
JamesRezo's avatar
JamesRezo a validé
	if (
		!$root
			// si pas de profondeur on peut tronquer
			$GLOBALS['profondeur_url'] < (_DIR_RESTREINT ? 1 : 2)
			// sinon c'est OK si _SET_HTML_BASE a ete force a false
			|| defined('_SET_HTML_BASE') && !_SET_HTML_BASE
		)
		$url = preg_replace(',^[^?]*/,', '', $url);
	// ajouter le cas echeant les variables _POST['id_...']
		if (str_starts_with($v, 'id_')) {
			$url = parametre_url($url, $v, $c, '&');

	// supprimer les variables sans interet
		$url = preg_replace(',([?&])('
			. 'lang|show_docs|'
			. 'changer_lang|var_lang|action)=[^&]*,i', '\1', $url);
		$url = preg_replace(',([?&])[&]+,', '\1', $url);
		$url = preg_replace(',[&]$,', '\1', $url);
	}
Fil's avatar
Fil a validé
	// eviter les hacks
	include_spip('inc/filtres_mini');
	$url = spip_htmlspecialchars($url);
JamesRezo's avatar
JamesRezo a validé

	$url = str_replace(["'", '"', '<', '[', ']', ':'], ['%27', '%22', '%3C', '%5B', '%5D', '%3A'], $url);
Fil's avatar
Fil a validé

		$url = str_replace('&amp;', $amp, $url);
	// Si ca demarre par ? ou vide, donner './'
	$url = preg_replace(',^([?].*)?$,', './\1', $url);
/**
 * Indique si on est dans l'espace prive
 *
 * @return bool
 *     true si c'est le cas, false sinon.
 */
	return defined('_ESPACE_PRIVE') ? _ESPACE_PRIVE : false;
 * Vérifie la présence d'un plugin actif, identifié par son préfixe
	return ($plugin && defined('_DIR_PLUGIN_' . strtoupper($plugin))) ? true : false;
 * Traduit une clé de traduction en l'obtenant dans les fichiers de langues.
 * @api
 * @uses inc_traduire_dist()
 * @uses _L()
 * @example
 *     ```
 *     _T('bouton_enregistrer')
 *     _T('medias:image_tourner_droite')
amemo's avatar
amemo a validé
 *     _T('medias:erreurs', array('nb'=>3))
 *     _T("email_sujet", array('spip_lang'=>$lang_usager))
amemo's avatar
amemo a validé
 *     Couples (variable => valeur) pour passer des variables à la chaîne traduite. la variable spip_lang permet de forcer la langue
 *     - string class : nom d'une classe a ajouter sur un span pour encapsuler la chaine
 *     - bool force : forcer un retour meme si la chaine n'a pas de traduction
 *     - bool sanitize : nettoyer le html suspect dans les arguments
JamesRezo's avatar
JamesRezo a validé
function _T($texte, $args = [], $options = []) {
JamesRezo's avatar
JamesRezo a validé
	$o = ['class' => '', 'force' => true, 'sanitize' => true];
JamesRezo's avatar
JamesRezo a validé
			$options = ['class' => $options];
esj's avatar
esj a validé

		$traduire = charger_fonction('traduire', 'inc');
	// On peut passer explicitement la langue dans le tableau
	// On utilise le même nom de variable que la globale
		$lang = $args['spip_lang'];
		// On l'enleve pour ne pas le passer au remplacement
		unset($args['spip_lang']);
marcimat's avatar
marcimat a validé
	else {
		$lang = $GLOBALS['spip_lang'];
	}
	$text = $traduire($texte, $lang);
	if ($text === null || !strlen($text)) {
		// pour les chaines non traduites, assurer un service minimum
		if (!$GLOBALS['test_i18n'] && _request('var_mode') != 'traduction') {
			$n = strpos($text, ':');
			if ($n !== false) {
				$text = substr($text, $n + 1);
			}
			$text = str_replace('_', ' ', $text);

/**
 * Remplace les variables `@...@` par leur valeur dans une chaîne de langue.
 *
 * Cette fonction est également appelée dans le code source de SPIP quand une
 * chaîne n'est pas encore dans les fichiers de langue.
 *
 * @see _T()
 * @example
 *     ```
 *     _L('Texte avec @nb@ ...', array('nb'=>3)
 *     ```
 * @param array $args
 *     Couples (variable => valeur) à transformer dans le texte
 * @param array $options
 *     - string class : nom d'une classe a ajouter sur un span pour encapsuler la chaine
 *     - bool sanitize : nettoyer le html suspect dans les arguments
JamesRezo's avatar
JamesRezo a validé
function _L($text, $args = [], $options = []) {
JamesRezo's avatar
JamesRezo a validé
	$defaut_options = [
JamesRezo's avatar
JamesRezo a validé
	];
	// support de l'ancien argument $class
	if ($options && is_string($options)) {
JamesRezo's avatar
JamesRezo a validé
		$options = ['class' => $options];
		$options += $defaut_options;
	} else {
		$options = $defaut_options;
	if (is_array($args) && count($args)) {
		if (!function_exists('interdire_scripts')) {
			include_spip('inc/texte');
		}
		if (!function_exists('echapper_html_suspect')) {
			include_spip('inc/texte_mini');
		}
		foreach ($args as $name => $value) {
			if (str_contains($text, (string) "@$name@")) {
				if ($options['sanitize']) {
					$value = echapper_html_suspect($value);
					$value = interdire_scripts($value, -1);
				}
				if (!empty($options['class'])) {
JamesRezo's avatar
JamesRezo a validé
					$value = "<span class='" . $options['class'] . "'>$value</span>";
				$text = str_replace("@$name@", (string) $value, (string) $text);
		// Si des variables n'ont pas ete inserees, le signaler
		// (chaines de langues pas a jour)
		if ($args) {
			spip_log("$f:  variables inutilisees " . join(', ', array_keys($args)), _LOG_DEBUG);
		}
Fil's avatar
 
Fil a validé

	if (($GLOBALS['test_i18n'] || _request('var_mode') == 'traduction') && is_null($options['class'])) {
		return "<span class='debug-traduction-erreur'>$text</span>";

/**
 * Retourne un joli chemin de répertoire
 *
 * Pour afficher `ecrire/action/` au lieu de `action/` dans les messages
 * ou `tmp/` au lieu de `../tmp/`
 *
b_b's avatar
b_b a validé
 * @param string $rep Chemin d’un répertoire
 * @return string
 */
function joli_repertoire($rep) {
	if ($a <> '.' && $a <> '/') {