<?php

/*******************************************************************************\
 * BIODIV, plugin et squelette pour SPIP - https://www.spip.net/               *
 *         dédié à la gestion d'observations naturalistes                      *
 *                                                                             *
 *         Copyright (C) 2008-2025 Renaud LAURETTE                             *
 *                                                                             *
 * BIODIV a été développé initialement pour le projet Biodiv.Balma de l'APCVEB *
 * (Association de Protection du Cadre de Vie et de l'Environnement balmanais) * 
 *     voir Biodiv.Balma : https://balma.biodiv.fr/                            *
 *     voir APCVEB : https://apcveb.fr/                                        *
 *                                                                             *
 *  Ce programme est un logiciel libre distribué sous licence GNU/GPL.         *
 *  Pour plus de détails voir les fichiers COPYING.txt et LICENCE-BIODIV.md    *
\*******************************************************************************/


include_spip('base/abstract_sql');
include_spip('action/editer_objet');
include_spip('inc/invalideur');

// Ordre Lier générique
// Cet ordre fait appel à des ordres plus spécialisés
// 	$lier : la structure XML de l'ordre à traiter
// 	$self : la référence d'un objet auquel se rapporte l'ordre

function ordreLier($lier,$self=null)
{
	$ordre= null;
	foreach($lier->attributes() as $key => $value) { 
	  if($value == 'self') $lier[$key]=$self;
	}
	
	switch($lier['xObjet']) {
	case 'observation':
		$ordre = ordreLierObservation($lier);
		executerUpdate($ordre);
		break;
	default:
		break;
	}
	
	return $ordre;
}

// Preparation d'un ordre SQL sur la base d'un ordreBiodiv 'lier'
// L'ordre retourné sera appelé par : sql_updateq($ordre['table'],$ordre['values'],$ordre['where']);
// L'ordre est valide si $ordre['pret']=true.
// Sinon l'erreur est dans $ordre['erreur'].

function ordreLierObservation($lier)
{
	$ordre = array();
	$ordre['pret'] = false;
	$ordre['skip'] = false;
	$ordre['type'] = 'lier';
	
	if(	isset($lier['id_observation']) 
	    and isset($lier['xRelation'])
    	    ) {
    		// Pour que la liaison s'effectue, il est nécessaire que l'observation existe : on vérifie
    		if(sql_countsel('spip_biodiv_observations', 'id_observation='.intval((string)$lier['id_observation'])) != 0) {
    			// elle existe : on prépare les élément de la requete SQL
    			$ordre['table'] = 'spip_biodiv_observations';
    			$ordre['where'] = 'id_observation='.intval((string)$lier['id_observation']);
		
    			switch($lier['xRelation']) {
    			case 'observation.campagne':
    				// Lier une observation à une campagne
    				// Lien particulier passant par une table de liens
    				ordre_controlerLienObservation($ordre,$lier,$lier['id_observation'],'campagne','id_campagne','spip_biodiv_campagnes');
    				$ordre['invalideur'] = array(
    					"id='campagne/".$lier['id_campagne']."'",
    					"id='observation/".$lier['id_observation']."'"
    					); 
    				$ordre['event'] = array(
    					'id_observation' => $lier['id_observation'],
    					'objet' => 'campagne',
    					'nature' => 'campagne',
    					'id_objet' => $lier['id_campagne']
    					);
    				break;
    			case 'observation.expo':
    				// Lier une observation à un panneau d'exposition
    				// Lien particulier passant par une table de liens
    				ordre_controlerLienObservation($ordre,$lier,$lier['id_observation'],'article','id_article','spip_articles');
    				$ordre['invalideur'] = array(
    					"id='article/".$lier['id_article']."'",
    					"id='observation/".$lier['id_observation']."'"
    					); 
    				$ordre['event'] = array(
    					'id_observation' => $lier['id_observation'],
    					'objet' => 'article',
    					'nature' => 'exposition',
    					'id_objet' => $lier['id_article']
    					);
    				break;
    			case 'observation.famille':
    				// L'ordre est de lier une observation à une famille (rubrique)
    				// On controle l'existance de la rubrique et on complète l'ordre
    				ordre_controlerId($ordre,$lier,'id_famille','id_rubrique','spip_rubriques');
    				// Lier une observation à une famille casse obligatoirement le lien éventuel
    				// qui existait avec une espèce
    				$ordre['values']['id_espece']=0;
    				// $ordre['values']['statut']='prop';
    				$ordre['invalideur'] = array(
    					"id='rubrique/".$lier['id_famille']."'",
    					"id='observation/".$lier['id_observation']."'"
    					);
    				$ordre['event'] = array(
    					'id_observation' => $lier['id_observation'],
    					'objet' => 'rubrique',
    					'nature' => 'famille',
    					'id_objet' => $lier['id_famille']
    					);
    				break;
    			case 'observation.espece':
    				// L'ordre est de lier une observation à une espèce (article)
    				// On controle l'existance de l'article et on complète l'ordre
    				ordre_controlerId($ordre,$lier,'id_espece','id_article','spip_articles');
    				// Lier une observation à une espèce casse obligatoirement le lien éventuel
    				// qui existait avec une famille
    				$ordre['values']['id_famille']=0;
    				// $ordre['values']['statut']='publie';
    				$ordre['invalideur'] = array(
    					"id='article/".$lier['id_espece']."'",
    					"id='observation/".$lier['id_observation']."'"
    					);
    				$ordre['event'] = array(
    					'id_observation' => $lier['id_observation'],
    					'objet' => 'article',
    					'nature' => 'espece',
    					'id_objet' => $lier['id_espece']
    					);
    				break;
    			case 'observation.taxon':
    				// On recherche l'espace via le taxon grace au cd_nom
    				// en cas de succes on lie à l'espèce   				
    				if(trouver_taxon($lier)) {
    					ordre_controlerId($ordre,$lier,'id_espece','id_article','spip_articles');
    					$ordre['values']['id_famille']=0;
    					$ordre['values']['statut']='publie';
    					$ordre['invalideur'] = array(
    						"id='article/".$lier['id_espece']."'",
    						"id='observation/".$lier['id_observation']."'"
    						);    				
    					$ordre['event'] = array(
    						'id_observation' => $lier['id_observation'],
    						'objet' => 'article',
    						'nature' => 'espece',
    						'id_objet' => $lier['id_espece']
    						);
    				} else {
    				    $ordre['erreur'] = _T('biodiv:msg_base_lien_manquant');
    				    $ordre['skip'] = true;
    				}
    				break;
    			case 'observation.sujet':
    				ordre_controlerId($ordre,$lier,'id_sujet','id_article','spip_articles');
    				$ordre['invalideur'] = array(
    						"id='article/".$lier['id_sujet']."'",
    						"id='observation/".$lier['id_observation']."'"
    						); 
    				$ordre['event'] = array(
    					'id_observation' => $lier['id_observation'],
    					'objet' => 'article',
    					'nature' => 'sujet',
    					'id_objet' => $lier['id_sujet']
    					);
    				break;
    			case 'observation.source':
    				ordre_controlerId($ordre,$lier,'id_source','id_source','spip_biodiv_sources');
    				$ordre['values']['ref_source']=$lier['ref_source'];
    				$ordre['invalideur'] = array(
    						"id='source/".$lier['id_source']."'",
    						"id='observation/".$lier['id_observation']."'"
    						);  
    				$ordre['event'] = array(
    					'id_observation' => $lier['id_observation'],
    					'objet' => 'source',
    					'nature' => 'source',
    					'id_objet' => $lier['id_source']
    					);
    				break;	
    			case 'observation.auteur':
    				ordre_controlerAuteurObservation($ordre,$lier);
    				$ordre['invalideur'] = array(
    						"id='auteur/".$lier['id_auteur']."'",
    						"id='observation/".$lier['id_observation']."'"
    						);  
    				$ordre['event'] = array(
    					'id_observation' => $lier['id_observation'],
    					'objet' => 'auteur',
    					'nature' => 'observateur',
    					'id_objet' => $lier['id_auteur']
    					);
    				break;
    			case 'observation.sans.operateur':
    				ordre_retirerOperateurObservation($ordre,$lier);
    				// L'invalideur est déjà pris en compte
    				// Pas d'event sur ce type d'action
    				break;
    			default:
    				// La relation portée par l'ordre est inconnue
    				$ordre['erreur'] = _T('biodiv:msg_relation_inconnue', array('rel'=>$lier['xRelation']));
    				break;
    			}
    		} else {
    			// L'observation à lier n'existe pas
    			$ordre['erreur'] = _T('biodiv:msg_observation_inexistante',
    						array('obs'=>intval((string)$lier['id_observation']))
    						);
    		}
    	} else {
    		// L'ordre est mal structuré
    		$ordre['erreur'] = _T('biodiv:msg_ordre_incomplet1');
    	}
	
	return $ordre;		
}


function trouver_taxon(&$lier) {
	if(isset($lier['cd_nom'])) {      
		$cdn= intval((string)$lier['cd_nom']);
		$r = sql_fetsel('id_espece','spip_taxrefs',"id_taxref=$cdn");
		if($r and isset($r['id_espece'])) {
			$v = $r['id_espece'];
			$lier['id_espece'] = $r['id_espece'];
			return $r['id_espece'];
		}
	}
	return null;
}

// Verifie la validité du lien
// Accepte de remplacer la cible par la valeur 'self' quand elle n'est pas définie
// Cette fonction modifie l'ordre et prépare ses paramètres.
//	$ordre : l'ordre SQL à compléter
//	$lier : l'ordre XML contenant les instructions
//	$self : la référence éventuelle à un objet
//	$id_lien : le nom du champ SQL qui portera le lien
//	$table: la table dont la valeur de $id_lien doit être une clé
//	$id_table : le champ de la $table portant la clé

function ordre_controlerId(&$ordre,$lier,$id_lien,$id_table,$table)
{
	$target=null;	
	// On vérifie la cohérence de l'ordre
	if(isset($lier[$id_lien])) { //Ok.	
		$target = intval((string)$lier[$id_lien]);	
		// A ce stade, $target est la cible du lien à poser.
		// On vérifie sa valeur.
		if($target) {
			// Cible non nulle. On prépare la partie "values" de l'ordre SQL update.
			$ordre['values'] = array($id_lien => $target);
			// On vérifie que la cible du lien existe dans sa table
			if(sql_countsel($table, "$id_table=$target")) { // Ok			
				$ordre['pret'] = true;
			} else { // Ko
				$ordre['erreur'] = _T('biodiv:msg_base_champ_inexistant', array('ch' => $id_lien));
			}
		} else {  // Cible nulle - non autorisé	
			$ordre['erreur'] = _T('biodiv:msg_base_lien_invalide');
		}
	} else {  // L'ordre n'est pas cohérent
		$ordre['erreur'] = _T('biodiv:msg_base_lien_manquant');
	}	
}

include_spip('inc/u_campagnes');

function ordre_controlerLienObservation(&$ordre,$lier,$idobs,$objet,$id_table,$table)
{
	// spip_log("controlerLienObservation: ordre: ". print_r($ordre,true), "biodiv."._LOG_INFO_IMPORTANTE);
	// spip_log("controlerLienObservation: lier: ". print_r($lier,true), "biodiv."._LOG_INFO_IMPORTANTE);
	// On vérifie la cohérence de l'ordre
	if(!isset($lier[$id_table])) {
		// L'ordre n'est pas cohérent
		$ordre['erreur'] = _T('biodiv:msg_base_lien_manquant');
		return false;
	}
	
	$target = intval((string)$lier[$id_table]);	
	if($target <= 0) {
		// Index non autorisé	
		$ordre['erreur'] = _T('biodiv:msg_base_lien_invalide');
		return false;
	}			
			
	// A ce stade, $target est la cible du lien à poser. 
	// On vérifie si le lien existe déjà
	if(sql_countsel('spip_biodiv_observations_liens', array('id_observation' => $idobs, 'id_objet' => $target, 'objet' => $objet))) {
		$ordre['erreur'] = _T('biodiv:msg_base_lien_double', 
								array('type' => 'observation', 'id' => $idobs, 'objet' => $objet, 'idobj' => $target));
		return false;
	}

	// On vérifie si la cible du lien existe
	if(!sql_countsel($table, "$id_table=$target")) { 
		$ordre['erreur'] = _T('biodiv:msg_base_objet_inexistant', array('objet' => $objet, 'id' =>$target));
		return false;
	}
	
	// Pour les campagnes, on vérifie la période et le périmètre
	if(($objet == 'campagne') and is_string($e=observation_pour_campagne($idobs,$target))) {
		$ordre['erreur'] = $e;
		return false;
	}
	
	// Tout est OK
	$ordre['table'] = 'spip_biodiv_observations_liens';
	$ordre['where'] = false;
	$ordre['values'] = array(
		'id_observation' => $idobs,
		'objet' => $objet,
		'id_objet' => $target
		);
	$ordre['pret'] = true;
	return true;
}

function ordre_controlerAuteurObservation(&$ordre,$lier) {
	// vérification de la présence des liens
	$idobs = intval((string)$lier['id_observation']);
	if(!isset($lier['id_auteur'])) {
		$ordre['erreur'] = _T('biodiv:msg_base_lien_manquant');
		return false;
	}
	
	$target = intval((string)$lier['id_auteur']);	
	if($target <= 0) {
		// Index non autorisé	
		$ordre['erreur'] = _T('biodiv:msg_base_lien_invalide');
		return false;
	}			
	// A ce stade, $target est la cible du lien. 
	// On vérifie si le lien existe déjà
	$deja = sql_countsel('spip_auteurs_liens', array('id_auteur' => $target, 'id_objet' => $idobs, 'objet' => 'observation'));	
	if($deja) {
		// On veut ajouter et le lien existe déjà
		$ordre['erreur'] = _T('biodiv:msg_base_lien_double', 
							array('type' => 'auteur', 'id' => $target, 'objet' => 'observation', 'idobj' => $idobs));
		return false;
	}
	// On vérifie si la cible du lien existe
	if(!sql_countsel('spip_auteurs', "id_auteur=$target")) { 
		$ordre['erreur'] = _T('biodiv:msg_base_objet_inexistant', array('objet' => 'auteur', 'id' =>$target));
		return false;
	}
	// Tout est OK
	$ordre['table'] = 'spip_auteurs_liens';
	$ordre['where'] = false;
	$ordre['values'] = array(
		'id_auteur' => $target,
		'objet' => 'observation',
		'id_objet' => $idobs
		);
	$ordre['nowhere'] = "id_auteur=$target AND objet='observation' AND id_objet=$idobs";
	$ordre['pret'] = true;
	return true;	
}

function ordre_retirerOperateurObservation(&$ordre,$lier) {
	// vérification de la présence des liens
	$idobs = intval((string)$lier['id_observation']);
	if(!isset($lier['id_auteur'])) {
		$ordre['erreur'] = _T('biodiv:msg_base_lien_manquant');
		return false;
	}
	// On identifie les auteurs déclarés et l'operateur
	$convertIdAuteur = function($x) { return intval((string)$x); };
	$auteurs = array_map($convertIdAuteur,explode(",", $lier['id_auteur']));
	$operateur = intval((string)$GLOBALS['visiteur_session']['id_auteur']);
	$xa = serialize($auteurs);
	spip_log("BIODIV::RetirerOperateur($operateur,$xa,$idobs)","biodiv."._LOG_INFO_IMPORTANTE);

	// On se prépare à ne rien faire ...
	$ordre['type'] = 'nop';
	$ordre['skip'] = false;
	$ordre['table'] = false;
	$ordre['where'] = false;
	$ordre['values'] = false;
	$ordre['nowhere'] = false;
	$ordre['invalideur'] = array();
	$ordre['pret'] = true;
		
	if(!in_array($operateur,$auteurs)) {
		// Si l'opérateur n'est pas dans les auteurs déclarés on le supprime
		if(($operateur == 1) and (in_array(0, $auteurs))) {
			// Sauf si c'est le webmestre alors que l'auteur est inconnu
		} else {
			spip_log("BIODIV::OperateurDejaAuteur()","biodiv."._LOG_INFO_IMPORTANTE);
			$ordre['type'] = 'delier';
			$ordre['table'] = 'spip_auteurs_liens';
			$ordre['nowhere'] = "id_auteur=$operateur AND objet='observation' AND id_objet=$idobs";
			$ordre['invalideur'] = array(
    			"id='auteur/$operateur'",
    			"id='observation/$idobs'"
    			); 
		}
	}
	
	return true;
}
	
function ordreCreer($creer)
{
	$ordre= null;
	$ordre['type'] = 'creer';
	
	switch($creer['xObjet']) {
	case 'espece':
	case 'sujet':
	case 'expo':
		$ordre = ordreCreerArticle($creer);
		break;
	case 'observation':
		$ordre = ordreCreerObservation($creer);
		break;		
	default:
		break;
	}
	
	return $ordre;
}

// Controle d'unicité
// Pour éviter de créer des doublons, on peut spécifier une condition sur une table
// Si cette condition existe et est satisfaite, la donnée est considérée non unique
// Sinon elle est considérée unique.
// La condition est constituée d'expressions SQL élémentaires séparées par des virgules
// Par exemple : "id_source=2,xref=28"
// Les conditions sont combinées par un ET logique

function controleUnicite($creer) {
	if(isset($creer['uTable']) && isset($creer['uCondition'])) {
		// L'ordre fait l'objet d'un controle d'unicite
		// on récupère le critère d'unicité sous forme de tableau
		$where = explode(",",$creer['uCondition']);
		if(sql_countsel($creer['uTable'], $where) !=0) {
			// La donnee existe déjà. On génère une erreur
			return false;	
		}
	}
	// La donnée est soit unique, soit non contrôlée
	return true;
}

include_spip('action/editer_article');
include_spip('inc/modifier');

function ordreCreerArticle($creer)
{
	$ordre = array();
	$ordre['pret'] = false;
	
	if(	isset($creer['titre']) and isset($creer['id_rubrique'])) {
    		$id_parent = intval((string)$creer['id_rubrique']);
    		if(sql_countsel('spip_rubriques', "id_rubrique=$id_parent") != 0) {
    			// creation de l'article comme coquille vide		
    			$id_article = article_inserer($id_parent);
    			if($id_article >0) {
    				// Article créé. On ajoute le contenu
    				$c = array();
    				$c['titre'] = $creer['titre'];
    				if(isset($creer['soustitre'])) $c['soustitre'] = $creer['soustitre'];
    				$c['composition'] = $creer['xObjet'];
    				$c['statut']='publie';
    				article_modifier($id_article, $c);
    				// On force le statut à 'publie'
    				/* 
    				$c = array();
    				$c['statut']='publie';
    				$err = article_instituer($id_article, $c);
    				*/
    				// Tout ok.
    				$ordre['id'] = $id_article;
    				$ordre['pret'] = true;
    			} else {
    				// Echec creation
    				$ordre['erreur'] = _T('biodiv:msg_echec_creation_article');
    			}
    		} else {
    			$ordre['erreur'] = _T('biodiv:msg_echec_auteur_ou_rubrique');	
    		}
    	    } else {
    	    	    $ordre['erreur'] = _T('biodiv:msg_ordre_incomplet2');
    	    }
    	
	return $ordre;		
}

function ordreCreerObservation($creer)
{	
    $ordre = array();
	$ordre['pret'] = false;
	
	if(	    isset($creer['espece'])
    	and isset($creer['date'])
    	and isset($creer['lat'])
    	and isset($creer['lng'])
    	and isset($creer['commune'])
    	and isset($creer['insee'])
    	) {
    	  $quantite = intval((string)$creer['quantite']);
    	  $quantite = ($quantite ==0 ) ? 1 : $quantite;
    	  
    	  $id_observation = objet_inserer('observation');
    	  if($id_observation>0) {
    	  	  // Article créé. On ajoute le contenu
    	  	  $c = array();	  	  
    		  $c['espece'] = strlen($creer['espece'])?$creer['espece']:$creer['binomial'];
    		  $c['titre'] = isset($creer['titre'])?$creer['titre']:$c['espece'];
    		  $c['quantite'] = $quantite;
    		  $c['discret'] = isset($creer['discret'])?$creer['discret']:"non";
    		  $c['lat'] = $creer['lat'];
    		  $c['lng'] = $creer['lng'];
    		  $c['date_obs'] = $creer['date'];   
    		  if(isset($creer['pjour'])) {
    		  	  $c['pjour'] = $creer['pjour'];
    		  }
    		  if(isset($creer['denombrement'])) {
    		  	  $c['denombrement'] = $creer['denombrement'];
    		  }
    		  if(isset($creer['adresse'])) {
    		  	  $c['adresse'] = $creer['adresse'];
    		  }
    		  $c['commune'] = $creer['commune'];
    		  $c['insee'] = $creer['insee'];
    		  if(isset($creer['descriptif'])) {
    		  	  $c['descriptif'] = $creer['descriptif'];
    		  }
    		  //$c['statut']='prepa';
    		  $err = objet_modifier('observation',$id_observation,$c);
    		  $ordre['id'] = $id_observation;
    		  $ordre['pret'] = true;
    	  } else {
    		// Echec creation
    		$ordre['erreur'] = _T('biodiv:msg_echec_creation_observation');
    	  }  
    } else {
    	$ordre['erreur'] = _T('biodiv:msg_ordre_incomplet3');
    }
    return $ordre;
}

function executerUpdate($ordre)
{
	if(!$ordre['pret']) return;
	if($ordre['type']=='nop') return;
	if($ordre['skip']) return;
	
	if($ordre['where']) { // C'est un update
		// Concerne les liens uniques représentés comme champs de la table principale
		sql_updateq($ordre['table'],$ordre['values'],$ordre['where']);
	} else { // c'est un insert
		// Concerne la création des objets principaux
		// et celles des liens gérés dans une table secondaire.
		if($ordre['type'] == 'delier') {
			// Si c'est un ordre pour délier, il faut supprimer l'entrée
			sql_delete($ordre['table'],$ordre['nowhere']);
		} else {		
			// sinon (lier ou créer) on crée une entrée.
			sql_insertq($ordre['table'],$ordre['values']);
		}
	}
	
	if( isset($ordre['event']) 
		and is_array($ordre['event']) 
		and ($ordre['type']!='delier') 
		) {
		// C'est un ordre de lien d'observation
		pipeline('apres_lien_observation',$ordre['event']);
	}	

	foreach($ordre['invalideur'] as $invalideur) {
		suivre_invalideur($invalideur);
	}
}
	
// Gestion des erreurs XML

function erreur_libxml($error)
{
    $return = "<br/>\n";
    switch ($error->level) {
        case LIBXML_ERR_WARNING:
            $return .= "<b>LIBXML: Attention $error->code</b>";
            break;
        case LIBXML_ERR_ERROR:
            $return .= "<b>LIBXML: Erreur $error->code</b>";
            break;
        case LIBXML_ERR_FATAL:
            $return .= "<b>LIBXML: Erreur fatale $error->code</b>";
            break;
    }
    $return .= ", ligne <b>$error->line</b> : ";
    $return .= trim($error->message) . "<br/>";
    return $return;
}

function erreurs_libxml() {
    $errors = libxml_get_errors();
    $msg="";
    foreach ($errors as $error) {
        $msg .= erreur_libxml($error);
    }
    libxml_clear_errors();
    return $msg;
}
