Dépôt officiel du core SPIP * Copie possible par svn sur svn://trac.rezo.net/spip * Les svn:externals sont présent dans https://git.spip.net/SPIP/[nom du plugin dist]
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

3587 lines
103 KiB

<?php
/***************************************************************************\
* SPIP, Systeme de publication pour l'internet *
* *
* Copyright (c) 2001-2019 *
* Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James *
* *
* Ce programme est un logiciel libre distribue sous licence GNU/GPL. *
* Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne. *
\***************************************************************************/
10 years ago
/**
* Utilitaires indispensables autour du serveur Http.
*
* @package SPIP\Core\Utilitaires
**/
10 years ago
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
* Nom du dossier conteneur
* @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) {
static $echecs = array();
if (strlen($dossier) and substr($dossier, -1) != '/') {
$dossier .= '/';
}
$f = str_replace('/', '_', $dossier) . $nom;
if (function_exists($f)) {
return $f;
}
if (function_exists($g = $f . '_dist')) {
return $g;
}
if (isset($echecs[$f])) {
return $echecs[$f];
}
// 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)
// et inclure le fichier
if (!$inc = include_spip($dossier . ($d = strtolower($nom)))
// si le fichier truc/machin/nom.php n'existe pas,
// la fonction peut etre definie dans truc/machin.php qui regroupe plusieurs petites fonctions
and strlen(dirname($dossier)) and dirname($dossier) != '.'
) {
include_spip(substr($dossier, 0, -1));
}
if (function_exists($f)) {
return $f;
}
if (function_exists($g)) {
return $g;
}
if ($continue) {
return $echecs[$f] = false;
}
// Echec : message d'erreur
spip_log("fonction $nom ($f ou $g) indisponible" .
($inc ? "" : " (fichier $d absent de $dossier)"));
include_spip('inc/minipres');
echo minipres(_T('forum_titre_erreur'),
_T('fichier_introuvable', array('fichier' => '<b>' . spip_htmlentities($d) . '</b>')),
array('all_inline'=>true,'status'=>404));
exit;
}
/**
* 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 ? $crash : array());
$crash[$file] = true;
ecrire_meta('message_crash_plugins', serialize($crash));
return false;
}
/**
* 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)
*
* @uses find_in_path()
* @see include_spip()
* @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');
}
/**
* 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
*
* @param string $fonc
* Nom de la fonction appelée par le pipeline
* @param string|array $val
* Les paramètres du pipeline, son environnement
* @return string|array $val
* Les paramètres du pipeline modifiés
**/
function minipipe($fonc, &$val) {
// fonction
if (function_exists($fonc)) {
$val = call_user_func($fonc, $val);
} // Class::Methode
else {
if (preg_match("/^(\w*)::(\w*)$/S", $fonc, $regs)
and $methode = array($regs[1], $regs[2])
and is_callable($methode)
) {
$val = call_user_func($methode, $val);
} else {
spip_log("Erreur - '$fonc' non definie !");
}
}
return $val;
}
/**
* 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 null|string|array $val
* 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();
if (!($ok = @is_readable($charger))) {
spip_log("fichier $charger pas cree");
}
}
if ($ok) {
include_once $charger;
}
}
// appliquer notre fonction si elle existe
$fonc = 'execute_pipeline_' . strtolower($action);
if (function_exists($fonc)) {
$val = $fonc($val);
} // plantage ?
else {
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
if (is_array($val)
and count($val) == 2
and (array_key_exists('data', $val))
) {
$val = $val['data'];
}
return $val;
}
/**
10 years ago
* 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`
*
* 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'
*
10 years ago
* @example
* ```
* spip_log($message)
* spip_log($message, 'recherche')
* spip_log($message, _LOG_DEBUG)
* spip_log($message, 'recherche.'._LOG_DEBUG)
10 years ago
* ```
*
* @api
* @link http://programmer.spip.net/spip_log
* @uses inc_log_dist()
*
* @param string $message
10 years ago
* Message à loger
* @param string|int $name
*
10 years ago
* - 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
* paramètre est planté pour cause de compatibilité ascendante.
*/
function spip_log($message = null, $name = null) {
static $pre = array();
static $log;
preg_match('/^([a-z_]*)\.?(\d)?$/iS', (string)$name, $regs);
if (!isset($regs[1]) or !$logname = $regs[1]) {
$logname = null;
}
if (!isset($regs[2]) or !$niveau = $regs[2]) {
$niveau = _LOG_INFO;
}
if ($niveau <= (defined('_LOG_FILTRE_GRAVITE') ? _LOG_FILTRE_GRAVITE : _LOG_INFO_IMPORTANTE)) {
if (!$pre) {
$pre = array(
_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:'
);
$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
**/
function journal($phrase, $opt = array()) {
$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
* @param bool|array $c
* 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.
**/
function _request($var, $c = false) {
if (is_array($c)) {
return isset($c[$var]) ? $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...
if (defined('_AJAX')
and _AJAX
and isset($GLOBALS['meta']['charset'])
and $GLOBALS['meta']['charset'] != 'utf-8'
and is_string($a)
// check rapide mais pas fiable
and preg_match(',[\x80-\xFF],', $a)
// check fiable
and include_spip('inc/charsets')
and is_utf8($a)
) {
return importer_charset($a, 'utf-8');
}
return $a;
}
/**
* 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
5 years ago
* @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]);
if ($val !== null) {
$c[$var] = $val;
}
return $c;
}
unset($_GET[$var]);
unset($_POST[$var]);
if ($val !== null) {
$_GET[$var] = $val;
}
return false; # n'affecte pas $c
}
/**
* Tester si une URL est absolue
*
* On est sur le web, on exclut certains protocoles,
* notamment 'file://', 'php://' et d'autres…
* @param string $url
* @return bool
*/
function tester_url_absolue($url) {
$url = trim($url);
if (preg_match(";^([a-z]{3,7}:)?//;Uims", $url, $m)) {
if (
isset($m[1])
and $p = strtolower(rtrim($m[1], ':'))
and in_array($p, array('file', 'php', 'zlib', 'glob', 'phar', 'ssh2', 'rar', 'ogg', 'expect', 'zip'))
) {
return false;
}
return true;
}
return false;
}
/**
* Prend une URL et lui ajoute/retire un paramètre
*
* @filtre
* @link http://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 (strpos($c, "|") !== false and is_null($v)) {
return null;
}
// lever l'#ancre
if (preg_match(',^([^#]*)(#.*)$,', $url, $r)) {
$url = $r[1];
$ancre = $r[2];
} else {
$ancre = '';
}
// eclater
$url = preg_split(',[?]|&amp;|&,', $url);
// recuperer la base
$a = array_shift($url);
if (!$a) {
$a = './';
}
$regexp = ',^(' . str_replace('[]', '\[\]', $c) . '[[]?[]]?)(=.*)?$,';
$ajouts = array_flip(explode('|', $c));
$u = is_array($v) ? $v : rawurlencode($v);
$testv = (is_array($v) ? count($v) : strlen($v));
$v_read = null;
// lire les variables et agir
foreach ($url as $n => $val) {
if (preg_match($regexp, urldecode($val), $r)) {
$r = array_pad($r, 3, null);
if ($v === null) {
// c'est un tableau, on memorise les valeurs
if (substr($r[1], -2) == "[]") {
if (!$v_read) {
$v_read = array();
}
$v_read[] = $r[2] ? substr($r[2], 1) : '';
} // c'est un scalaire, on retourne direct
else {
return $r[2] ? substr($r[2], 1) : '';
}
} // suppression
elseif (!$testv) {
unset($url[$n]);
}
8 years ago
// Ajout. Pour une variable, remplacer au meme endroit,
// pour un tableau ce sera fait dans la prochaine boucle
elseif (substr($r[1], -2) != '[]') {
$url[$n] = $r[1] . '=' . $u;
unset($ajouts[$r[1]]);
}
8 years ago
// Pour les tableaux on laisse tomber les valeurs de
// départ, on remplira à l'étape suivante
else {
8 years ago
unset($url[$n]);
}
}
}
// traiter les parametres pas encore trouves
if ($v === null
and $args = func_get_args()
and count($args) == 2
) {
return $v_read; // rien trouve ou un tableau
} elseif ($testv) {
foreach ($ajouts as $k => $n) {
if (!is_array($v)) {
$url[] = $k . '=' . $u;
} else {
$id = (substr($k, -2) == '[]') ? $k : ($k . "[]");
foreach ($v as $w) {
$url[] = $id . '=' . (is_array($w) ? 'Array' : $w);
}
}
}
}
// eliminer les vides
$url = array_filter($url);
// recomposer l'adresse
if ($url) {
$a .= '?' . join($sep, $url);
}
return $a . $ancre;
}
/**
* 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()
* @param string $url
* @param string $ancre
* @return string
*/
function ancre_url($url, $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')) {
include_spip('inc/charsets');
}
$ancre = preg_replace(
array('/^[^-_a-zA-Z0-9]+/', '/[^-_a-zA-Z0-9]/'),
array('', '-'),
translitteration($ancre)
);
}
return $url . (strlen($ancre) ? '#' . $ancre : '');
}
/**
* Pour le nom du cache, les `types_urls` et `self`
*
* @param string|null $reset
* @return string
*/
function nettoyer_uri($reset = null) {
static $done = false;
static $propre = '';
if (!is_null($reset)) {
return $propre = $reset;
}
if ($done) {
return $propre;
}
$done = true;
return $propre = nettoyer_uri_var($GLOBALS['REQUEST_URI']);
}
/**