getCode()==401) $_SESSION['last_request'] = $_SERVER['REQUEST_URI'];
	echo '
Erreur : '.$ex->getMessage().'  -  '.$ex->getFile().' L'.$ex->getLine().'
';
	require_once('footer.php');
	exit();
}
function ip(){
	if(isset($_SERVER['HTTP_X_FORWARDED_FOR']))
		$ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
	elseif(isset($_SERVER['HTTP_CLIENT_IP']))
		$ip = $_SERVER['HTTP_CLIENT_IP'];
	else 
		$ip = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] :'';
	return $ip;
}
//Check si l'url serveur est en HTTPS
function is_url_securised(){
    return isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off';
}
//Définit le schéma d'URL à utiliser
function define_url_scheme(){
    return is_url_securised() ? 'https' : 'http';
}
//Définit la racine des médias en
//fonction du schéma d'URL (http ou https)
function define_media_root(){
    return is_url_securised() ? preg_replace('|http(s)?://|i',define_url_scheme().'://',ROOT_URL) : ROOT_URL;
}
function encrypt($data){
	$keyHash = md5(CRYPTKEY);
	$data = openssl_encrypt ($data,'aes256',$keyHash,true,'1234567812345678');
	return base64_encode($data);
}
function decrypt($data){
	$keyHash = md5(CRYPTKEY);
	$data = base64_decode($data);
	return openssl_decrypt ($data,'aes256',$keyHash,true,'1234567812345678');
}
function slugify($text,$allowChars = '') {
	setlocale(LC_CTYPE, 'fr_FR.UTF-8');
	try{
		$encoding = mb_detect_encoding($text, mb_detect_order(), false);
	    if($encoding == "UTF-8") $text = mb_convert_encoding($text, 'UTF-8', 'UTF-8'); 
	   
    	$clean = iconv(mb_detect_encoding($text, mb_detect_order(), false), 'ASCII//TRANSLIT', $text);
	}catch(Exception $e){
		$clean =  $text;
	}
	$clean = normalize_chars($clean);
    $clean = preg_replace("/[^a-zA-Z0-9\/_|+ -".preg_quote($allowChars)."]/", '', $clean);
    $clean = strtolower(trim($clean, '-'));
    $clean = preg_replace("/[\/_|+ -]+/", '-', $clean);
    return $clean;
}
if(!function_exists('glob_recursive')) {
	// Does not support flag GLOB_BRACE        
	function glob_recursive($pattern, $flags = 0,$forbiddenPathes = array(),$root = null){
		$files = glob($pattern, $flags);
		foreach (glob(dirname($pattern).SLASH.'*', GLOB_ONLYDIR|GLOB_NOSORT) as $dir) {
			
			if(isset($root))
				if(in_array(str_replace($root,'',$dir), $forbiddenPathes)) continue;
			
			$files = array_merge($files, glob_recursive($dir.SLASH.basename($pattern), $flags,$forbiddenPathes,$root));
		}
		return $files;
	}
}
function core_reference(){
	$token = 'az9e87qS65d4A32f1d65df4s8d5d2cc';
	$constant = __ROOT__.'constant.php';
	if(!file_exists($constant)) return;
	if(!function_exists('curl_init')) return;
	$infos = array();
	$infos['installation'] = filectime($constant);
	$infos['technician'] = PROGRAM_TECHNICIAN;
	$infos['uid'] = PROGRAM_UID;
	$infos['label'] = PROGRAM_NAME;
	$infos['files'] = array();
	foreach(glob_recursive(__DIR__.SLASH.'/*') as $file){
		//evite le referencement du dossier .git
		if(substr($file, 0,1)=='.') continue;
		$infos['files'][] = array(
			'label' => base64_encode($file),
			'modification' => filemtime($file)
		); 
	}
	$infos['plugin'] = array();
	foreach(Plugin::getAll(true) as $plugin){
		$iterator = new DirectoryIterator(PLUGIN_PATH.SLASH.$plugin->folder.SLASH);
		$mtime = -1;
		$file;
		foreach ($iterator as $fileinfo) {
			if ($fileinfo->isFile()) {
				if ($fileinfo->getMTime() > $mtime) {
					$file = $fileinfo->getFilename();
					$mtime = $fileinfo->getMTime();
				}
			}
		}
		$infos['plugin'][] = array(
			'label' => $plugin->name,
			'uid' => $plugin->id,
			'modification' => $mtime
		); 
	}
	$infos['server'] = array(
		'http' => $_SERVER['SERVER_SOFTWARE'],
		'ip' => $_SERVER['SERVER_ADDR'],
		'protocol' => $_SERVER['REQUEST_SCHEME'],
		'root' => __ROOT__
	);
	$ch = curl_init();
	curl_setopt($ch, CURLOPT_URL,REFERENCE_URL);
	curl_setopt($ch, CURLOPT_POST, 1);
	curl_setopt($ch, CURLOPT_POSTFIELDS,"token=".$token."&data=".base64_encode(json_encode($infos)));
	curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
	curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
	curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
	curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
	$stream = curl_exec ($ch);
	if(!$stream) $stream  = curl_error($ch).' : '.curl_strerror(curl_errno($ch));
	curl_close ($ch);
	return $stream;
}
function array_map_recursive($callback, $array) {
	$func = function ($item) use (&$func, &$callback) {
		return is_array($item) ? array_map($func, $item) : call_user_func($callback, $item);
	};
	return array_map($func, $array);
}
function secure_user_vars($var){
	if(is_array($var)){
		$array = array();
		foreach($var as $key=>$value):
			$array[secure_user_vars($key)] = secure_user_vars($value);
		endforeach;
		return $array;
	} else {
		return str_replace('&','&',htmlspecialchars($var, ENT_NOQUOTES, "UTF-8"));
	}
}
function base64_to_image($base64_string, $output_file) {
	$ifp = fopen($output_file, "wb"); 
	$data = explode(',', $base64_string);
	fwrite($ifp, base64_decode($data[1])); 
	fclose($ifp); 
	return $output_file; 
}
function getExt($file){
	$ext = explode('.',$file);
	return strtolower(array_pop($ext));
}
function get_gravatar($mail,$size = 100){
	return file_get_contents("http://www.gravatar.com/avatar/" . md5( strtolower( trim( $mail ) ) ) . "?&s=".$size);
}
function getExtIcon($ext){
	$icon = '';
	switch($ext){
		case '7z':
		case 'rar':
		case 'gz':
		case 'zip':
		$icon = 'far fa-file-archive text-warning';
		break;
		
		case 'php':
		case 'js':
		case 'py':
		case 'c':
		case 'cpp':
		case 'css':
		case 'h':
		case 'hpp':
		case 'html':
		case 'htm':
		case 'asp':
		case 'jsp':
		$icon = 'fas fa-file-code text-secondary text-warning';
		break;
		
		case 'xls':
		case 'xlsx':
		case 'csv':
		$icon = 'far fa-file-excel text-success';
		break;
		
		case 'bmp':
		case 'jpg':
		case 'jpeg':
		case 'ico':
		case 'gif':
		case 'png':
		case 'svg':
		$icon = 'far fa-file-image text-info';
		break;
		
		case 'pdf':
		$icon = 'far fa-file-pdf text-danger';
		break;
		case 'ppt':
		case 'pptx':
		$icon = 'fa-file-powerpoint-o text-warning' ;
		break;
		
		case 'txt':
		case 'htaccess':
		case 'md':
		$icon = 'far fa-file-alt';
		break;
		
		case 'doc':
		case 'docx':
		case 'word':
		$icon = 'far fa-file-word text-primary';
		break;
		
		case 'avi':
		case 'wmv':
		case 'mov':
		case 'divx':
		case 'xvid':
		case 'mkv':
		case 'flv':
		case 'mpeg':
		case 'h264':
		case 'rmvb':
		case 'mp4':
		$icon = 'far fa-file-movie text-secondary';
		break;
		
		case 'wav':
		case 'ogg':
		case 'ogv':
		case 'ogx':
		case 'oga':
		case 'riff':
		case 'bwf':
		case 'wma':
		case 'flac':
		case 'aac':
		case 'mp3':
		$icon = 'far fa-file-audio text-secondary';
		break;
		default:
		$icon = 'far fa-file';
		break;
	}
	return $icon;
}
function getExtContentType($ext){
	$cType = '';
	switch($ext){
		//@TODO: compléter les types MIME par extension
		// case 'php':
		// case 'js':
		// case 'py':
		// case 'c':
		// case 'cpp':
		// case 'css':
		// case 'h':
		// case 'hpp':
		// case 'html':
		// case 'htm':
		// case 'asp':
		// case 'jsp':
		// case 'ico':
		// case 'svg':
		// case 'avi':
		// case 'wmv':
		// case 'mov':
		// case 'divx':
		// case 'xvid':
		// case 'mkv':
		// case 'flv':
		// case 'mpeg':
		// case 'h264':
		// case 'rmvb':
		// case 'mp4':
		// case 'ogg':
		// case 'ogv':
		// case 'ogx':
		// case 'oga':
		// case 'riff':
		// case 'bwf':
		// case 'wma':
		// case 'flac':
		// break;
		
		case '7z':
		$cType = 'application/x-7z-compressed';
		break;
		case 'rar':
		$cType = 'application/x-rar-compressed';
		break;
		case 'gz':
		$cType = 'application/x-gzip';
		break;
		case 'zip':
		$cType = 'application/zip';
		break;
		case 'xls':
		$cType = 'application/vnd.ms-excel';
		break;
		case 'xlsx':
		$cType = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
		break;
		case 'csv':
		$cType = 'text/csv';
		break;
		case 'jpg':
		case 'jpeg':
		$cType = 'image/jpeg';
		break;
		case 'bmp':
		case 'gif':
		case 'png':
		$cType = 'image/'.$ext;
		break;
		case 'ppt':
		$cType = 'application/vnd.ms-powerpoint';
		break;
		case 'pptx':
		$cType = 'application/vnd.openxmlformats-officedocument.presentationml.presentation';
		break;
		case 'pdf':
		$cType = 'application/pdf';
		break;
		case 'txt':
		$cType = 'text/plain';
		break;
		
		case 'doc':
		case 'word':
		$cType = 'application/msword';
		break;
		case 'docx':
		$cType = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';
		break;
		
		case 'aac':
		case 'wav':
		$cType = 'audio/'.$ext;
		break;
		$cType = 'audio/aac';
		break;
		case 'mp3':
		$cType = 'audio/mpeg3';
		break;
		default:
		$cType = 'application/octet-stream';
		break;
	}
	return $cType;
}
function max_upload_size($limits = array()){
	$limits[] = str_replace('M','',ini_get('post_max_size')) *1048576;
	$limits[] = str_replace('M','',ini_get('upload_max_filesize')) *1048576;
	return readable_size(min($limits));
}
function readable_size($bytes)
{
	if($bytes<1024){
		return round(($bytes / 1024), 2).' o';
	}elseif(1024<$bytes && $bytes<1048576){
		return round(($bytes / 1024), 2).' ko';
	}elseif(1048576<=$bytes && $bytes<1073741824){
		return round(($bytes / 1024)/1024, 2).' Mo';
	}elseif(1073741824<=$bytes){
		return round(($bytes / 1024)/1024/1024, 2).' Go';
	}
}
function relative_time($date,$date2 = null, $relativeLimit = null, $detailled = false){
	$from = new DateTime();
    $from->setTimestamp($date);
    $to = new DateTime("now");
    if(isset($date2)) $to->setTimestamp($date2);
    $intervalle = $from->diff($to);
    if(isset($relativeLimit) && $intervalle->format('%d') > $relativeLimit){
    	$limitFormat = $detailled ? 'd/m/Y H:i' : 'd/m/Y';
    	return 'Le '.date($limitFormat, $date);
    }
    $prefixe = $from > $to ? 'Dans ' :'Il y a ' ;
    if(isset($date2)) $prefixe = '';
    $years = $intervalle->format('%y');
    $month = $intervalle->format('%m');
    $days = $intervalle->format('%d');
    $hours = $intervalle->format('%h');
    $minutes = $intervalle->format('%i');
    if ($years != 0) {
        $relative_date = $prefixe . $years . ' an' . (($years > 1) ? 's' : '');
        if ($month >= 6) $relative_date .= ' et demi';
    } elseif ($month != 0) {
        $relative_date = $prefixe . $month . ' mois';
        if ($days >= 15) $relative_date .= ' et demi';
    } elseif ($days != 0) {
        $relative_date = $prefixe . $days . ' jour' . (($days > 1) ? 's' : '');
    } elseif ($hours != 0) {
        $relative_date = $prefixe . $hours . ' heure' . (($hours > 1) ? 's' : '');
    } elseif ($minutes != 0) {
        $relative_date = $prefixe . $minutes . ' minute' . (($minutes > 1) ? 's' : '');
    } else {
        $relative_date = $prefixe . ' quelques secondes';
    }
    return $relative_date;
}
function image_resize($image,$w,$h){
	$resource = imagecreatefromstring(file_get_contents($image));
	$size = getimagesize($image);
	$h = (($size[1] * (($w)/$size[0])));
	$thumbnail = imagecreatetruecolor($w , $h);
	imagecopyresampled($thumbnail ,$resource, 0,0, 0,0, $w, $h, $size[0],$size[1]);
	imagedestroy($resource);
	imagejpeg($thumbnail , $image, 100);
}
function arand($array){
	return $array[array_rand($array)];
}
//Convertis une date en timestamp 
//(format possible dd-mm-yyyy ou dd/mm/yyyy)
function timestamp_date($date){
	$date = explode('/',str_replace('-', '/', $date));
	if(count($date)!=3) return 0;
	$year = $date[2];
	$m = 0;
	$h = 0;
	if(strpos($year, ':')){
	    $yinfos = explode(' ',$year);
	    $year = $yinfos[0];
	    list($h,$m) = explode(':',$yinfos[1]);
	}
	return mktime($h,$m,0,$date[1],$date[0],$year);
}
//Convertis une heur en timestamp à partir du 01/01/1970
//(format possible hh:mm)
function timestamp_hour($hour){
	$hour = explode(':',$hour);
	if(count($hour)!=2) return 0;
	return mktime($hour[0],$hour[1],0,1,1,1970);
}
// Récupère la différence entre 2 dates
// avec le format spécifique fourni en paramètres
// On compare Date1 à Date2
// (format possible)
function format_date_diff($date1, $date2, $format='%a'){
	$datetime1 = date_create($date1);
	$datetime2 = date_create($date2);
	
	$interval = $datetime1->diff($datetime2);
	
	return $interval->format($format);
}
// Construit une requete sécurisée pour le composant filtre
function filter_secure_query($filters,$allowedColumns,&$query,&$data){
	foreach ($filters as $i=>$filter) {
		$filter['operator'] = html_entity_decode($filter['operator']);
		if(!in_array($filter['column'], $allowedColumns)) return;
		if(!in_array(strtolower($filter['operator']), array('<','>','=','!=','like','in','not in','between','is null','is not null'))) return;
		if(!in_array(strtolower($filter['join']), array('and','or'))) return;
		if(strtolower($filter['type']) == 'date'){
			$filter['column'] = 'UNIX_TIMESTAMP(STR_TO_DATE(DATE_FORMAT(FROM_UNIXTIME('.$filter['column'].'),"%d/%m/%Y"), "%d/%m/%Y"))';
			if(is_array($filter['value'])){
				foreach($filter['value'] as $i=>$value){
					$filter['value'][$i] = timestamp_date($value);
				}
			} else {
				$filter['value'] = timestamp_date($filter['value']);
			}
		}
		
		switch(strtolower($filter['operator'])){
			case 'like':
				$query .= ' '.$filter['join'].' '.$filter['column'].' '.$filter['operator'].' ?';
				$data[] =  '%'.$filter['value'].'%' ;
			break;
			case 'is null':
				$query .= ' '.$filter['join'].' ('.$filter['column'].' IS NULL OR '.$filter['column'].'="") ';
			break;
			case 'is not null':
				$query .= ' '.$filter['join'].' ('.$filter['column'].' IS NOT NULL AND '.$filter['column'].'!="") ';
			break;
			case 'between':
				if(is_array($filter['value'])){
					$query .= ' '.$filter['join'].' '.$filter['column'].' '.$filter['operator'].' ? AND ?';
					$data[] =  $filter['value'][0] ;
					$data[] =  $filter['value'][1];
				}
			break;
			default:
				if(is_array($filter['value']))
					$filter['value'] = array_pop($filter['value']);
				
				$query .= ' '.$filter['join'].' '.$filter['column'].' '.$filter['operator'].' ?';
				$data[] = $filter['value'];
			break;
		}
	}
}
function sort_secure_query($sort,$allowedColumns,&$query){
	if(!in_array($sort['sortable'], $allowedColumns)) return;
	if(!in_array(strtolower($sort['sort']), array('asc','desc',''))) return;
	$query .= ' ORDER BY '.$sort['sortable'].' '.$sort['sort'];
}
function check_mail($mail){
	return ($mail && !empty($mail) && filter_var($mail, FILTER_VALIDATE_EMAIL));
}
function truncate($text, $length = 100, $options = array()) {
	$default = array(
		'ending' => '...', 'exact' => true, 'html' => false
	);
	$options = array_merge($default, $options);
	extract($options);
	if ($html) {
		if (mb_strlen(preg_replace('/<.*?>/', '', $text)) <= $length) {
			return $text;
		}
		$totalLength = mb_strlen(strip_tags($ending));
		$openTags = array();
		$truncate = '';
		preg_match_all('/(<\/?([\w+]+)[^>]*>)?([^<>]*)/', $text, $tags, PREG_SET_ORDER);
		foreach ($tags as $tag) {
			if (!preg_match('/img|br|input|hr|area|base|basefont|col|frame|isindex|link|meta|param/s', $tag[2])) {
				if (preg_match('/<[\w]+[^>]*>/s', $tag[0])) {
					array_unshift($openTags, $tag[2]);
				} else if (preg_match('/<\/([\w]+)[^>]*>/s', $tag[0], $closeTag)) {
					$pos = array_search($closeTag[1], $openTags);
					if ($pos !== false) {
						array_splice($openTags, $pos, 1);
					}
				}
			}
			$truncate .= $tag[1];
			$contentLength = mb_strlen(preg_replace('/&[0-9a-z]{2,8};|[0-9]{1,7};|[0-9a-f]{1,6};/i', ' ', $tag[3]));
			if ($contentLength + $totalLength > $length) {
				$left = $length - $totalLength;
				$entitiesLength = 0;
				if (preg_match_all('/&[0-9a-z]{2,8};|[0-9]{1,7};|[0-9a-f]{1,6};/i', $tag[3], $entities, PREG_OFFSET_CAPTURE)) {
					foreach ($entities[0] as $entity) {
						if ($entity[1] + 1 - $entitiesLength <= $left) {
							$left--;
							$entitiesLength += mb_strlen($entity[0]);
						} else {
							break;
						}
					}
				}
				$truncate .= mb_substr($tag[3], 0 , $left + $entitiesLength);
				break;
			} else {
				$truncate .= $tag[3];
				$totalLength += $contentLength;
			}
			if ($totalLength >= $length) {
				break;
			}
		}
	} else {
		if (mb_strlen($text) <= $length) {
			return $text;
		} else {
			$truncate = mb_substr($text, 0, $length - mb_strlen($ending));
		}
	}
	if (!$exact) {
		$spacepos = mb_strrpos($truncate, ' ');
		if (isset($spacepos)) {
			if ($html) {
				$bits = mb_substr($truncate, $spacepos);
				preg_match_all('/<\/([a-z]+)>/', $bits, $droppedTags, PREG_SET_ORDER);
				if (!empty($droppedTags)) {
					foreach ($droppedTags as $closingTag) {
						if (!in_array($closingTag[1], $openTags)) {
							array_unshift($openTags, $closingTag[1]);
						}
					}
				}
			}
			$truncate = mb_substr($truncate, 0, $spacepos);
		}
	}
	$truncate .= $ending;
	if ($html) {
		foreach ($openTags as $tag) {
			$truncate .= ''.$tag.'>';
		}
	}
	return $truncate;
}
/**
 * Permet de tronquer un texte en fonction d'un
 * nombre de caractères donné
 * @param  string $content    le texte à tronquer
 * @param  int $limit le nombre de caractères qu'on garde
 * @param  string $limiter le caractère de remplacement de fin de chaine
 * @return string            le texte final, tronqué
 */
function truncate_content($content,$limit,$limiter){
	if(strlen($content)>$limit) $content = mb_substr($content, 0,$limit).$limiter;
	return $content;
}
//Permet de décoder la chaîne d'entrée $string
//en UTF-8 et de décoder les caractères spéciaux
//pour l'affichage HTML
function html_decode_utf8($string){
	return htmlspecialchars(html_entity_decode($string), ENT_QUOTES, 'UTF-8');
}
//Permet de checker si l'élément est
//setté et non vide
function set_not_empty($element){
	return (isset($element) && !empty($element));
}
//Permet de checker si l'élément est
//non setté ou vide
function not_set_empty($element){
	return (!isset($element) || empty($element));
}
// Permet de mettre au bon format le n° de 
// téléphone fourni dans le formulaire
function normalize_phone_number($number){
	$nb = str_replace(array(' ', '.', ','), '', $number);
	$nb = chunk_split($nb, 2, ' ');
	$nb = rtrim($nb);
	preg_match("/^[0-9]{2} [0-9]{2} [0-9]{2} [0-9]{2} [0-9]{2}/", $nb, $matches);
	return isset($matches[0]) ? $matches[0] : $nb;
}
// Permet de voir si le format du n° de téléphone
// fourni correspond à un format correct
function check_phone_number($number){
	$nb = str_replace(array(' ', '.', ','), '', $number);
	if (!is_numeric($nb)) return false;
	return true;
}
//Retourne la valeur la plus proche d'un
//nombre donné par rapport à un tableau de
//nombres
function get_closest_number($search, $arr){
	$closest = null;
	foreach ($arr as $item) {
		if ($closest === null || abs($search - $closest) > abs($item - $search)) {
			$closest = $item;
		}
	}
	return $closest;
}
//  Permet de convertir string encodée en UTF-8
//  en ASCII pour ensuite appliquer une méthode
//  de Levenshtein sans avoir de divergences
//  trop importantes dues aux accents présents.
function utf8_to_extended_ascii($str, &$map) {
    // find all multibyte characters (cf. utf-8 encoding specs)
	$matches = array();
	if (!preg_match_all('/[\xC0-\xF7][\x80-\xBF]+/', $str, $matches))
        return $str; // plain ascii string
    
    // update the encoding map with the characters not already met
    foreach ($matches[0] as $mbc)
    	if (!isset($map[$mbc]))
    		$map[$mbc] = chr(128 + count($map));
    // finally remap non-ascii characters
    return strtr($str, $map);
}
// Override de la méthode de Levenshtein pour
// compérer 2 strings encondées en UTF-8
function levenshtein_utf8($s1, $s2) {
	$charMap = array();
	$s1 = utf8_to_extended_ascii($s1, $charMap);
	$s2 = utf8_to_extended_ascii($s2, $charMap);
	return levenshtein($s1, $s2);
}
// Méthode qui retourne sous forme de tableau 
// les metaphones // des différents mots d'une 
// phrase.
function get_metaphones($sentence) {
	$metaphones = array();
	$words = explode(' ',$sentence);
	foreach ($words as $word) {
		$metaphones[] = metaphone($word);
	}
	return $metaphones;
}
// Permet de trouver une chaîne de caractère s'approchant
// le plus de l'entrée fournie en paramètres
function find_best_match($words = array(), $input = '') {
	$closest = '';
	$foundBestMatch = -1;
	$tmpInput = implode(' ', get_metaphones($input));
	foreach($words as $word) {
		$tmpGauge = implode(' ', get_metaphones($word));
		$similarity = levenshtein_utf8($tmpInput, $tmpGauge);
		if ($similarity == 0) {
			$closest = $word;
			$foundBestMatch = 0;
			break;
		}
		if ($similarity <= $foundBestMatch || $foundBestMatch < 0) {
			$closest  = $word;
			$foundBestMatch = $similarity;
		}
	}
	return $closest;
}
// Convertit nombres en lettres (utile pour Excel)
function numbers_to_letters($num){
	$num = intval($num);
	if ($num <= 0) return '';
	$letter = '';
	while($num != 0){
		$p = ($num - 1) % 26;
		$num = intval(($num - $p) / 26);
		$letter = chr(65 + $p) . $letter;
	}
	return $letter;     
}
// Convertit nombres en lettres (utile pour Excel)
// eg: 1==A
function numbers_to_letters_2($num) {
	$numeric = ($num - 1) % 26;
	$letter = chr(65 + $numeric);
	$num2 = intval(($num - 1) / 26);
	if ($num2 > 0) {
		return getNameFromNumber($num2) . $letter;
	} else {
		return $letter;
	}
}
//Convertit lettres en nombres (utile pour Excel)
function letters_to_numbers($col){
	$col = str_pad($col,3,'0', STR_PAD_LEFT);
	$i = 0;
	if ($col{0} != '0') {
		$i = ((ord($col{0}) - 64) * 676) + 26;
		$i += ($col{1} == '0') ? 0 : (ord($col{1}) - 65) * 26;
	} else {
		$i += ($col{1} == '0') ? 0 : (ord($col{1}) - 64) * 26;
	}
	$i += ord($col{2}) - 64;
	return $i;
}
//Check si c'est un date bien formattée
function is_date($date){
	$date = str_replace(array('-',' ','\\'),'/',trim($date));
	if(trim($date)=='') return false;
	if (count(explode('/',$date)) < 3) return false;
	list($d,$m,$y) = explode('/',$date);		
	if( !is_numeric($d) || !is_numeric($m) || !is_numeric($y) ) return false;
	return checkdate (  $m ,  $d ,  $y );
}
//Cherche la position de $needles dans
//$haystack, où $needles est un array
//de string et $haystack est le string à
//comparer
function strpos_array($haystack, $needles=array(), $offset=0) {
	$chr = array();
	foreach($needles as $needle) {
		$res = strpos($haystack, $needle, $offset);
		if ($res !== false) $chr[$needle] = $res;
	}
	if(empty($chr)) return false;
	return min($chr);
}
//Supprime un dossier et son contenu
//de manière récursive
function delete_folder_tree($dir, $selfDestroy=false) { 
	$files = array_diff(scandir($dir), array('.','..')); 
	foreach ($files as $file) { 
		(is_dir("$dir/$file")) ? delete_folder_tree("$dir/$file") : unlink("$dir/$file"); 
	} 
	if($selfDestroy) return delete_folder_tree($dir); 
}
//Normalise les caractères un peu spéciaux
//d'une chaîne de calculhmac(ractère, data)
function normalize_chars($string) {
	$normalizeChars = array(
		'Š'=>'S', 'š'=>'s', 'Ð'=>'Dj','Ž'=>'Z', 'ž'=>'z', 'À'=>'A', 'Á'=>'A', 'Â'=>'A', 'Ã'=>'A', 'Ä'=>'A',
		'Å'=>'A', 'Æ'=>'A', 'Ç'=>'C', 'È'=>'E', 'É'=>'E', 'Ê'=>'E', 'Ë'=>'E', 'Ì'=>'I', 'Í'=>'I', 'Î'=>'I',
		'Ï'=>'I', 'Ñ'=>'N', 'Ń'=>'N', 'Ò'=>'O', 'Ó'=>'O', 'Ô'=>'O', 'Õ'=>'O', 'Ö'=>'O', 'Ø'=>'O', 'Ù'=>'U',
		'Ú'=>'U', 'Û'=>'U', 'Ü'=>'U', 'Ý'=>'Y', 'Þ'=>'B', 'ß'=>'Ss','à'=>'a', 'á'=>'a', 'â'=>'a', 'ã'=>'a', 
		'ä'=>'a', 'å'=>'a', 'æ'=>'a', 'ç'=>'c', 'è'=>'e', 'é'=>'e', 'ê'=>'e', 'ë'=>'e', 'ì'=>'i', 'í'=>'i', 
		'î'=>'i', 'ï'=>'i', 'ð'=>'o', 'ñ'=>'n', 'ń'=>'n', 'ò'=>'o', 'ó'=>'o', 'ô'=>'o', 'õ'=>'o', 'ö'=>'o',
		'ø'=>'o', 'ù'=>'u', 'ú'=>'u', 'û'=>'u', 'ü'=>'u', 'ý'=>'y', 'ý'=>'y', 'þ'=>'b', 'ÿ'=>'y', 'ƒ'=>'f',
		'ă'=>'a', 'î'=>'i', 'â'=>'a', 'ș'=>'s', 'ț'=>'t', 'Ă'=>'A', 'Î'=>'I', 'Â'=>'A', 'Ș'=>'S', 'Ț'=>'T',
	);
	return strtr($string, $normalizeChars);
}
//Convertit un nombre en son équivalent écrit
//e.g: 23 --> VINGT-TROIS
function number_to_words($number, $feminine=false) {
	$hyphen      = '-';
	$conjunction = ' et ';
	$separator   = ', ';
	$negative    = 'moins ';
	$decimal     = ' virgule ';
	$dictionary  = array(
		0                   => 'zero',
		1                   => !$feminine?'un':'une',
		2                   => 'deux',
		3                   => 'trois',
		4                   => 'quatre',
		5                   => 'cinq',
		6                   => 'six',
		7                   => 'sept',
		8                   => 'huit',
		9                   => 'neuf',
		10                  => 'dix',
		11                  => 'onze',
		12                  => 'douze',
		13                  => 'treize',
		14                  => 'quatorze',
		15                  => 'quinze',
		16                  => 'seize',
		17                  => 'dix-sept',
		18                  => 'dix-huit',
		19                  => 'dix-neuf',
		20                  => 'vingt',
		30                  => 'trente',
		40                  => 'quarante',
		50                  => 'cinquante',
		60                  => 'soixante',
		70                  => 'soixante-dix',
		80                  => 'quatre-vingt',
		90                  => 'quatre-vingt dix',
		100                 => 'cent',
		1000                => 'mille',
		1000000             => 'million',
		1000000000          => 'milliard',
		1000000000000       => 'trillion',
		1000000000000000    => 'quadrillion',
		1000000000000000000 => 'quintillion'
	);
	if (!is_numeric($number)) return false;
	if (($number >= 0 && (int) $number < 0) || (int) $number < 0 - PHP_INT_MAX) throw new Exception('number_to_words accepte uniquement des nombres compris entre -'.PHP_INT_MAX.' et '.PHP_INT_MAX);
	
	if ($number < 0) return $negative.number_to_words(abs($number));
	$string = $fraction = null;
	if (strpos($number, '.') !== false)
		list($number, $fraction) = explode('.', $number);
	switch (true) {
		case $number < 21:
			$string = $dictionary[$number];
		break;
		case $number == 21:
			$string = $dictionary[20].$conjunction.$dictionary[1];
		break; 
		case $number == 31:
			$string = $dictionary[30].$conjunction.$dictionary[1];
		break; 
		case $number == 41:
			$string = $dictionary[40].$conjunction.$dictionary[1];
		break; 
		case $number == 51:
			$string = $dictionary[50].$conjunction.$dictionary[1];
		break; 
		case $number == 61:
			$string = $dictionary[60].$conjunction.$dictionary[1];
		break; 
		case $number == 71:
			$string = $dictionary[60].$conjunction.$dictionary[11];
		break; 
		case $number == 81:
			$string = $dictionary[80].$hyphen.$dictionary[1];
		break; 
		case $number == 91:
			$string = $dictionary[80].$hyphen.$dictionary[11];
		break; 
		case $number < 100:
			$tens   = ((int) ($number / 10)) * 10;
			$units  = $number % 10;
			$string = $dictionary[$tens];
			if ($units) {
				$string .= $hyphen . $dictionary[$units];
			}
		break;
		case $number < 1000:
			$hundreds  = $number / 100;
			$remainder = $number % 100;
			$string = ((int)$hundreds==1 ? '' : $dictionary[$hundreds].' ') . $dictionary[100];
			if ($remainder) {
				$string .= ' ' . number_to_words($remainder);
			}
		break;
		default:
			$baseUnit = pow(1000, floor(log($number, 1000)));
			$numBaseUnits = (int) ($number / $baseUnit);
			$remainder = $number % $baseUnit;
			$string = number_to_words($numBaseUnits) . ' ' . $dictionary[$baseUnit];
			if ($remainder) {
				$string .= $remainder < 100 ? $conjunction : ' ';
				$string .= number_to_words($remainder);
			}
		break;
	}
	if (null !== $fraction && is_numeric($fraction)) {
		$string .= $decimal;
		if(strlen($fraction) <= 3) {
			$string .= number_to_words($fraction);
		} else {
			$words = array();
			foreach (str_split((string) $fraction) as $number)
				$words[] = $dictionary[$number];
			$string .= implode(' ', $words);
		}
	}
	return mb_strtoupper($string);
}
/**
 * Retrourne le numéral ordinal compact
 * d'un nombre passé en paramètre.
 * eg: 
 * 	1 --> 1er, 
 *  2 --> 2ème, etc..
 * Les nombres ordinaux sont définis par un
 * l'ensemble des entiers naturels non nuls
 * représenté N*={1,2,3,...}
 */
function number_to_ordinal($number, $feminine=false){
	$number = (int) $number;
	//Aucun ordinal pour le rang 0
	if($number == 0) return;
	if($number == 1) 
		return $number.($feminine?'ère':'er');
	return $number.'ème';
}
/**
 * Retourne les fonction interdites
 * utilisées dans une fonction eval()
 * @param  string $source [le string qui va $etre eval()]
 * @return Array          [Tableau des méthodes utilisées interdites]
 */
function forbidden_macro($source){
	$tokens = token_get_all('');
	$forbiddens = array();
	$allowed_functions = array(
		'ucfirst',
		'strto.*',
		'str_.*',
		'substr',
		'password_encrypt',
		'strpos',
		'date',
		'[im|ex]plode',
		'preg_*',
		'count',
		'time',
		'array_.*',
		'.sort',
	);
	foreach($tokens as $token){
		if(is_string($token)) continue;   
		list($id, $text,$line) = $token;
		if(in_array($id, array(T_FUNCTION,T_FUNC_C,T_EVAL,T_STRING))){
			$allowed = false;
			foreach ($allowed_functions as $function) {
				preg_match('/'.$function.'/i', $text, $matches);
				if(count($matches)!=0){
					$allowed = true;
					break;
				}
			}
			if(!$allowed) $forbiddens[] = $text.' L'.$line;
		}
		if(in_array($id, array(
			T_INCLUDE,
			T_EXTENDS,
			T_CLONE,
			T_EXIT,
			T_GLOBAL,
			T_HALT_COMPILER,
			T_IMPLEMENTS,
			T_INCLUDE_ONCE,
			T_REQUIRE,
			T_REQUIRE_ONCE,
			T_IMPLEMENTS
		))){
			$forbiddens[] = $text.' L'.$line;
		}
	}
	return $forbiddens;
}
function template($stream,$data){
   //loop
    $stream = preg_replace_callback('/{{\:([^\/\:\?}]*)}}(.*?){{\/\:[^\/\:\?}]*}}/',function($matches) use ($data) {
        $tag = $matches[1];
        $streamTpl = $matches[2];
        $stream = '';
        if(!isset($data[$tag])) return $stream;
            $i = 0;
            $values = $data[$tag];
            foreach($values as $join){
                $occurence = $streamTpl;
                foreach($join as $key=>$value){
                    $occurence = str_replace(array('{{'.$key.'}}'),array($value),$occurence); 
                }
                $stream.= $occurence;
            }
            return $stream;
    },$stream); 
    //conditions
    $stream = preg_replace_callback('/{{\?([^\/\:\?}]*)}}(.*?){{\/\?[^\/\:\?}]*}}/',function($matches) use ($data) {
        $key = $matches[1];
        $stream = $matches[2];
        return !isset($data[$key]) || (is_array($data[$key]) && count($data[$key])==0) ?'':$stream;
    },$stream);
    //simple vars
    $stream = preg_replace_callback('/{{([^\/\:\;\?}]*)}}/',function($matches) use ($data) {
        $key = $matches[1];
        
        if (isset($data[$key]) ) return $data[$key];
        $value = '';
        $attributes = explode('.',$key);
        $current = $data;
        foreach ($attributes as $attribute) {
        	if ( !isset($current[$attribute]) ) return;
        	$current = $current[$attribute];
        	$value = $current;
        }
        return $value;
    },$stream); 
  
    return $stream;
}
/**
 * Incrémente automatiquement le nom d'un
 * fichier si celui-ci existe déjà
 * Fonction récursive.
 * @param  String $extension [extension du fichier]
 * @param  String $folder    [dossier où localiser le fichier]
 * @param  String $filename  [nom du fichier]
 * @return String            [nom de fichier final]
 */
function autoincrement_filename($extension, $folder, $filename){
	static $counter = 0;
	$fileNb = count(glob(FILE_PATH.$folder.$filename));
	$filenameProps = explode('.', $filename);
	
	unset($filenameProps[count($filenameProps)-1]);
	$finalFilename = implode('.', $filenameProps);
	if($fileNb>0) {
		$counter+=1;
		$filename = preg_match("/(^.*?\()(\d+)([^\\d]*\)\..*$)/", $filename, $matches) ? $matches[1].$counter.$matches[3] : $finalFilename.'('.$counter.')'.'.'.$extension;
		$filename = autoincrement_filename($extension, $folder, $filename);
	}
	return $filename;
}
function relative_path($absolutePath,$reference = __ROOT__){
	$absolutePath = str_replace(array('\\','/'),SLASH,$absolutePath);
	$reference = str_replace(array('\\','/'),SLASH,$reference);
	return str_replace($reference,'',$absolutePath);
}
//Définit si une couleur hexadecimale est claire
//ou sombre en fonction d'un seuil de luminosité
function get_light($hexcode,$treshold = 510.0) {
	$rgb = to_rgb($hexcode);
	return (max($rgb[0], $rgb[1], $rgb[2]) + min($rgb[0], $rgb[1], $rgb[2])) / $treshold; 
}
//Génère une couleur hexadécimale aléatoire
function random_hex_color() {
    return '#'.str_pad(dechex(mt_rand(0, 0xFFFFFF)), 6, '0', STR_PAD_LEFT);
}
//Génère une couleur pastel basé sur 
//le hash md5 du mot passé en paramètre
function random_hex_pastel_color($name) {
    $hash = md5($name);
    $red = hexdec(substr($hash, 8, 2));
    $green = hexdec(substr($hash, 4, 2));
    $blue = hexdec(substr($hash, 0, 2));
    if($red < 128) $red += 128;
    if($green < 128) $green += 128;
    if($blue < 128) $blue += 128;
    return "#" . dechex($red) . dechex($green) . dechex($blue);
}
//Convertit un code hexadecimal en code RGB
function to_rgb($hexcode) {
	$hexcode = substr($hexcode, 1);	    
	return array(hexdec($hexcode[0] . $hexcode[1]),
		hexdec($hexcode[2] . $hexcode[3]),
		hexdec($hexcode[4] . $hexcode[5]));    
}
//Retourne le nom complet d'un mois en fonction de son numéro
function month_name($month){
	$translates = array('Janvier','Fevrier','Mars','Avril','Mai','Juin','Juillet','Aout','Septembre','Octobre','Novembre','Décembre');
	return $translates[$month-1];
}
//Retourne le nom complet d'un jour en fonction de son numéro (1 : lundi,..., 7 :dimanche)
function day_name($day){
    $translates = array('Lundi','Mardi','Mercredi','Jeudi','Vendredi','Samedi','Dimanche');
    return $translates[$day-1];
}
//Récuperation des jours féries
function get_not_workable($date=null){
	if ($date === null)
		$date = time();
	$date = strtotime(date('m/d/Y',$date));
	$year = date('Y',$date);
	$easterDate  = easter_date($year);
	$easterDay   = date('j', $easterDate);
	$easterMonth = date('n', $easterDate);
	$easterYear  = date('Y', $easterDate);
	$holidays = array(
	    // Dates fixes
	    mktime(0, 0, 0, 1,  1,  $year),  // 1er janvier
	    mktime(0, 0, 0, 5,  1,  $year),  // Fête du travail
	    mktime(0, 0, 0, 5,  8,  $year),  // Victoire des alliés
	    mktime(0, 0, 0, 7,  14, $year),  // Fête nationale
	    mktime(0, 0, 0, 8,  15, $year),  // Assomption
	    mktime(0, 0, 0, 11, 1,  $year),  // Toussaint
	    mktime(0, 0, 0, 11, 11, $year),  // Armistice
	    mktime(0, 0, 0, 12, 25, $year),  // Noel
	    // Dates variables
	    mktime(0, 0, 0, $easterMonth, $easterDay + 1,  $easterYear),
	    mktime(0, 0, 0, $easterMonth, $easterDay + 39, $easterYear),
	    mktime(0, 0, 0, $easterMonth, $easterDay + 50, $easterYear),
	);
	return $holidays;
}
//Retourne le chemin web d'un fichier en fonction de son chemin 
//physique (ex : /img/logo.png pour /var/www/erp-core/img/logo.png)
function webpath($path){
    $url = ROOT_URL.str_replace(array(__DIR__.SLASH,"\\","/"),array('','/','/'),$path);
    $url = preg_replace('/([^\:])(\/{2,})/i', '$1/', $url);
    return $url;
}
//Retourne un tableau clé/valeur des valeurs
//existantes en doublons du tableau passé en paramètre
function array_not_unique($array=array()){
	return array_diff_key($array, array_unique($array));
}
//Retourne true si aucune valeur du tableau passé
//en paramètre a un doublon, false sinon. 
function is_array_unique($array=array()){
	return empty(array_not_unique($array));
}
function make_cookie($nom, $valeur, $expire='',$ns ='/') {
	if($expire == ''){
		setcookie($nom, $valeur, mktime(0,0,0, date("d"), date("m"), (date("Y")+1)), $ns);
		} else {
		setcookie($nom, '', mktime(0,0,0, date("d"), date("m"), (date("Y")-1)), $ns);
		}
}
//Permet de formatter les prix à afficher
//de la même manière partout sur l'ERP.
//Si jamais on veut changer de normalisation
//pour l'affichage des prix, il suffit de changer
//le fonctionnement ici uniquement.
function display_price($price){
	return number_format($price, 2, ',', ' ');
}
//Permet de formatter les prix à enregistrer
//de la même manière partout sur l'ERP.
//Si jamais on veut changer de normalisation
//pour l'enregistrement des prix, il suffit de changer
//le fonctionnement ici uniquement.
function format_price($price){
	return str_replace(',','.',$price);
}
//Permet de calculer un age en fonction de la date du jour
//Paramètre : la date de départ(format timestamp), l'unité de retour souhaitée(format 'd', 'm', 'Y'... voir fonction diff de php)
//Renvoie : l'age en entier
function age($from,$unit = 'y'){
	if(!isset($from) || !is_numeric($from)) return 'N/A';
	$from = new \DateTime(date('Y-m-d',$from));
	$to = new \DateTime('today');
	$age = $from->diff($to);
	if(!$age || !array_key_exists($unit, $age))
		return 'N/A';
	return $age = $age->$unit;
}
//Permet d'échapper tous les caractères interdits dans une chaine json
function escape_json_string($value) { # list from www.json.org: (\b backspace, \f formfeed)
    $escapers = array("\\", "/", "\"", "\n", "\r", "\t", "\x08", "\x0c");
    $replacements = array("\\\\", "\\/", "\\\"", "\\n", "\\r", "\\t", "\\f", "\\b");
    $result = str_replace($escapers, $replacements, $value);
    return $result;
}
?>