<?php
/**
 * Fonctions utiles au plugin Biodiv
 *
 * @plugin     Biodiv
 * @copyright  2008-2024
 * @author     Renaud LAURETTE
 * @licence    GNU/GPL
 * @package    SPIP\Biodiv\Fonctions
 */

if (!defined('_ECRIRE_INC_VERSION')) {
	return;
}


/* leaflet_api_list()
 * Retourne la liste des serveurs de tuiles pour la sélection de la couche par défaut
 * depuis la configuration Biodiv dans l'espace privé. Cette liste doit correspondre
 * à celle qui figure dans la fonction Feuillet() de feuillet.js
 */
/* 
function leaflet_api_list()
{
	$laliste= array(
		'Google Hybrid' => 'Google Hybrid',
		'Google Streets' => 'Google Streets',
		'OSM' => 'OSM',                 
		'Esri Topo Map' => 'Esri Topo Map',
		'Esri Imagery' => 'Esri Imagery',
		"ArcGIS Streets" => "ArcGIS Streets",
		'CartoDB' => 'CartoDB',
		);
	if(lire_config('biodiv/tuilesLocales','non')=='oui')
		$laliste['Local']='Local';
	return $laliste;
}


function lire_json($fichier)
{
    $content = file_get_contents($fichier);
    $jx = json_decode($content);
    $out = json_encode($jx);
    return $out;
}
*/
/**
 * Recherche d'un fichier lieu
 *
 * Les lieux et les périmètres peuvent être notés selon différentes syntaxes.
 * La fonction décode la syntaxe et recalcule le chemin d'accès au fichier
 *
 * @param  string $lieu  Syntaxe désignant un périmètre ou un lieu
 * @return string        Chemin relatif d'accès au fichier correspondant
**/

function retrouver_lieu($lieu) {
	// détection des fichiers temporaires
	if(substr($lieu,0,2) == "t|") $lieu = _DIR_BIODIV_TMP_LIEUX . substr($lieu,2);
	return $lieu;
}


/**
 * Récupération du rectangle englobant d'un lieu
 *
 * Cette fonction est utilisée pour focaliser la recherche géographique en base
 * et restreindre les observations dont on testera l'appartenance à un polygone
 *
 * @param  string $lieu  Syntaxe désignant un périmètre ou un lieu
 * @return array         Extrémités de la bounding box
**/

function bounding_box($lieu) {
	
	$bbox = array();
	$lieu = retrouver_lieu($lieu);
	if(file_exists($lieu)) {
		// lecture du geojson
		$content = file_get_contents($lieu);
		$jx = json_decode($content);
		$bbox['minlat'] = $jx->bbox[1];
		$bbox['maxlat'] = $jx->bbox[3];
		$bbox['minlng'] = $jx->bbox[0];
		$bbox['maxlng'] = $jx->bbox[2];
	}
	return $bbox;
}


/**
 * Récupération du polygone constituant un lieu
 *
 * Les lieux sont des features geojson de type polygone simple.
 * La fonction retrouve le fichier correspondant au lieu et en extrait
 * le premier polygone
 *
 * @param  string $lieu  Syntaxe désignant un périmètre ou un lieu
 * @return object        Polygone du lieu
**/

function polygone_lieu($lieu) {
	
	$poly = array();
	$lieu = retrouver_lieu($lieu);
	if(file_exists($lieu)) {
		// spip_log("    Le fichier lieu existe ","Biodiv");
		// lecture du geojson
		$content = file_get_contents($lieu);
		$jx = json_decode($content);
		$poly = $jx->geometry->coordinates[0];
	}
	return $poly;	
}

/**
 * Fonction de compression d'une série de coordonnées
 * pour utilisation par l'API de carte statique Mapquest.
 * voir : https://developer.mapquest.com/documentation/common/encode-decode/
 * Attention : contrairement à l'exemple ci-dessus pour lequel les points sont
 * des paires (lat,lon), les points récupérés du geojson sont des (lon,lat).
 * Les fonctions ci-dessous sont adaptées pour traiter des coordonnées geojson.
 */
 function mpqCompress($points,$mpqPrecision=5) {
 	 $oldLat=0.0; $oldLng=0.0; $index=0; $encoded = '';
 	 $len = count($points);
 	 $precision = pow(10,$mpqPrecision);
 	 
 	 while($index < $len) {
 	 	 // convention geojson - inverser pour les points (lat,lon)
 	 	 $lat = round($points[$index][1] * $precision);
 	 	 $lng = round($points[$index][0] * $precision);
 	 	 $encoded .= mpqEncodeNumber($lat - $oldLat);
 	 	 $encoded .= mpqEncodeNumber($lng - $oldLng);
 	 	 $oldLat = $lat;
 	 	 $oldLng = $lng;
 	 	 $index++;
 	 }
 	 return $encoded;
 }
 
 function mpqEncodeNumber($num) {
 	 $lnum = $num << 1;
 	 if($lnum < 0) { $lnum = ~($lnum); }
 	 $encoded = '';
 	 while($lnum > 32) {
 	 	 $encoded .= chr((32|($lnum & 31))+63);
 	 	 $lnum >>= 5;
 	 }
 	 $encoded .= chr($lnum+63);
 	 return $encoded;
 }
 
function polygone_lieu_mapquest($lieu) {
	$encoded = '';
	$lieu = retrouver_lieu($lieu);
	if(file_exists($lieu)) {
		// lecture du geojson
		$content = file_get_contents($lieu);
		$jx = json_decode($content);
		$poly = $jx->geometry->coordinates[0];
		// encodage des points
		$encoded = mpqCompress($poly);
	}
	return $encoded;	
}


/**
 * Filtrage d'une liste d'observations en fonction d'un lieu
 *
 * Reçoit un tableau d'observation, dont chaque entrée est un tableau associatif
 * comprenant les clés suivantes
 *		- id : identifiant de l'observation
 *		- lat : latitude
 *		- lng : longitude
 * Ce tableau va être filtré par un polygone simple situé dans un fichier lieu
 * La fonction retourne les identifiants des observations du tableau situés
 * à l'intérieur du polygone.
 *
 * @param  array $tabobs 	Tableau d'observation
 * @param  string $lieu	    Accès au fichier lieu
 * @return array        	Tableau des identifiants d'observation
**/

function lieu_polygonal($tabobs, $lieu) {

	$result = array();
	$lieu = retrouver_lieu($lieu);
	if(file_exists($lieu)) {
		// lecture du geojson
		$content = file_get_contents($lieu);
		$jx = json_decode($content);
		// on le suppose bien structuré et contenant un polygone
		$poly = $jx->geometry->coordinates[0];
		
		foreach($tabobs as $val) {
			$pt[1] = $val['lat'];
			$pt[0] = $val['lng'];
			$id = $val['id'];
			if (polygonFilter($poly,$pt)) $result[] = $id;
		}
	}
	return $result;	
}

function polygonFilter($poly, $pt) {
         // y=0=lng ; x=1=lat
         // d'après Jonas Raoni Soares Silva  @ http://jsfromhell.com/math/is-point-in-poly [rev. #0]
            
         for($c = false, $i = -1, $l = count($poly), $j = $l - 1; ++$i < $l; $j = $i)
                        (($poly[$i][0] <= $pt[0] && $pt[0] < $poly[$j][0]) || ($poly[$j][0] <= $pt[0] && $pt[0] < $poly[$i][0]))
                        && ($pt[1] < ($poly[$j][1] - $poly[$i][1]) * ($pt[0] - $poly[$i][0]) / ($poly[$j][0] - $poly[$i][0]) + $poly[$i][1])
                        && ($c = !$c);
                return $c;
}

/**
 * Génère un fichier geojson équivalent à un fichier lieu fourni si l'extension fournie est 'lieu'
 * Retourne alors le chemin d'accès au fichier généré.
 * Sinon retourne le chemin d'accès fourni sans modification et ne génère rien
 *
 * @param  string $fichier  Le chemin relatif d'accès à un fichier
 * @param  string $ext		L'extension du fichier
 * @return string		  	Le chemin relatif d'accès à un fichier
**/
function lieu2geojson($fichier,$ext) {
	
	// On ne traite que les lieux
	if($ext != 'lieu') return $fichier;
	
	$lieu = retrouver_lieu($fichier);	// fonctionne avec toutes les syntaxes de fichier
	if(file_exists($lieu)) {
		// lecture du lieu
		$content = file_get_contents($lieu);
		$jx = json_decode($content);
		// selection des éléments pertinents pour geojson
		$jout = (object)[];
		$jout->type = $jx->type;
		$jout->bbox = $jx->bbox;
		$jout->geometry = $jx->geometry;
		// génère le fichier temporaire
		$output = placer_en_cache($lieu,'lieu','geojson');
		$content = json_encode($jout);
		file_put_contents($output,$content);
		return $output;
	}
	// erreur : on ne fait rien
	return 'xxx-'.$lieu;
}


/**
 ** Retourne un tableau dont les lignes sont les espèces de la rubrique $i_rubrique
 ** n'ayant pas été observées depuis $nba années.
 ** Chaque ligne contient :
 ** - 'titre' : le nom de l'espèce
 ** - 'soustitre' : le nom binominal de l'espèce
 ** - 'id_espece' : son numéro de fiche dans le site
 ** - 'date' : la date de la dernière observation
*/

function especes_plus_observees($id_rubrique,$nba) {
	
	$annee = 31536000; /* secondes dans une année */
	$before = time() -($nba * $annee);
	$bdata = getdate($before);
	$dmax = sprintf("%4d-%02d-%02d",$bdata["year"],$bdata["mon"],$bdata["mday"]);
	
	$res = sql_select(
		array( // select
			"e.titre",
			"e.soustitre",
			"o.id_espece",
			"max(o.date_obs) AS date",
			),
		array( // from
			"spip_biodiv_observations AS o",
			"spip_articles AS e",
			),
		array( // where
			"o.id_espece = e.id_article",
			"o.type_obs = 'espece'", 
			"e.id_rubrique = ". intval($id_rubrique),
			),
		"o.id_espece", // group by
		"max(o.date_obs) ASC",	// order by
		"", //limit
		"max(o.date_obs)< ". sql_quote($dmax)  // having
		);
	
	$retour = array();
	if ($res) {
		while ($r = sql_fetch($res)) {
			$retour[] = $r;
			// utilisation des resultats avec $r['colonne']
		}
	}
	//echo "\n<!-- Résultat: " . print_r($retour,true) . "-->\n\n";
	// spip_log("Especes_plus_observees depuis '$dmax' : ".print_r($retour,true), "biodiv."._LOG_INFO_IMPORTANTE);
	return $retour;
}	


/**
 * Retourne un chemin d'accès pour un fichier à mettre en cache, sur la base
 * d'un nom de fichier original
 *
 * @param string $fichier  	Le chemin relatif d'accès au fichier original
 * @param string $old		L'extension du fichier original
 * @param string $new		L'extension qu'aura le fichier à cacher
 * @return string		  	Le chemin d'accès au fichier en cache
**/
function placer_en_cache($fichier,$old,$new) {
	// vérifier l'existance de /local/cache-$new
	$dir = _NOM_TEMPORAIRES_ACCESSIBLES .'cache-'.$new;
	if(!is_dir($dir)) {
		mkdir($dir);
	} 
	else {
		$files = array_diff(scandir($dir),array('..','.'));
		if(count($files)>50) {
			$now = time();
			foreach($files as $file) {
				$f = $dir.'/'.$file;
				// On supprime les fichiers de plus de 3 heures (environ). Les autres sont laissés
				// au cas où un visiteur les utiliseait encore.
				if($now - filectime($f) > 10000 ) { 
					unlink($f);
				}
			}
		}
	}
	
				
	// créer le nom de fichier 
	$bname = basename($fichier,$old);
	$fname = $dir .'/'. $bname . $new;
	return $fname;
}
	

/**
 * Filtrage d'une liste d'observations selon les droits du visiteur logé
 * Usage : {id_observation IN [(#ENV{listeobs}|debusquable)]}
 *
 * @param  array $tabobs  Liste d'identifiants d'observations demandées
 * @return array		  Liste d'identifiants d'observations autorisées
**/
function debusquable($tabobs) {
	if(!is_array($tabobs)) $tabobs = array($tabobs);
	$qui = isset($GLOBALS['visiteur_session']) ? $GLOBALS['visiteur_session'] : null;
	$result = filtrer_observations_debusquables($tabobs,$qui);
	return $result;
}

function espionne($value,$label) {
	spip_log("Espionne $label = ".print_r($value,true),_LOG_INFO_IMPORTANTE);
	return($value);
}

/**
 * Construction d'un lien d'accès à un taxon
 *
 * Construit l'URL permetant d'accéder à la fiche d'un taxon sur le site de l'INPN,
 * à partir de l'identifiant de la fiche espèce et du template d'URL fourni 
 * dans la configuration TAXREF.
 *
 * @param  integer $espece  Identifiant de la fiche espèce associée au taxon
 * @return string        	Code HTML du lien vers la page du taxon sur le site de l'INPN
**/

function url_taxref($cdnom) {
	$taxrefConf = unserialize($GLOBALS['meta']['taxref']);
	$urlPattern = $taxrefConf['url'];
	$url = sprintf($urlPattern,$cdnom);
	return $url;
}


/**
 * Verbalisation des types de dénombrement d'observation (Standard SINP)
 *
 * @param  string $val 	 Code de dénombrement de l'observation en base
 * @return string        Statut verbalisé dans la langue courante
**/

function txt_denombrement($val) {
	switch($val) {
	case 'Co':
		$txt = _T('observation:enum_denombrement_co');
		break;
	case 'Es':
		$txt = _T('observation:enum_denombrement_es');
		break;
	case 'Ca':
		$txt = _T('observation:enum_denombrement_ca');
		break;
	case 'NSP':
	default:
		$txt = _T('observation:enum_denombrement_nsp');
		break;
	}
	return($txt);
}

/**
 * Verbalisation des statuts des sources
 *
 * @param  string $val 	 Code de statut de la source en base
 * @return string        Statut verbalisé dans la langue courante
**/

function txt_statut_source($val) {
	switch($val) {
	case 'Te':
		$txt = _T('source:enum_statut_source_te');
		break;                    
	case 'Co':
		$txt = _T('source:enum_statut_source_co');
		break;
	case 'Li':
		$txt = _T('source:enum_statut_source_li');
		break;
	case 'NSP':
	default:
		$txt = _T('source:enum_statut_source_nsp');
		break;
	}
	return($txt);
}

/**
  * Enregistrement d'une donnée dans la session de l'utilisateur
  * sous une clef aléatoire. Cette fonction permet d'obtenir une clef utilisable dans
  * une URL afin de passer en paramètre une structure complexe gérée par une variable SPIP
  *
  * Syntaxe (exemple avec un tableau)
  *   Préparation:
  *      #SET{tableau,#ARRAY}
  *		 #URL_PAGE{mapage}|parametre_url{reftableau,[(#GET{tableau}|garder_donnees)]}
  *   L'URL appelée ressemblera à 
  *      spip.php?page=mapage&reftableau=d_5fg78ardf87ae23o9
  *   Récupération dans le squelette mapage.html :
  *      BOUCLE_var(MESOBJETS) {id_objet IN [(#ENV{reftableau}|retrouver_donnees)]}
  *
  * @param mixed $data	Les données à garder
  * @return $clef		La clef sous laquelle le tableau est stocké
  *
 */
 
function garder_donnees($data) {
	$clef = 'd_'.md5('data'.time());
	session_set($clef,serialize($data));
	return $clef;
}

function retrouver_donnees($clef) {
	return unserialize(session_get($clef));
}

/**
 * Canonise un nom d'espèce
 */
function canoniser($espece) {
	$pattern = '/ [Ss]p[\\.]?$/';
	$replace = '';
	$canon = preg_replace($pattern,$replace,trim($espece));
	return $canon;
}

/**
 * Retourne la liste des espèces associées à plus d'un taxon
 * Utilisé pour déterminer les espèces dont la classification a évolué dans le temps
 * L'historique se retrouve à partir des dates des entrées dans la table des taxons
 *
 * @return array         Tableau des identifiants d'espèces
**/

function especes_multi_taxon() {
	$resultats = sql_select(
			array('id_espece', 'COUNT(*)'),	// select
			'spip_taxrefs',	// from
			'',				// where
			'id_espece',	// groupby
			'',				// orderby
			'',				// limit
			'COUNT(*) > 1'	// having
			);
	$especes = array();
	while($res = sql_fetch($resultats)) {
		$especes[] = $res['id_espece'];
	}
	return $especes;
}

/**
 * Génération d'un UUID pour un objet de la base, à partir de son URL
 * et de l'UUID de la base, stocké dans #CONFIG{sinp/idcnp}
 * S'utilise : [(#URL_OBSERVATION|generer_uid)]
 *
 * @param string $url 	 URL de l'objet sur le site
 * @return string        UUID-v5
**/

function generer_uid ($url) {
	$sinp = unserialize($GLOBALS['meta']['sinp']);
	$idcnp = $sinp['idcnp'];
	//$namespace = "8345705a-2322-11ea-87fb-0242ac11000e";
	return uuid_v5($idcnp,$url);                               
}

/**
 * Génération d'un UUID conforme à la RFC 4122 v5
 * Nécessite de fournir un UUID en entrée (namespace)
 * Retourne un UUID fixe pour une paire (namespace,name)
 * Dans la pratique, le namespace sera l'UUID de la base Biodiv
 * et name sera l'url de l'objet nécesitant un UUID.
 *
 * @param  string $namespace 	 UUID de référence pour le calcul de celui de l'objet
 * @param  string $name			 identifiant de l'objet (son URL)
 * @return string        		 UUID-v5 généré
**/

function uuid_v5($namespace, $name) {
    if(preg_match('/^\{?[0-9a-f]{8}\-?[0-9a-f]{4}\-?[0-9a-f]{4}\-?'.
                      '[0-9a-f]{4}\-?[0-9a-f]{12}\}?$/i', $namespace) !== 1) return false;

    // Get hexadecimal components of namespace
    $nhex = str_replace(array('-','{','}'), '', $namespace);
    // Binary Value
    $nstr = '';
    // Convert Namespace UUID to bits
    for($i = 0; $i < strlen($nhex); $i+=2) {
      $nstr .= chr(hexdec($nhex[$i].$nhex[$i+1]));
    }
    // Calculate hash value
    $hash = sha1($nstr . $name);
    return sprintf('%08s-%04s-%04x-%04x-%12s',
    	// 32 bits for "time_low"
    	substr($hash, 0, 8),
    	// 16 bits for "time_mid"
    	substr($hash, 8, 4),
    	// 16 bits for "time_hi_and_version",
    	// four most significant bits holds version number 5
    	(hexdec(substr($hash, 12, 4)) & 0x0fff) | 0x5000,
    	// 16 bits, 8 bits for "clk_seq_hi_res",
    	// 8 bits for "clk_seq_low",
    	// two most significant bits holds zero and one for variant DCE1.1
    	(hexdec(substr($hash, 16, 4)) & 0x3fff) | 0x8000,
    	// 48 bits for "node"
    	substr($hash, 20, 12)
    	);
}


/**
 * Génére un identifiant temporaire pour un utilisateur qui lui permet une authentification rapide
 *
 * @param integer $ida  Identifiant de l'auteur
 * @return string        Identifiant temporaire ou message d'erreur
**/

function biodiv_tempid($ida=0) {
	$auteur = intval((string)$ida);
	// l'auteur demandé doit avoir un identifiant
	if($auteur == 0) return _T('biodiv:pas_un_auteur');
	// et il doit être celui de l'auteur connecté
	if( !isset($GLOBALS['visiteur_session']['id_auteur']) OR
		($GLOBALS['visiteur_session']['id_auteur']!= $auteur)) 
			return _T('biodiv:pas_un_auteur');
	// On récupère les infos de l'auteur.
	// Celles dont on a besoin ne sont pas dans la session
	if($row = sql_fetsel('*','spip_auteurs','id_auteur='.$auteur)) {
		// données trouvées : on vérifie que l'accès était bien protégé par autentification
		if( ($row['statut']== '5poubelle') OR
			($row['login'] == '') OR
			($row['pass'] == '')
			) { 
			return _T('biodiv:pas_un_auteur');
		}
		
		$t = time() + (90*24*3600);	// expiration de l'autorisation dans 10 mn
		// On calcule une clé suffisamment aléatoire ...
		$k = $row['nom'] . $row['alea_actuel'] . $t; 
		$ukey = hash('sha1',$k,false); // substr($k,0,8);
		// On génère une info reconnaissable avec la clé et la date d'expiration
		// et on enregistre le tout dans 'cookie_oubli' qui n'a aucune chance d'être utilisé
		// pour son rôle nominal si l'auteur a réussi à s'identifier.
		$ck = 'b.'.$ukey.'.'.$t;
		sql_updateq('spip_auteurs',array('cookie_oubli'=>$ck),'id_auteur='.$row['id_auteur']);
		// et on renvoie la clé
		return $ukey;
	}
	// Quelque chose s'est mal passé.
	return _T('biodiv:pas_un_auteur');
}

/*
function badTrack($message) {
	$filename = "tmp/log/badtrack.log";
	if (!is_string($message)) {
			$message = print_r($message, true);
	}
	$message .= "\n";
	file_put_contents($filename,$message,FILE_APPEND);
}
*/	

/**
 * Retourne un chemin vers un $fichier par défaut dans
 * 'data-dist' à moins que le $fichier n'existe dans 'data'
 * Syntaxe: [(#VAL{nom-fichier}|personnalisation)]
 */
function personnalisation($fichier) {
	if( ($chemin_image = find_in_path("data/$fichier")) ||
		($chemin_image = find_in_path("data-dist/$fichier"))
		) {
		return $chemin_image;
		}
	return find_in_path("data-dist/vide.txt");
}
		
?>