Subversion Repositories eFlore/Projets.eflore-projets

Rev

Rev 1109 | Blame | Compare with Previous | Last modification | View Log | RSS feed

<?php
 /**
 * Description :
 * Classe NomsVernaculaires.php fournit une liste de noms vernaculaires et leur liaison à la bdtfx
 * Le but étant de fournir un ensemble minimal d'information comprenant :
 * un identifiant (numérique ou alphanumérique sous forme de ChatMot si possible), un nom, une langue et
 * une relation avec un taxon de la bdtfx.
 * Si l'url finit par /noms-vernaculaires on retourne une liste de noms (seulement les 100 premières par défaut).
 * L'url peut contenir des paramètres optionnels passés après le ? : /observations?param1=val1&param2=val2&...
 *
 * ATTENTION : /attributions groupe par taxon, le nombre de résultats est donc
  * inférieur ou égal au nombre demandé par navigation.limite
 * 
 * Les paramètres de requête disponibles sont : masque, masque.code, masque.nom, masque.region , recherche,
 * distinct, retour.format, navigation.depart et navigation.limite.
 *
 * Encodage en entrée : utf8
 * Encodage en sortie : utf8
 * @package framework-v3
 * @author Delphine Cauquil <delphine@tela-botanica.org>
 * @author Jennifer Dhé <jennifer.dhe@tela-botanica.org>
 * @license GPL v3 <http://www.gnu.org/licenses/gpl.txt>
 * @license CECILL v2 <http://www.cecill.info/licences/Licence_CeCILL_V2-en.txt>
 * @version 1.0
 * @copyright 1999-${year} Tela Botanica (accueil@tela-botanica.org)
 */

 // Un caractère de concaténation entre le projet et le service.
 // Ce caractère ne doit pas faire partie d'aucun des noms de service ou projet
 define('RES_VAL_SEP', '@');
 define('SPE_INDEX_NVJFL', '_result_ontologies' . RES_VAL_SEP . 'nvjfl');

 class NomsVernaculaires extends Commun {

     static $onto_multi_support = array('conseil_emploi', 'genre');
     static $champ_infos = array(
         'taxon' => array('service' => 'taxons', 'ressource' => 'nt:', 'projet' => 'bdtfx', 'nom' => 'nom_sci',
                          // utilisés par ajouterChampsOntologieLigneResultat()
                          'intitule' => 'taxon.code', // intitulé du champ tel qu'il sera renvoyé en JSON
                          'bdd_champ' => 'num_taxon'), // intitulé du champ tel qu'il est présent dans l'enregistrement MySQL
         'conseil_emploi' => array('service' => 'ontologies', 'ressource' => 'numStatut:', 'projet' => 'nvjfl', 'nom' => 'nom',
                                   'intitule' => 'conseil_emploi', 'bdd_champ' => 'num_statut'),
         'genre' => array('service' => 'ontologies', 'ressource' => 'genreNombre:', 'projet' => 'nvjfl', 'nom' => 'nom',
                          'intitule' => 'genre', 'bdd_champ' => 'num_genre'));

     protected $service = 'noms-vernaculaires';

     /**
      * Permet de stocker la requete formulée : /noms-vernaculaires | /noms-vernaculaires/#id |
      *  /noms-vernaculaires/#id/champ | /noms-vernaculaires/#id/relations
      * Est remplit au cours de l'analyse des ressources (traiterRessources()), par défaut, a la valeur du service.
      * Est utilisée principalement pr déterminer le format du tableau à retourner.   */
     protected $format_reponse = 'noms-vernaculaires';

     /** Variables constituant les parametres de la requete SQL (champ, condition, limit) remplie
      * selon ressources et paramètres */
     protected $requete_champ = array('*');
     protected $requete_condition = '';
     protected $limite_requete = array(
         'depart' => 0,
         'limite' => 100
     );

        /**
         * Vrai tri SQL
         */
        protected $tri;
        protected $tri_ordre = 'asc';

        // wtf ? on trie après avoir exécuté la requête ?
    protected $champ_tri = 'code_langue';
    protected $direction_tri = 'asc';

     /**
      * Indique les champs supplémentaires à retourner
      *  - conseil_emploi = conseil d'emploi du nom vernaculaire
      *  - genre = genre et nombre du nom
      *  - taxon = nom retenu associé à ce nom
      */
     protected $champs_supp = array();

     /**
      * Precise la contenance plus ou moins précise du tableau à retourner :
      *  - min = les données présentes dans la table
      *  - max = les données de la table + les informations complémentaires (pour les identifiants et les codes)
      *  - oss = la liste des nom_sci (uniquement pour noms et taxons) */
     protected $retour_format = 'max';
     /** Valeur du paramètre de requete recherche :
      *  - stricte : le masque est passé tel quel à l'opérateur LIKE.
      *  - etendue : ajout automatique du signe % à la place des espaces et en fin de masque avec utilisation de LIKE.
      *  - floue : recherche tolérante vis-à-vis d'approximations ou d'erreurs (fautes d'orthographe par exemple) */
     protected $recherche;

     /** Permet de stocker le tableau de résultat (non encodé en json) */
     protected $table_retour = array();
     /** Stocke le nombre total de résultats de la requete principale. Est calculée lors de l'assemblage de la requete */
     protected $total_resultat;

     protected $config;

     public function __construct($config) {
         $this->config = is_null($config) ? Config::get('NomsVernaculaires') : $config;
     }

     //+------------------------------------------------------------------------------------------------------+
     // créer une condition en fonction du paramétre
     public function traiterParametres() {
         if (isset($this->parametres) && !empty($this->parametres)) {

             if (isset($this->parametres['recherche']) && $this->parametres['recherche'] != '') {
                 $this->recherche = $this->parametres['recherche'];
             }
             foreach ($this->parametres as $param => $valeur) {
                 switch ($param) {
                     case 'masque' :
                         $this->ajouterFiltreMasque('nom_vernaculaire', $valeur);
                         break;
                     case 'masque.nt' :
                         $this->ajouterFiltreMasque('num_taxon', $valeur);
                         break;
                     case 'masque.nv' :
                         $this->ajouterFiltreMasque('nom_vernaculaire', $valeur);
                         break;
                     case 'masque.lg' :
                         $this->ajouterFiltreMasque('code_langue', $valeur);
                         break;
                     case 'masque.cce' :
                         $this->ajouterFiltreMasque('num_statut', $valeur);
                         break;
                     case 'retour.format' :
                         $this->retour_format = $valeur;
                         break;
                     case 'retour.tri' :
                         $this->tri = $valeur;
                         break;
                     case 'retour.ordre' :
                                                 if (in_array(strtolower($valeur), aray('asc', 'desc'))) {
                                                        $this->tri_ordre = $valeur;
                                                 }
                         break;
                     case 'navigation.depart' :
                         $this->limite_requete['depart'] = $valeur;
                         break;
                     case 'navigation.limite' :
                         $this->limite_requete['limite'] = $valeur;
                         break;
                     case 'retour.champs' :
                         $this->champs_supp = explode(',',$valeur);
                     break;
                     case 'recherche' :
                         break;
                     case 'version.projet' :
                         break;
                     default :
                         $p = 'Erreur dans les paramètres de recherche de votre requête : '.
                             '</br> Le paramètre " '.$param.' " n\'existe pas.';
                             $this->renvoyerErreur(RestServeur::HTTP_CODE_MAUVAISE_REQUETE, $p);
                 }
             }
         }
     }

     public function ajouterFiltreMasque($nom_champ, $valeur) {
         if ($nom_champ == 'num_taxon') { // si il s'agit d'un chiffre
             $this->requete_condition[] = $nom_champ.' = '.$this->getBdd()->proteger($valeur);
         } else {
             if ($this->recherche == 'floue') {
                 $this->requete_condition[] = '(SOUNDEX('.$nom_champ.') = SOUNDEX(\''.$valeur.'\')'
                     .' OR SOUNDEX(REVERSE('.$nom_champ.')) = SOUNDEX(REVERSE(\''.$valeur.'\'))) ';
             } else {
                 if ($this->recherche == 'etendue') {
                     $valeur = '%'.str_replace(' ','% ', $valeur);
                     $valeur .= '%';
                 }
                 $this->requete_condition[] = $nom_champ.' LIKE '.$this->getBdd()->proteger($valeur);
             }
         }
     }

     //+------------------------------------------------------------------------------------------------------+
     // en fonction de la présence des ressources modifie requete_champ et requete_condition
     public function traiterRessources() {
         if (isset($this->ressources) && !empty($this->ressources)) {
             if (isset($this->ressources[0]) && !empty($this->ressources[0])) {
                 $this->traiterRessourceId(); // ajoute condition id=#valeur
                 if (isset($this->ressources[1]) && !empty($this->ressources[1])) {
                     $this->traiterRessourceChamp(); //modifie requete_champ ou requete_condition
                 }
             }
         } else { //rajoute distinct pour ne pas avoir plusieurs fois le même nom
             $this->requete_champ = array('distinct(id)', 'nom_vernaculaire ');
                         $this->requete_champ = array_merge($this->requete_champ, $this->champs_supp);
         }
     }

     //requete : /noms-vernaculaires/#id (ex : /noms-vernaculaires/7)
     public function traiterRessourceId() {
         if (is_numeric($this->ressources[0])) {
             $this->requete_condition[] = ' id = '.$this->getBdd()->proteger($this->ressources[0]);
             $this->format_reponse .= '/id';
         } elseif ($this->ressources[0] == 'attributions') {
             $this->format_reponse .= '/attributions';
         } else {
             $r = 'Erreur dans les ressources de votre requête : </br> La ressource " '.$this->ressources[0].
                 ' " n\'existe pas.';
             $this->renvoyerErreur(RestServeur::HTTP_CODE_MAUVAISE_REQUETE, $r);
         }
     }


     public function traiterRessourceChamp() {
         $this->format_reponse .= '/champ';
         $this->analyserChamp();
     }

    public function analyserChamp() {
        $this->recupererTableConfig('champs_possibles');// s'il y a plusieurs champs correspondant au champ demandé ils sont séparé par des |
        $champs = explode(' ', $this->ressources[1]);
                //$this->requete_champ = array(); // * car absence de mappings
        foreach ($champs as $champ) {
            preg_match('/^([^.]+)(\.([^.]+))?$/', $champ, $match);
            if (isset($this->champs_possibles[$match[1]])) {
                                // wtf?
                //$this->requete_champ[] = str_replace('|', ', ', $this->champs_possibles[$match[1]]);
                                // marche pas, pour chaque champ il faut en retourner un qui a un
                                // autre nom etc. , pas le temps de faire des mappings
                                $this->requete_champ[] = $match[1];
            } elseif (isset($this->champs_possibles[$match[0]])) {
                                // wtf ?
                $this->requete_champ[] = str_replace('|', ', ', $this->champs_possibles[$match[0]]);
            } else {
                $champs_possibles = implode('</li><li>', array_keys($this->champs_possibles));
                $c = 'Erreur dans votre requête : </br> Le champ "'.$champ_possibles.'" n\'existe pas. '.
                     'Les champs disponibles sont : <li>'.$champs_possibles.'</li> et leurs déclinaisons (ex. ".code").';
                $this->renvoyerErreur(RestServeur::HTTP_CODE_MAUVAISE_REQUETE, $c);
            }
        }
    }

         // fait du neuf avec du vieux
        public function assemblerLaRequete() {
                $requete = false;
                if ($this->format_reponse == 'noms-vernaculaires') {
                        // mode liste
                        $requete = $this->assemblerRequeteListe();
                } else {
                        $requete = $this->assemblerRequeteAutre();
                }
                return $requete;
        }

        /**
         * Exécute un astucieux GROUP BY afin que le service retourne ce qu'il
         * annonce qu'il retourne (truc de ouf!) - pour le mode "liste"
         */
        protected function assemblerRequeteListe() {
                $count = $this->recupererTotalResultat();
                $limiteClause = self::formerRequeteLimite( // LIMIT
                        $this->limite_requete['depart'],
                        $count,
                        $this->limite_requete['limite']
                );
        $req = sprintf(
            'SELECT %s, group_concat(num_taxon) as num_taxon, IF(num_statut="",1,0) AS is_null' .
            ' FROM %s WHERE %s GROUP BY id ORDER BY %s is_null ASC, num_statut ASC %s -- %s:%d',

            in_array('*', $this->requete_champ) ? ' * ' : implode(', ', $this->requete_champ),
            $this->table,
            $this->requete_condition ? implode(' AND ', $this->requete_condition) : 'TRUE',
                        $this->tri ? ($this->tri . ' ' . $this->tri_ordre . ', ') : '',
            $limiteClause,
            __FILE__, __LINE__);
                return $req;
        }

         /**
          * Ancien système d'assemblage de requête
          */
        protected function assemblerRequeteAutre() {
        $nolimit = in_array(
            $this->format_reponse,
            array($this->service.'/id', $this->service.'/id/champs'));
        if(!$nolimit) {
            $count = $this->recupererTotalResultat();
            $limiteClause = self::formerRequeteLimite( // LIMIT
                $this->limite_requete['depart'],
                $count,
                $this->limite_requete['limite']);
        }
        $req = sprintf(
            'SELECT %s, IF(num_statut="",1,0) AS is_null' .
            ' FROM %s WHERE %s ORDER BY %s is_null ASC, num_statut ASC %s -- %s:%d',

            in_array('*', $this->requete_champ) ? ' * ' : implode(', ', $this->requete_champ),
            $this->table,
            $this->requete_condition ? implode(' AND ', $this->requete_condition) : 'TRUE',
                        $this->tri ? ($this->tri . ' ' . $this->tri_ordre . ', ') : '',
            $nolimit ? '' : $limiteClause,
            __FILE__, __LINE__);
                return $req;
        }

        //ajout d'une limite seulement pour les listes (pas plus de 100 resultats retournés pr les requetes
        // suivantes : /noms-vernaculaires et /noms-vernaculaires/#id/relations)
        static function formerRequeteLimite(&$depart, $total, $limite) {
        if ($depart > $total) {
                        $depart = $total - $limite < 0 ? 0 : ($total - $limite);
                        return ' LIMIT ' . $depart . ', ' . $limite;
        }
        return ' LIMIT ' . $depart . ', ' . $limite;
        }

        //on récupère le nombre total de résultats de la requete (ex : le nombre d'id contenu dans la liste /noms-vernaculaires)
        public function recupererTotalResultat() {
        $res = $this->getBdd()->recuperer(sprintf(
            'SELECT COUNT(%s) AS nombre FROM %s WHERE %s -- %s:%d',
                        
            $this->format_reponse == 'noms-vernaculaires/attributions' ? 'id' : 'distinct(id)',
            $this->table,
            $this->requete_condition ? implode(' AND ', $this->requete_condition) : 'TRUE',
            __FILE__, __LINE__));

                if (! $res)
            throw new Exception('Données introuvables', RestServeur::HTTP_CODE_RESSOURCE_INTROUVABLE);
        if($res['nombre'] == 0) {
            print json_encode(
                array(
                    "entete" => array(
                        "depart" => $this->limite_requete['depart'],
                        "limite" => $this->limite_requete['limite'],
                        "masque" => $this->recupererMasque(),
                        "total" => 0
                    ),
                    "resultat" => array()
                ));
            die; // die() très dommage (pour phpunit), mais la stack d'imbrication ne nous permet pas de retourner proprement
            }

        $this->total_resultat = $res['nombre'];
        return $res['nombre'];
        }

        //+------------------------------------------------------------------------------------------------------+
        // determine en fct du service appelé (/noms-vernaculaires | /noms-vernaculaires/#id | /noms-vernaculaires/#id/champ |
        // /noms-vernaculaires/#id/relations) le format du tableau à retourner.
        public function retournerResultatFormate($resultat) {
                $this->recupererTableConfig('correspondance_champs');
                switch ($this->format_reponse) {
                        case 'noms-vernaculaires'                               : 
                                $reponse = ($this->retour_format == 'oss') ? $this->formaterEnOss($resultat) : $this->formaterNomsVernaculaires($resultat);                     break;
                        case 'noms-vernaculaires/attributions'  : $reponse = $this->formaterNomsVernaculairesAttributions($resultat);   break;
                        case 'noms-vernaculaires/id'                    : $reponse = $this->formaterNomsVernaculairesId($resultat);                     break;
                        case 'noms-vernaculaires/id/champ'              : $reponse = $this->formaterNomsVernaculairesIdChamp($resultat);        break;
                        default                                                                 :                                                                                                                                       break;
                }
                return $reponse;
        }
        
        public function ajouterJsonEnTeteNV() {
                $table_retour_json['masque'] = $this->recupererMasque();
                $table_retour_json['depart'] = $this->limite_requete['depart'];
                $table_retour_json['limite'] = $this->limite_requete['limite'];
                $table_retour_json['total']  = $this->total_resultat;
                $url = $this->formulerUrl($this->total_resultat, '/noms-vernaculaires');
                if (isset($url['precedent']) && $url['precedent'] != '') {
                        $table_retour_json['href.precedent'] = $url['precedent'];
                }
                if (isset($url['suivant']) && $url['suivant']   != '') {
                        $table_retour_json['href.suivant']   = $url['suivant'];
                }
                return $table_retour_json;
        }

        /**
         * @TODO Ne devrait pas retourner un oblet mais un Array (conserve l'ordre,
         * évite d'écraser des clefs etc.)
         */
        public function ajouterJsonResultatNV($resultat) {
                $resultat_json = array();
                foreach ($resultat as $tab) {
                        $this->table_retour = array();
                        foreach ($tab as $key => $valeur) {
                                if ($valeur != '') {
                                        switch ($key) {
                                                case 'id'                               : $num = $valeur;                                                               break;
                                                case 'nom_vernaculaire' : $this->table_retour['nom'] = $valeur;                 break;
                                                default                                 :                                                                                               break;
                                        }
                                        // champs supplémentaires
                                        if (in_array($key, $this->champs_supp)) {
                                                $this->table_retour[$key] = $valeur;
                                        }
                                }
                        }
                    if ($this->retour_format == 'max') $this->table_retour['href'] = $this->ajouterHref('noms-vernaculaires', $num);
                        $resultat_json[$num] = $this->table_retour;
                }
                return $resultat_json;
        }

        
        public function formaterNomsVernaculaires($resultat) {
                $table_retour_json['entete'] = $this->ajouterJsonEnTeteNV();
                $resultat = $this->hierarchiserResultat($resultat);
                $table_retour_json['resultat'] = $this->ajouterJsonResultatNV($resultat);
                return $table_retour_json;
        }
        
        public function hierarchiserResultat($resultat) {
                //tri recherche floue
                if (isset($this->parametres['masque.nv'])) {
                        return $this->trierRechercheFloue($this->parametres['masque.nv'], $resultat, 'nom_vernaculaire');
                }
                if (isset($this->parametres['masque'])) {
                        return $this->trierRechercheFloue($this->parametres['masque'], $resultat, 'nom_vernaculaire');
                }
                return $resultat;
        }
        
        public function recupererMasque() {
                $tab_masque = array();
                foreach ($this->parametres as $param=>$valeur) {
                        if (strstr($param, 'masque') != false) {
                                $tab_masque[] = $param.'='.$valeur;
                        }
                }
                return implode('&', $tab_masque);
        }
        
        public function formaterEnOss($resultat) {
                $table_nom = array();
                $oss = '';
                foreach ($resultat as $tab) {
                        if (isset($tab['nom_vernaculaire']) ) {
                                if (!in_array($tab['nom_vernaculaire'], $table_nom)) {
                                        $table_nom[] = $tab['nom_vernaculaire'];
                                        $oss [] = $tab['nom_vernaculaire'];
                                }
                        }
                }
                if (isset($this->masque)) $masque = implode('&', $this->masque);
                else $masque = 'Pas de masque';
                return array($masque, $oss);
        }
        
        public function formaterNomsVernaculairesAttributions($resultat) {
                //on remplie la table $table_retour_json['entete']
                $table_retour_json['entete']['masque'] = $this->recupererMasque();
                $table_retour_json['entete']['depart'] = $this->limite_requete['depart'];
                $table_retour_json['entete']['limite'] = $this->limite_requete['limite'];
                $table_retour_json['entete']['total']  = $this->total_resultat;
                $url = $this->formulerUrl($this->total_resultat, '/noms-vernaculaires/attributions');
                if (!empty($url['precedent'])) {
                        $table_retour_json['entete']['href.precedent'] = $url['precedent'];
                }
                if (!empty($url['suivant'])) {
                        $table_retour_json['entete']['href.suivant']   = $url['suivant'];
                }
                $resultat_json = array();
                foreach ($resultat as &$tab) {          
                        $nnv = $tab['num_nom_vernaculaire'];
                        $resultat_json[$nnv]['id'] = $tab['id'];
                        $resultat_json[$nnv]['nom_vernaculaire'] = $tab['nom_vernaculaire'];
                        $resultat_json[$nnv]['langue.code'] = $resultat_json[$nnv]['code_langue'] = $tab['code_langue'];
                        $resultat_json[$nnv]['taxon.code'] = 'bdtfx.nt:'.$tab['num_taxon'];
                        if ($this->retour_format == 'max') {
                                $this->taxons[] = $tab['num_taxon']; // utilisé pour chercher les noms latins plus bas
                                if($this->champs_supp) {
                                        //$resultat_json[$nnv] = $this->ajouterChampsOntologieLigneResultat($tab);
                                        // simple initialisation par copie de la référence de l'original
                                        $resultat_json[$nnv] = &$tab;
                                } else {
                                        $resultat_json[$nnv]['num_taxon'] = $tab['num_taxon'];
                                        $resultat_json[$nnv]['nom_retenu.code'] = $tab['num_taxon'];
                                        $resultat_json[$nnv]['taxon'] = $tab['num_taxon'];
                                        $resultat_json[$nnv]['href'] = $this->ajouterHref('noms-vernaculaires', $tab['id']);
                                }
                        }
                }

                // dans ce cas (particulier?) nous n'avons pour l'heure initialisé qu'une référence
                // vers le tableau de valeurs original
                if ($this->retour_format == 'max' && $this->champs_supp) {
                        // récupérons désormais les ontologies
                        $this->ajouterChampsOntologieLigneTousResultats($resultat_json);
                }

                if ($this->retour_format == 'max') {
                        // Deuxième boucle pour demander tous lestaxons présents en une
                        // fois et les attribuer aux noms car c'est beaucoup plus rapide
                        $noms_sci = $this->recupererNomTaxons();
                        foreach ($resultat_json as $num_nom => &$tab) {
                                $tab = $this->ajouterTaxonsAttributionsLigneResultat($tab, $noms_sci);
                                if($tab == null) {
                                        unset($resultat_json[$num_nom]);
                                }
                        }
                }
                
                $table_retour_json['resultat'] = $resultat_json;
                return $table_retour_json;
        }
        
        /**
         * Ajoute les champs d'ontologie supplémentaires si necéssaire
         * en faisant appels aux web services associés.
         * Les appels peuvent être fait individuellement (pour un couple <ontologie:valeur>) ou bien
         * regroupés, **si le webservice correspondant le supporte**.
         *
         * Nous disposons à ce jour de 3 (trois) webservices d'ontologies correspondant aux noms vernaculaires (cf $champ_infos)
         * Mais 2 d'entre eux sont identiques, il s'agit de /nvjfl/ontologies/. Or ce webservice supporte le multi-critère.
         * Nous pouvons donc factorisé l'appel pour "conseil_emploi" et "genre", mais pas pour "taxon".
         *
         * @param array in/out $resultats: tous les résultats
         */
        public function ajouterChampsOntologieLigneTousResultats(&$resultats) {
                $champs_sup = array_intersect($this->champs_supp, array_keys(self::$champ_infos));

                // La regroupement des toutes les valeurs recherchées (pour tous les
                // résultats), pour "les" onotologies supportant le multi-critère est effectué ci-dessous.
                // Dans les faits ce n'est le cas que pour nvjfl.
                $ontologieParamPending = self::NvjflOntologieIndex($resultats, $champs_sup);
                $this->NvjflOntologieExpand($ontologieParamPending);
                self::NvjflOntologieCombine($resultats);

                // pour les ontologies multi-critères, on vient de le régler ci-dessus
                $champs_sup = array_diff($champs_sup, self::$onto_multi_support);


                // ici, $champs_sup ne peut contenir, au plus, que "taxon".
                // code historique:
                foreach($champs_sup as $cle) {
                        $champs_supplementaires = self::$champ_infos[$cle];
                        // extrait, depuis un élément de $champ_infos:
                        // $service, $ressource, $projet, $nom, $intitule, $bdd_champ
                        extract($champs_supplementaires);

                        foreach ($resultats as &$tab) {
                                $valeur_recherche = $tab[$bdd_champ];
                                if(!trim($valeur_recherche)) continue;

                                $url = $this->ajouterHrefAutreProjet($service, $ressource, $valeur_recherche, $projet);
                                $tab[$intitule] = $this->chercherSignificationCode($url, $nom);
                        }
                }
        }

        /* Récupère les valeurs recherchées pour une liste de résultats, (plus ou moins)
           spécifiquement au service d'Ontologies de NVJFL.
           Aggrège les valeurs dans le tableau retourné.
           Une référence vers l'index du tableau (NULL pour l'instant) est laissée dans
           un élément du résultat. */
        static function NvjflOntologieIndex(&$resultats, $champs_sup) {
                // nous ne supportons le multi-critère que sur les ontologies nvjfl, et nous
                // avons précisé celles qui sont concernées dans self::$onto_multi_support
                $champs_sup = array_intersect($champs_sup, self::$onto_multi_support);
                $ontologieParamPending = Array();
                foreach($resultats as &$resultat) {
                        foreach($champs_sup as $v) {
                                // de cet extract() nous n'utilisons que $bdd_champ et $ressource
                                extract(self::$champ_infos[$v]);
                                if(!isset($resultat[$bdd_champ])) continue;

                                $valeur_recherche = $resultat[$bdd_champ];
                                if(!trim($valeur_recherche)) continue;

                                // XXX: $ressource contient déjà ':' comme suffixe
                                $critere = $ressource . $valeur_recherche;
                                $ontologieParamPending[$critere] = NULL;
                                // placeholder pour le résultat
                                $resultat[SPE_INDEX_NVJFL][$v][$critere] =
                                        &$ontologieParamPending[$critere];
                        }
                }
                return $ontologieParamPending;
        }

        // TODO: switch to static si il peut en être de même pour ajouterHrefAutreProjet()
        /* À partir d'un aggrégat des critère de requêtes d'ontologies, spécifiques à NVJFL,
           créé une URL multi-critère.
           Celle-ci, dans ce cas précis, n'est que la concaténation, par des virgules,
           des couples <ressource:ValeurRecherchée>.
           L'URL est appelée et la valeur correspondante est remplacée dans $criteres_requete.

           Note: dans le cadre du tryptique index/expand/combine pour lequel cette fonction existe,
           la valeur est référencée par un élément d'une ou plusieurs lignes de $resultat correspondantes.
           Celle(s)-ci sera[ont] donc changée(s) dans la foulée. */
        public function NvjflOntologieExpand(&$criteres_requete) {
                // équivalent spécifique de ajouterHrefAutreProjet()
                $valeurs_requises = implode(',', array_keys($criteres_requete));
                // en vérité, nous ne supportons ceci ici que pour nvjfl et non n'importe quel url_service
                $url = Config::get('url_service').'/ontologies/'.$valeurs_requises;
                $val = $this->consulterHref($url);

                // TODO, le webservice d'ontologies devrait être modifié pour retourner un tableau
                // indexé par critère requesté à *CHAQUE* fois, y compris lorsque 1 seul critère est
                // demandé.
                if(array_key_exists('id', $val) && count($criteres_requete) == 1) {
                        $k = key($criteres_requete);
                        $criteres_requete[$k] = $val;
                        return;
                }

                // subtilité, cette affectation modifie par conséquent les valeurs dans
                // $resultats[X][SPE_INDEX_NVJFL]
                // dont la référence pointe toujours sur $v
                foreach($val as $k => $v) $criteres_requete[$k] = $val->$k;
        }

        /* Fonction finale du tryptique: réordonne les valeurs obtenues auprès du web-service
           NVJFL en adéquation avec les champs attendus en sortie.
           Dès l'indexation des critères, nous avons associé une (ou plusieurs) référence(s) du
           tableau de résultats vers le tableau de retour des ontologies à l'aide d'un index
           particulier l'index SPE_INDEX_NVJFL qui contient comme élément(s)
           un ou plusieurs ontologies (les indexes de self::$champ_infos) qui elles-mêmes contiennent
           une ou plusieurs valeurs représentant les valeurs recherchées appartement à cette ontologies.
           Celui-ci est supprimé après avoir été correctement copié. */
        /**
         * @param array in/out $resultats: tous les résultats
         * @param array in $critere: tableau des ontologies:valeur demandées, de la forme [ numStatut:1, genreNombre:11, ... ]
         */
        static function NvjflOntologieCombine(&$resultats) {
                foreach($resultats as &$resultat) {
                        if(!array_key_exists(SPE_INDEX_NVJFL, $resultat)) continue;

                        /* Note: la complétude d'un résultat peut dépendre de plusieurs ontologies différentes,
                           d'où cette boucle. Cependant une seule valeur sera demandé pour cette ontologie, c'est pourquoi
                           $resultat[SPE_INDEX_NVJFL][$onto_name], s'il existe, ne contiendra toujours qu'un seul élément.
                           Puisque par définition un résultat contenant des valeurs d'ontologie n'aura jamais qu'un seul et unique
                           attribut num_genre (ou num_statut, ou autre) */
                        foreach(self::$onto_multi_support as $onto_name) {
                                if(!array_key_exists($onto_name, $resultat[SPE_INDEX_NVJFL])) continue;

                                /* $onto_name est un nom d'ontologie (l'une des clefs, parmi conseil_emploi et genre,
                                   cf la boucle sur $champs_sup dans  NvjflOntologieIndex()
                                   de cet extract() nous n'utilisons que $intitule et $nom */
                                extract(self::$champ_infos[$onto_name]);

                                // equivalent de l'affectation finale de chercherSignificationCode()
                                // (utilisé lors de recherches d'ontologies en mono-critère)
                                // XXX: PHP-5.3 pas de récupération d'attribut sur fonction
                                $r = current($resultat[SPE_INDEX_NVJFL][$onto_name]);
                                $resultat[$intitule] = $r->$nom;

                                // XXX: certes nous pourrions nous contenter du unset() final
                                unset($resultat[SPE_INDEX_NVJFL][$onto_name]);
                        }
                        unset($resultat[SPE_INDEX_NVJFL]);
                }
        }

        /**
         * Ajoute les champs d'ontologie supplémentaires si necéssaire
         * en faisant appels aux web services associés
         * @param array $ligne_resultat
         * 
         * @return array la ligne modifiée
         */
        public function ajouterChampsOntologieLigneResultat($ligne_resultat) {
                foreach(self::$champ_infos as $cle => $champs_supplementaires) {
                        if(!in_array($cle, $this->champs_supp)) continue;
                        // extrait, depuis un élément de $champ_infos:
                        // $service, $ressource, $projet, $nom, $intitule, $bdd_champ
                        extract($champs_supplementaires);
                        $valeur_recherche = $ligne_resultat[$bdd_champ];
                        if(!trim($valeur_recherche)) continue;
                        $url = $this->ajouterHrefAutreProjet($service, $ressource, $valeur_recherche, $projet);
                        $ligne_resultat[$intitule] = $this->chercherSignificationCode($url, $nom);
                }
                return $ligne_resultat;
        }
        
        /**
         * Fonction qui ajoute les attributions à une ligne de résultats
         *
         * @param array $ligne_tableau_resultat
         * @param array $nom_sci
         */
        public function ajouterTaxonsAttributionsLigneResultat(&$ligne_tableau_resultat, &$noms_sci) {
                if (isset($noms_sci[$ligne_tableau_resultat['num_taxon']])) {
                        $ligne_tableau_resultat['nom_retenu.code'] = $noms_sci[$ligne_tableau_resultat['num_taxon']]['id'];
                        $ligne_tableau_resultat['taxon'] = $noms_sci[$ligne_tableau_resultat['num_taxon']]['nom_sci'];
                } else {
                        $ligne_tableau_resultat = null;
                }
                return $ligne_tableau_resultat;
        }
        
        private function trierLigneTableau($a, $b) {
                $retour = 0;
                
                if ($a[$this->champ_tri] == $b[$this->champ_tri]) {
                        $retour = 0;
                }
                                
                if($this->champ_tri == 'code_langue') {
                        if ($a[$this->champ_tri] == 'fra' && $b[$this->champ_tri] != 'fra') {
                                $retour = ($this->direction_tri == 'asc') ? -1 : 1;
                        } else if ($a[$this->champ_tri] != 'fra' && $b[$this->champ_tri] == 'fra') {
                                $retour = ($this->direction_tri == 'asc') ? 1 : -1;
                        } else {
                                $retour = $this->comparerChaineSelonDirectionTri($a[$this->champ_tri], $b[$this->champ_tri]);
                        }
                } else {
                        $retour = $this->comparerChaineSelonDirectionTri($a[$this->champ_tri], $b[$this->champ_tri]);
                }
                return $retour;
        }
        
        private function comparerChaineSelonDirectionTri($a, $b) {
                if($this->direction_tri == 'asc') {
                        return ($a < $b) ? -1 : 1;
                } else {
                        return ($a > $b) ? -1 : 1;
                }
        }
        
        // formatage de la reponse /id ss la forme
        // id, nom_vernaculaire, attributions
        // langue
        // num_nom (correspond à un taxon bdtfx)
        public function formaterNomsVernaculairesId($resultat) {
                foreach ($resultat as $taxon) { // pour chaque attribution à un taxon bdtfx
                        // on crée les variables qui serviront de clés et on les enléves du tableau
                        $num_nom = $taxon['num_nom_vernaculaire']; // unique pour un trinôme id, langue, taxon
                        unset($taxon['num_nom_vernaculaire']);
                        $langue = $taxon['code_langue'];
                        unset($taxon['code_langue']);

                        foreach ($this->correspondance_champs as $key => $correspondance) { // ordonne les infos pour affichage
                                if (isset($taxon[$key]) && $taxon[$key] != "") {
                                        $this->afficherDonnees($correspondance, $taxon[$key], $langue, $num_nom);
                                }
                        }
                        foreach ($taxon as $key => $valeur) { // rajoute les champs non prévus dans l'api
                                if (!isset($this->correspondance_champs[$key]) && $valeur != "") {
                                        $this->afficherDonnees($key, $valeur, $langue, $num_nom);
                                }
                        }
                        if ($this->retour_format == 'max') $this->chargerBiblio($num_nom, $langue);
                }
                if ($this->retour_format == 'max') $this->afficherTaxons(); // va chercher les noms de tous les taxons
                unset($this->table_retour['href']);
                return $this->table_retour;
        }

        public function afficherDonnees($champ, $valeur, $langue = '', $num_nom = '') {
                if ($champ == 'id' || $champ == 'nom_vernaculaire') {
                        $this->table_retour[$champ] = $valeur;
                } elseif (preg_match('/^(.*)\.code$/', $champ, $match)) {
                                switch ($match[1]) {
                                        case 'taxon'    : if ($this->retour_format == 'max') {$this->taxons[$num_nom] = $valeur;}
                                                $this->afficherPointCode($match[1], $langue, $num_nom, $valeur);        break;
                                        case 'langue'   : //$this->afficherPointCode($match[1], 'iso-639-3', 'langues', $valeur);
                                                break;
                                        case 'genre'    : $this->afficherPointCode($match[1], $langue, $num_nom, $valeur);      break;
                                        case 'conseil_emploi'   : $this->afficherPointCode($match[1], $langue, $num_nom, $valeur);      break;
                                        default : break;
                                }

                } elseif ($langue != '') {
                        $this->table_retour['attributions'][$langue][$num_nom][$champ] = $valeur;
                } else {
                        $this->table_retour[$champ] = $valeur;
                }
        }

        public function afficherPointCode($nomChamp, $langue, $num_nom, $valeur) {
                if (isset(self::$champ_infos[$nomChamp])) {
                        extract(self::$champ_infos[$nomChamp]);
                }

                if ($this->retour_format == 'max') {
                        $url = $this->ajouterHrefAutreProjet($service, $ressource, $valeur, $projet);
                        if ($service == 'taxons') {
                                $code_valeur = '';
                                $this->table_retour['attributions'][$langue][$num_nom]['nom_retenu.code'] = $code_valeur;
                        } else {
                                $code_valeur = $this->chercherSignificationCode($url, $nom);
                        }
                        if ($projet != '') $projet .= '.';
                        $this->table_retour['attributions'][$langue][$num_nom][$nomChamp] = $code_valeur;
                        $this->table_retour['attributions'][$langue][$num_nom][$nomChamp.'.code'] = $projet.$ressource.$valeur;
                        $this->table_retour['attributions'][$langue][$num_nom][$nomChamp.'.href'] = $url;
                } else {
                        if ($projet != '') $projet .= '.';
                        $this->table_retour['attributions'][$langue][$num_nom][$nomChamp.'.code'] = $projet.$ressource.$valeur;
                }
        }

        public function chercherSignificationCode($url, $nom) {
                if (isset($this->signification_code[$url])) {
                        $valeur = $this->signification_code[$url];
                } else {
                        $res = $this->consulterHref($url);
                        $valeur = $res->$nom;
                        $this->signification_code[$url] = $valeur;
                }
                return $valeur;
        }

        /**
         * Apparemment, regroupe les noms vernaculaires par taxons (élimine donc
         * certains noms) - j'ai bon ?
         */
        public function afficherTaxons() {
                $resultat = $this->recupererNomTaxons();
                foreach ($this->table_retour['attributions'] as $code_langue=>$langue) {
                        foreach ($langue as $num_nom=>$taxon) {
                                $num_tax = ltrim($taxon['taxon.code'], 'bdtfx.nt:');
                                if (isset($resultat[$num_tax])) {
                                        $this->table_retour['attributions'][$code_langue][$num_nom]['nom_retenu.code'] = $resultat[$num_tax]['id'];
                                        $this->table_retour['attributions'][$code_langue][$num_nom]['taxon'] = $resultat[$num_tax]['nom_sci'];
                                }
                        }
                }
        }
        
        public function recupererNomTaxons() {
                $taxons = array_unique($this->taxons);
                // @TODO attention à la limite de taille de l'URL - faire un POST plutôt
                $url = Config::get('url_service_base').'bdtfx/taxons?navigation.limite=500&ns.structure=au&masque.nt='.implode(',', $taxons);
                $res = $this->consulterHref($url);
                foreach ($res->resultat as $id => $taxon) {
                        $resultat[$taxon->num_taxonomique]['id'] = 'bdtfx.nn:'.$id;
                        $resultat[$taxon->num_taxonomique]['nom_sci'] = $taxon->nom_sci_complet;
                }
                return $resultat;
        }

        public function formaterNomsVernaculairesIdChamp($resultat) {
                $this->table_retour['id'] = $this->ressources[0];
                $champs = explode(' ', $this->ressources[1]);
                if (in_array('attributions', $champs) != false) {
                        $this->formaterNomsVernaculairesId($resultat);
                        unset($this->table_retour['nom_vernaculaire']);
                } else {
                        $champ_attributions = array('num_taxon', 'zone_usage', 'num_statut', 'num_genre', 'notes');
                        foreach ($resultat as $taxon) {
                                foreach ($taxon as $key=>$valeur) {
                                        if ($key == 'code_langue' && in_array('langue', $champs) != false) {
                                                $this->table_retour['attributions']['langue'][] = $valeur;
                                        } elseif (in_array($key, $champ_attributions) != false) {
                                                $this->afficherPoint($this->correspondance_champs[$key] , $valeur, $taxon['code_langue'], $taxon['num_nom_vernaculaire']);
                                        } elseif (in_array($key, $champs) != false) {
                                                $this->table_retour[$key] = $valeur;
                                        }
                                }
                                if (in_array('biblio', $champs) != false) $this->chargerBiblio($taxon['num_nom_vernaculaire'], $taxon['code_langue']);
                        }
                        if (in_array('biblio', $champs) != false && array_search('biblio.num_ref', $this->table_retour) != false) $this->table_retour['biblio'] = null;
                }
                return $this->table_retour;
        }

        /**
         * Quelqu'un sait-il ce que fait cette foutue fonction ? :-/
         */
        public function afficherPoint($champ, $valeur, $langue, $num_nom) {
                $aMatche = preg_match('/^(.*)\.code$/', $champ, $match);
                if ($aMatche !== false && $aMatche !== 0) {
                        $champ = $match[1];
                        if (isset(self::$champ_infos[$champ])) {
                                extract(self::$champ_infos[$champ]);
                                $url = $this->ajouterHrefAutreProjet($service, $ressource, $valeur, $projet);
                                $projet .= '.';
                        }

                        $champs = explode(' ', $this->ressources[1]);
                        if (in_array($champ.'.*', $champs) !== false && isset($projet)) {
                                $this->table_retour['attributions'][$langue][$num_nom][$champ.'.code'] = $projet.$ressource.$valeur;
                                $this->table_retour['attributions'][$langue][$num_nom][$champ.'.href'] = $url;
                        }
                        if (in_array($champ.'.code', $champs) !== false && isset($projet)) {
                                $this->table_retour['attributions'][$langue][$num_nom][$champ.'.code'] = $projet.$ressource.$valeur;
                        }
                        if (in_array($champ.'.href', $champs) !== false && isset($projet)) {
                                $this->table_retour['attributions'][$langue][$num_nom][$champ.'.href'] = $url;
                        }
                        if (in_array($champ, $champs) !== false) {
                                if (isset($url)) {
                                        $this->table_retour['attributions'][$langue][$num_nom][$champ] = $this->chercherSignificationCode($url, $nom);
                                } else {
                                        $this->table_retour['attributions'][$langue][$champ] = $valeur;
                                }
                        }
                }
        }

        public function afficherLangue($nomChamp, $projet, $service, $valeur, $ressource = '', $nom = 'nom') {
                if ($this->retour_format == 'max') {
                                $this->table_retour['attributions'][$nomChamp] = $nom;
                                $this->table_retour['attributions'][$nomChamp.'.code'] = $projet.$ressource.$valeur;
                                $this->table_retour['attributions'][$nomChamp.'.href'] = $url;
                } else {
                        $this->table_retour['attributions'][$nomChamp.'.code'] = $projet.$ressource.$valeur;
                }
        }

        public function chargerBiblio($num_nom, $langue) {
                list($table, $version) = explode('_v',$this->table);
                $requete = "SELECT b.*, lb.notes FROM nvjfl_lien_biblio_v$version lb, nvjfl_biblio_v$version b ".
                                        "WHERE b.num_ref = lb.num_ref AND lb.num_nom = '$num_nom' ;";
                $resultat = $this->getBdd()->recupererTous($requete);

                 if ($resultat == '') { //cas ou la requete comporte des erreurs
                        $r = 'La requête SQL formée comporte une erreur !!';
                        $this->renvoyerErreur(RestServeur::HTTP_CODE_RESSOURCE_INTROUVABLE, $r);
                        Debug::printr($requete);
                 } elseif ($resultat) {
                        foreach ($resultat as $res) {
                                foreach ($res as $cle => $valeur) {
                                        if ($valeur !== "") {
                                                $this->table_retour['attributions'][$langue][$num_nom]['biblio.'.$cle] = $valeur;
                                        }
                            }
                        }
                }
        }

}
?>