* @copyright 2013 Tela-Botanica * @license http://www.cecill.info/licences/Licence_CeCILL_V2-fr.txt Licence CECILL * @license GPL v3 */ define('SEPARATEUR_IMAGES', " / "); define('PREFIX_CHAMPS_ETENDUS', "ext:"); // utilisé par formaterUrlUser() [ nécessaire pour le widget d'export) define('USER_BASEURL', 'http://www.tela-botanica.org/profil:%d'); Class FormateurGroupeColonne { // cache pour les données des fonctions static $cache = Array(); // test sur la table cel_references, mis à TRUE si la table existe static $is_table = false; // les groupes de champs utilisables static $fieldGroups = array( 'standard', 'avance', 'etendu', 'baseflor', 'auteur' ); // les données baseflor à récupérer: colonnes présentes dans cel_references // et intitulés associés static $baseflor_col = array( "ve_lumiere" => "Lumière", "ve_temperature" => "Température", "ve_continentalite" => "Continentalité", "ve_humidite_atmos" => "Humidité Atmosphérique", "ve_humidite_edaph" => "Humidité", "ve_reaction_sol" => "Réaction (pH)", "ve_nutriments_sol" => "Nutriments", "ve_salinite" => "Salinité", "ve_texture_sol" => "Texture" , "ve_mat_org_sol" => "Matière Organique", "catminat_code" => "Code Catminat", "syntaxon" => "Syntaxon", ); // TODO: dirty, ordre des champs étendus... souhaité pour florilèges: static $ordre_champ_etendus_Florileges = array( "personneStructure", "personneService", "personneFonction", "adresse", "latitudeDebutRue", "longitudeDebutRue", "latitudeFinRue", "longitudeFinRue", "typoUrbaine", "revetementSol", "presenceZoneVegetalise", "hauteurBatimentAvoisinant", "intensiteGestion", "periodiciteTraitementPhyto", "dateArretTraitementPhyto", "itineraireGestion", "dateDerniereIntervention", "hauteurPlante", "resistanceTraitementPhyto", "vitesseCroissance", "perceptionTechnicien", "perceptionRiverainMauvaise", ); static function colGroupsValidation($groupe_de_champs = 'standard,avance') { if(! $groupe_de_champs) return FALSE; if(is_string($groupe_de_champs)) { $groupe_de_champs = array_flip(explode(',', $groupe_de_champs)); } elseif(is_array($groupe_de_champs)) { $groupe_de_champs = array_flip($groupe_de_champs); } else { return NULL; } $groupe_de_champs = array_intersect_key(array_flip(self::$fieldGroups), $groupe_de_champs); if(!$groupe_de_champs) return FALSE; // toujours ajouter standard $groupe_de_champs['standard'] = TRUE; return implode(',', array_keys($groupe_de_champs)); } /* * @param $fieldSets: un liste de noms de colonnes ou de sets de colonnes * séparés par des virgules * eg: "espece" ou "champs-etendus", ... * * @return: un tableau associatif déjà ordonné * clé: abbrev [machine-name] de la colonne (eg: "espece" ou "mot-clef") * valeur: des données relative à cette colonne, cf GenColInfo * * Si la colonne n'utilise pas de fonction de récupération particulière * (ie: si le champ exportés [ou importé] correspond exactement au champ dans la base de donnée) * Alors 'abbrev' doit avoir la même valeur que le nom de la colonne dans la table mysql `cel_obs`. * */ static function nomEnsembleVersListeColonnes($groupe_de_champs = 'standard') { if(! $groupe_de_champs) $groupe_de_champs = 'standard'; if(is_string($groupe_de_champs)) { $groupe_de_champs = array_flip(explode(',', $groupe_de_champs)); } elseif(is_array($groupe_de_champs)) { $groupe_de_champs = array_flip($groupe_de_champs); } else { return NULL; } $groupe_de_champs = array_intersect_key(array_flip(self::$fieldGroups), $groupe_de_champs); if(!$groupe_de_champs) return NULL; $colonnes = Array(); if(isset($groupe_de_champs['standard'])) { $colonnes += Array( 'nom_sel' => self::GenColInfo(Array('abbrev' => 'nom_sel', 'nom' => 'Espèce')), 'nom_sel_nn' => self::GenColInfo(Array('abbrev' => 'nom_sel_nn', 'nom' => 'Numéro nomenclatural', 'importable' => FALSE)), 'nom_ret' => self::GenColInfo(Array('abbrev' => 'nom_ret', 'nom' => 'Nom retenu', 'importable' => FALSE)), 'nom_ret_nn' => self::GenColInfo(Array('abbrev' => 'nom_ret_nn', 'nom' => 'Numéro nomenclatural nom retenu', 'importable' => FALSE)), 'nt' => self::GenColInfo(Array('abbrev' => 'nt', 'nom' => 'Numéro taxonomique', 'importable' => FALSE)), 'famille' => self::GenColInfo(Array('abbrev' => 'famille', 'nom' => 'Famille', 'importable' => FALSE)), 'nom_referentiel' => self::GenColInfo(Array('abbrev' => 'nom_referentiel', 'nom' => 'Referentiel taxonomique')), 'zone_geo' => self::GenColInfo(Array('abbrev' => 'zone_geo', 'nom' => 'Commune')), 'ce_zone_geo' => self::GenColInfo(Array('abbrev' => 'ce_zone_geo', 'nom' => 'Identifiant Commune', 'fonction' => 'convertirCodeZoneGeoVersDepartement')), 'date_observation' => self::GenColInfo(Array('abbrev' => 'date_observation', 'nom' => 'Date', 'fonction' => 'formaterDate')), 'lieudit' => self::GenColInfo(Array('abbrev' => 'lieudit', 'nom' => 'Lieu-dit')), 'station' => self::GenColInfo(Array('abbrev' => 'station', 'nom' => 'Station')), 'milieu' => self::GenColInfo(Array('abbrev' => 'milieu', 'nom' => 'Milieu')), 'commentaire' => self::GenColInfo(Array('abbrev' => 'commentaire', 'nom' => 'Notes')), 'latitude' => self::GenColInfo(Array('abbrev' => 'latitude', 'nom' => 'Latitude', 'extra' => 1, 'fonction' => 'trim0')), 'longitude' => self::GenColInfo(Array('abbrev' => 'longitude', 'nom' => 'Longitude', 'extra' => 1, 'fonction' => 'trim0')), 'altitude' => self::GenColInfo(Array('abbrev' => 'altitude', 'nom' => 'Altitude', 'extra' => 1)), // pas de trim0 car INT(5) en DB 'geodatum' => self::GenColInfo(Array('abbrev' => 'geodatum', 'nom' => 'Référentiel Géographique', 'extra' => 1, 'importable' => FALSE)), ); } if(isset($groupe_de_champs['avance'])) { $colonnes += array( // TODO: importable = FALSE car pas de merge de données importées 'ordre' => self::GenColInfo(Array('abbrev' => 'ordre', 'nom' => 'Ordre', 'extra' => 1, 'importable' => FALSE)), 'id_observation' => self::GenColInfo(Array('abbrev' => 'id_observation', 'nom' => 'Identifiant', 'extra' => 1, 'importable' => FALSE)), 'mots_cles_texte' => self::GenColInfo(Array('abbrev' => 'mots_cles_texte', 'nom' => 'Mots Clés', 'extra' => 1)), 'date_creation' => self::GenColInfo(Array('abbrev' => 'date_creation', 'nom' => 'Date Création', 'extra' => 1, 'importable' => FALSE)), 'date_modification' => self::GenColInfo(Array('abbrev' => 'date_modification', 'nom' => 'Date Modification', 'extra' => 1, 'importable' => FALSE)), // rappel transmission = 1, signifie simplement "public" // des données importées peuvent être d'emblée "publiques" // "importable" = TRUE 'transmission' => self::GenColInfo(Array('abbrev' => 'transmission', 'nom' => 'Transmis', 'extra' => 1, 'fonction' => 'boolOuiNon')), 'date_transmission' => self::GenColInfo(Array('abbrev' => 'date_transmission', 'nom' => 'Date Transmission', 'extra' => 1, 'importable' => FALSE)), 'abondance' => self::GenColInfo(Array('abbrev' => 'abondance', 'nom' => 'Abondance', 'extra' => 1)), 'certitude' => self::GenColInfo(Array('abbrev' => 'certitude', 'nom' => 'Certitude', 'extra' => 1)), 'phenologie' => self::GenColInfo(Array('abbrev' => 'phenologie', 'nom' => 'Phénologie', 'extra' => 1)), // XXX: getImages() dépend du contexte de Cel, et doit être appelée comme cas particulier // cf ExportXLS::traiterLigneObservation() 'images' => self::GenColInfo(Array('abbrev' => 'images', 'nom' => 'Image(s)', 'extra' => 1, 'fonction_data' => NULL /* cas particulier 'getImages' */, 'importable' => TRUE, //'preload' => array(__CLASS__, 'getImages_preload')//TODO )), /* 'nom_commun' => self::GenColInfo(Array('abbrev' => 'nom_commun', 'nom' => 'Nom Commun', 'extra' => 1, 'fonction_data' => 'getNomCommun', 'importable' => FALSE), 'nom-commun' => self::GenColInfo(Array('abbrev' => 'nom-commun', 'nom' => 'Nom Commun', 'extra' => 1, 'fonction_data' => 'getNomCommun_v2'), 'nom-commun' => self::GenColInfo(Array('abbrev' => 'nom-commun', 'nom' => 'Nom Commun', 'extra' => 1, 'fonction_data' => 'getNomCommun_v3'), 'importable' => FALSE), */ 'nom-commun' => self::GenColInfo(Array('abbrev' => 'nom-commun', 'nom' => 'Nom Commun', 'extra' => 1, 'fonction_data' => NULL /* cas particu 'getNomCommun_v4' */, 'preload' => array(__CLASS__, 'getNomCommun_preload'))) ); } if(isset($groupe_de_champs['baseflor'])) { $colonnes += array( // champ dynamique 'baseflor' => self::GenColInfo(Array('abbrev' => 'baseflor', 'nom' => '', 'extra' => 1, 'importable' => FALSE, 'preload' => array(__CLASS__, 'baseflor_preload'), 'dyna' => array(__CLASS__, 'baseflor_ligne'))), ); } if(isset($groupe_de_champs['etendu'])) { $colonnes += array( // champ dynamique 'etendu' => self::GenColInfo(Array('abbrev' => 'etendu', 'nom' => '', 'extra' => 1, 'importable' => FALSE, 'preload' => array(__CLASS__, 'champsEtendus_preload'), 'dyna' => array(__CLASS__, 'champsEtendus_ligne'))), ); } if(isset($groupe_de_champs['auteur'])) { $colonnes += array( 'observateur' => self::GenColInfo(Array('abbrev' => 'observateur', 'nom' => 'Observateur', 'extra' => 1, 'fonction_data' => 'formaterUrlUser', 'importable' => FALSE)), ); } return $colonnes; } static function preload($colonnes, $cel, $ids) { $result = array(); foreach($colonnes as $abbrev => $colonne) { if(!$colonne['preload']) continue; $result[$abbrev] = call_user_func($colonne['preload'], $cel, $ids); } return $result; } public static function getIntitulesColonnes($colonnes) { // array_filter pour supprimer les colonnes "dynamique" n'ayant pas défini $nom (cf GenColInfo()) return array_filter(array_map(array('FormateurGroupeColonne', 'retournerNomItem'), $colonnes)); } public static function retournerNomItem(&$item) { return $item['nom']; } public static function getLigneObservation(&$obs, &$colonnes, $cel = false) { $ligne_formatee = array(); foreach($colonnes as $abbrev => $colonne) { $valeur = null; if($colonne['extra'] == 2 || ! is_null($colonne['dyna'])) continue; // valeur directe depuis cel_obs ? if(isset($obs[$abbrev])) $valeur = $obs[$abbrev]; // pré-processeur des champs if(function_exists($colonne['fonction'])) { $valeur = $colonne['fonction']($valeur); } elseif(method_exists(__CLASS__, $colonne['fonction'])) { $valeur = call_user_func(array(__CLASS__, $colonne['fonction']), $valeur); } elseif($colonne['fonction']) { die("méthode {$colonne['fonction']} introuvable"); } // fonction pour obtenir des champs (étendus) elseif(function_exists($colonne['fonction_data'])) { $valeur = $colonne['fonction_data']($obs); } elseif(method_exists(__CLASS__, $colonne['fonction_data'])) { $valeur = call_user_func(array(__CLASS__, $colonne['fonction_data']), $obs); } // // cette section devrait être vide: // // cas particuliers ingérable avec l'architecture actuelle: if(false && $abbrev == 'date_observation' && $valeur == "0000-00-00") { /* blah */ } // ici à cause du passage de $cel ($this->utilisateur) if($abbrev == 'images') { $valeur = FormateurGroupeColonne::getImages($obs, $cel->id_utilisateur); } if($abbrev == 'nom-commun') { $valeur = FormateurGroupeColonne::getNomCommun_v4($obs); } if($valeur == null) { $valeur = ""; } // // fin de section "cas particuliers" $ligne_formatee[] = $valeur; } // uniquement les champs dynamiques foreach($colonnes as $abbrev => $colonne) { $valeur = null; if(is_null($colonne['dyna'])) continue; // XXX: PHP-5.3 call_user_func_array($colonne['dyna'], array($obs, &$ligne_formatee)); } return $ligne_formatee; } /* * Wrapper générant un tableau associatif: * Ne pas changer les valeurs par défaut du prototype sans réflexion sur l'implication pour nomEnsembleVersListeColonnes() * @param $abbrev (obligatoire): nom court de colonne, largement utilisé lors de l'import. * En effet chaque ligne importée est accessible à l'aide du `define` de $abbrev en majuscule, préfixé de "C_" * Exemple: $ligne[C_LONGITUDE] pour "longitude". * cf: ImportXLS::detectionEntete() * @param $nom (obligatoire): nom complet de colonne (utilisé pour la ligne d'en-tête) * Les définition de champs dynamique (correspondant à de multiples colonnes) doivent laisser cette valeur * vide afin de ne pas créer une colonne supplémentaire erronée. * @param $is_extra: * Si 0, la colonne est une colonne standard * Si 1, la colonne est extra [le plus souvent générée automatiquement] * (auquel cas une bordure bleue entoure son nom dans la ligne d'entête) * Si 2, la colonne n'est pas traité à l'export, mais une définition peut lui être donnée * qui pourra être utilisée à l'import, exemple: "image" * @param $fonction (optionnel): un nom d'un fonction de préprocessing * $fonction doit prendre comme seul argument la valeur d'origine et retourner la valeur transformée * @param $fonction_data (optionnel): une *méthode* d'obtention de donnée * $fonction_data doit prendre comme premier argument le tableau des champs de l'enregistrement existant * $fonction_data doit retourner une valeur * @param $importable (optionnel): défini si la colonne est traitée (ou absolument ignorée par PHPExcel) lors de * l'import. * @param $preload (optionnel): défini une fonction de préchargement massif de donnée potentiellement utilisable par $fonction_data. * Utile, notamment, dans le cadre de l'export * @param $fonction_dynamique (optionnel): défini une fonction ajoutant un nombre arbitraire de colonnes à une ligne donnée * Utile, notamment, dans le cadre de l'export des champs étendus ou des données baseflor * La fonction doit TOUJOURS alterer la ligne en lui ajoutant une nombre CONSTANT d'éléments (NULL ou non) * La fonction doit prendre comme arguments ($obs, &$ligne_formatee) */ static function GenColInfo($args) { $default = Array('abbrev' => NULL, 'nom' => NULL, 'extra' => 0, 'fonction' => NULL, 'fonction_data' => NULL, 'importable' => TRUE, 'preload' => NULL, 'dyna' => NULL); $ret = array_intersect_key($args, $default); return array_merge($default, $ret); } static function formaterDate($date_heure_mysql) { //return ""; if (!$date_heure_mysql || $date_heure_mysql == "0000-00-00 00:00:00") return NULL; // malheureusement pas disponible en php < 5.3 //$date_format = DateTime::createFromFormat("Y-m-d H:i:s", $date_heure_mysql); $val = explode(' ', $date_heure_mysql); $date = explode('-', $val[0]); $heure = explode(':', $val[1]); $timestamp = mktime((int) $heure[0], (int) $heure[1], (int) $heure[2], (int) $date[1], (int) $date[2], (int) $date[0]); if(!$timestamp) return NULL; // TODO: les widgets ne font malheureusement pas usage de l'heure dans le CEL // TODO: si modification, ne pas oublier de modifier le format d'import correspondant // dans ImportXLS, traiterDateObs() (actuellement: "Y/m/d" car utilisation de strtotime() qui ne lit pas tout) // $date_formatee = strftime('%d/%m/%Y', $timestamp); $date_formatee = strftime('%Y/%m/%d', $timestamp); if(!$date_formatee) return "00/00/0000"; return $date_formatee; } static function formaterUrlUser($obs) { $is_id = is_numeric($obs['ce_utilisateur']); return sprintf("%s %s <%s>%s", $obs['prenom_utilisateur'], $obs['nom_utilisateur'], preg_replace(';@.*;', '@...', $obs['courriel_utilisateur']), $is_id ? sprintf(' (' . USER_BASEURL . ')', $obs['ce_utilisateur']) : ''); } static function getImages_preload($cel, $obsids) { if(!$obsids) return; $rec = Cel::db()->requeter( sprintf("SELECT o.id_observation, GROUP_CONCAT(nom_original ORDER BY nom_original ASC SEPARATOR '%s') AS i " . "FROM cel_images i LEFT JOIN cel_obs_images oi ON (i.id_image = oi.id_image) LEFT JOIN cel_obs o ON (oi.id_observation = o.id_observation) " . "WHERE o.ce_utilisateur = %d AND o.id_observation IN (%s) " . "GROUP BY id_observation", SEPARATEUR_IMAGES, $cel->id_utilisateur, implode(',', $obsids))); foreach($rec as $v) { self::$cache['getImages'][$v['id_observation']] = $v['i']; } return NULL; } static function getImages($obs, $id_utilisateur) { if(! $id_utilisateur) return NULL; if(isset(self::$cache['getImages'][$obs['id_observation']])) return self::$cache['getImages'][$obs['id_observation']]; $rec = Cel::db()->requeter( sprintf("SELECT GROUP_CONCAT(nom_original ORDER BY nom_original ASC SEPARATOR '%s') AS i FROM cel_images i" ." LEFT JOIN cel_obs_images oi ON (i.id_image = oi.id_image)" ." LEFT JOIN cel_obs o ON (oi.id_observation = o.id_observation)" ." WHERE o.ce_utilisateur = %d AND o.id_observation = %d LIMIT 1", SEPARATEUR_IMAGES, $id_utilisateur, $obs['id_observation'])); return $rec ? $rec[0]['i'] : NULL; } public static function convertirCodeZoneGeoVersDepartement($code_zone_geo) { $code_departement = ''; if(self::estUnCodeInseeDepartement($code_zone_geo)) { $code_departement = substr(ltrim($code_zone_geo,'INSEE-C:'),0,2); } return $code_departement; } public static function trim0($lonlat) { return trim(trim($lonlat, "0"), "."); } public static function boolOuiNon($transmission) { return $transmission ? 'oui' : ''; } public static function estUnCodeInseeDepartement($code_a_tester) { return preg_match('/^INSEE-C:[0-9]{5}/',$code_a_tester); } // TODO: référentiel ne devrait pas être généré au moment d'un Config::get, // comme dans Config::get('nomsVernaRechercheLimiteeTpl') // Par exemple, la variable pour "nva" ? function getNomCommun($obs) { $langue = 'fra'; list($referentiel) = explode(':', strtolower($obs['nom_referentiel'])); if($referentiel == 'bdtfx') $referentiel = 'nvjfl'; else return ''; $cache_id = $referentiel . '-' . $obs['nt'] . '-' . $langue; if(isset(self::$cache['getNomCommun'][$cache_id])) { //debug: error_log("require url_service_nom_attribution: OK ! (pour \"{$obs['nom_ret']}\")"); return self::$cache['getNomCommun'][$cache_id]; } // pas de cache: //debug: error_log("require url_service_nom_attribution pour \"{$obs['nom_ret']}\""); // pour bdtfx: // /service:eflore:0.1/nvjfl/noms-vernaculaires/attributions?masque.nt=X&masque.lg=fra&retour.champs=num_statut // /projet/services/modules/0.1/nvjfl/NomsVernaculaires.php $url = str_replace(Array('{referentiel}', '{valeur}', '{langue}'), Array($referentiel, $obs['nt'], $langue), self::$config['eflore']['url_service_nom_attribution']) . // TODO ! "&retour.champs=num_statut"; $noms = @json_decode(file_get_contents($url)); if(! $noms) return ''; $noms = array_filter((array)($noms->resultat), array($this, retournerNumStatutUn)); // XXX: php 5.3 $nom = array_pop($noms)->nom_vernaculaire; // cache self::$cache['getNomCommun'][$cache_id] = $nom; return $nom; } private function retournerNumStatutUn(&$item) { return ($item->num_statut == 1); } private function retournerNumStatutUnArr(&$item) { return ($item['num_statut'] == 1); } // si getNomCommun_v2 ou getNomCommun_v3 sont utilisés /* require_once('/home/raphael/eflore/framework/framework/Framework.php'); Framework::setCheminAppli("/home/raphael/eflore/projets/services/index.php"); Framework::setInfoAppli(Config::get('info')); require_once('/home/raphael/eflore/projets/services/modules/0.1/Projets.php');*/ /* Tente de bootstraper le framework au plus court et d'initialiser une instance de NomsVernaculaires pour obtenir le nom commun */ function getNomCommun_v2($obs) { static $service; $service = new Projets(); $langue = 'fra'; list($referentiel) = explode(':', strtolower($obs['nom_referentiel'])); if($referentiel == 'bdtfx') $referentiel = 'nvjfl'; else return ''; $cache_id = $referentiel . '-' . $obs['nt'] . '-' . $langue; if(isset(self::$cache['getNomCommun'][$cache_id])) { error_log("require NomsVernaculaires.php: OK ! (pour \"{$obs['nom_ret']}\")"); return self::$cache['getNomCommun'][$cache_id]; } // pas de cache: error_log("require NomsVernaculaires.php pour \"{$obs['nom_ret']}\""); $donnees = Array('masque.nt' => $obs['nt'], 'masque.lg' => $langue, 'retour.champs' => 'num_statut'); $noms = $service->consulter(Array('nvjfl', 'noms-vernaculaires'), $donnees); if(! $noms) return ''; $noms = array_filter((array)($noms->resultat), array($this, retournerNumStatutUn)); // XXX: php 5.3 $nom = array_pop($noms)->nom_vernaculaire; // cache self::$cache['getNomCommun'][$cache_id] = $nom; return $nom; } /* Effectue un bootstraping plus sage que ci-dessus, mais le gain d'efficacité n'est pas aussi retentissant qu'espéré */ static $service; function getNomCommun_v3($obs) { if(! $this->service) $this->service = new Projets(); $langue = 'fra'; list($referentiel) = explode(':', strtolower($obs['nom_referentiel'])); if($referentiel == 'bdtfx') $referentiel = 'nvjfl'; else return ''; $cache_id = $referentiel . '-' . $obs['nt'] . '-' . $langue; if(isset(self::$cache['getNomCommun'][$cache_id])) { error_log("require NomsVernaculaires.php: OK ! (pour \"{$obs['nom_ret']}\")"); return self::$cache['getNomCommun'][$cache_id]; } // pas de cache: error_log("require NomsVernaculaires.php pour \"{$obs['nom_ret']}\""); $donnees = Array('masque.nt' => $obs['nt'], 'masque.lg' => $langue, 'retour.champs' => 'conseil_emploi'); $this->service->initialiserRessourcesEtParametres(Array('nvjfl', 'noms-vernaculaires', 'attributions'), $donnees); try { $noms = $this->service->traiterRessources(); } catch(Exception $e) { return ''; } if(! $noms) return ''; $noms = array_filter($noms['resultat'], array($this, retournerNumStatutUnArr)); // XXX: php 5.3 $premier_nom = array_pop($noms); $nom = $premier_nom['nom_vernaculaire']; // cache self::$cache['getNomCommun'][$cache_id] = $nom; return $nom; } /* Cette fonction initialise le cache des noms communs en 1 fois, sur la liste des observations à exporter. Ainsi, les appels successifs à getNomCommun_v4() ne sont pas couteux (pas de requête SQL) */ static function getNomCommun_preload($cel, $obsids) { if(!$obsids) return; if(!self::referenceTableExiste()) return NULL; // CREATE INDEX i_nom_referentiel ON cel_obs (nom_referentiel(5)); $req = sprintf("SELECT r.referentiel, r.num_taxon, r.nom_commun FROM cel_references r" . " INNER JOIN cel_obs c ON (r.referentiel = substring_index(c.nom_referentiel, ':', 1) and r.num_taxon = c.nt)" . " WHERE c.id_observation IN (%s)", implode(',', $obsids)); $res = Cel::db()->requeter($req); foreach($res as $v) { self::$cache['getNomCommun'][$v['referentiel'] . '-' . $v['num_taxon'] . '-' . 'fra'] = $v['nom_commun']; } return NULL; } static function referenceTableExiste() { if(!self::$is_table) { // une seule fois if(! Cel::db()->executerRequete("SHOW TABLES LIKE 'cel_references'", Cel::SQL_RETOUR_LIGNE)) return FALSE; self::$is_table = TRUE; } return TRUE; } static function getNomCommun_v4($obs) { if(! $obs['nt']) return NULL; if(! self::referenceTableExiste()) return NULL; $langue = 'fra'; list($referentiel) = explode(':', strtolower($obs['nom_referentiel'])); $cache_id = $referentiel . '-' . $obs['nt'] . '-' . $langue; // cache: if(isset($cache['getNomCommun'])) { if(isset(self::$cache['getNomCommun'][$cache_id])) return self::$cache['getNomCommun'][$cache_id]; // XXX: problème de valeurs NULL ? if(array_key_exists($cache_id, self::$cache['getNomCommun'])) return self::$cache['getNomCommun'][$cache_id]; } // pas de cache: $nom = Cel::db()->executerRequete(sprintf("SELECT nom_commun FROM cel_references " . "WHERE referentiel = '%s' AND num_taxon = %d LIMIT 1", $referentiel, $obs['nt']), Cel::SQL_RETOUR_LIGNE); if(! $nom) return NULL; $nom = $nom["nom_commun"]; // cache self::$cache['getNomCommun'][$cache_id] = $nom; return $nom; } /* Cette fonction initialise le cache des données baseflor en 1 fois, sur la liste des observations à exporter. Ainsi, les appels successifs à baseflor_ligne() ne sont pas couteux (pas de requête SQL) */ static function baseflor_preload($cel, $obsids) { if(!$obsids) return; if(!self::referenceTableExiste()) return NULL; $req = sprintf("SELECT referentiel, num_nom_retenu, %s FROM cel_references r" . " INNER JOIN cel_obs c ON (r.num_nom_retenu = c.nom_ret_nn)" . " WHERE c.id_observation IN (%s)", //" AND catminat_code IS NOT NULL", // TODO: suppression des NULL ici signifie que le cache sera partiel... implode(',', array_keys(self::$baseflor_col)), implode(',', $obsids)); $res = Cel::db()->requeter($req); if(!$res) return NULL; foreach($res as $v) { $data = $v; unset($data['referentiel']); // non nécessaire unset($data['num_nom_retenu']); // non nécessaire self::$cache['getBaseflor'][$v['referentiel'] . '-' . $v['num_nom_retenu']] = $data; } return NULL; } static function baseflor_ligne($obs, &$ligne) { if(! $obs['nom_ret_nn']) { $ligne = array_merge($ligne, array_fill(0, count(self::$baseflor_col), NULL)); return; } if(! self::referenceTableExiste()) { $ligne = array_merge($ligne, array_fill(0, count(self::$baseflor_col), NULL)); return; } list($referentiel) = explode(':', strtolower($obs['nom_referentiel'])); $cache_id = $referentiel . '-' . $obs['nom_ret_nn']; // XXX: problème de valeurs NULL pour utiliser simplement isset() ? // @ car getBaseflor[] n'est peut-être pas encore initialisé if(@array_key_exists($cache_id, self::$cache['getBaseflor'])) { $ligne = array_merge($ligne, self::$cache['getBaseflor'][$cache_id]); return; } // pas de cache: $data = Cel::db()->executerRequete(sprintf("SELECT %s FROM cel_references " . "WHERE referentiel = '%s' AND num_nom_retenu = %d LIMIT 1", implode(', ', array_keys(self::$baseflor_col)), $referentiel, $obs['nom_ret_nn']), Cel::SQL_RETOUR_LIGNE); if(! $data) { $ligne = array_merge($ligne, array_fill(0, count(self::$baseflor_col), NULL)); return; } // cache self::$cache['getBaseflor'][$cache_id] = $data; $ligne = array_merge($ligne, $data); } static function champsEtendus_preload($cel, $obsids) { $gestion_champs_etendus = new GestionChampsEtendus($cel->config, 'obs'); $colonnes_champs_supp_par_obs = $gestion_champs_etendus->consulterClesParLots($obsids); // ces deux lignes réordonnent l'ordre des colonnes des champs étendus en fonction de l'ordre (très spécifique) // de self::$ordre_champ_etendus_Florileges, les champs non-mentionnés sont ajoutés à la fin. $colonnes_champs_supp_par_obs = self::sortArrayByArray(array_flip($colonnes_champs_supp_par_obs), self::$ordre_champ_etendus_Florileges); $colonnes_champs_supp_par_obs = array_keys($colonnes_champs_supp_par_obs); // si le SELECT des clefs ne retourne rien, une autre requêtes est inutile // TODO: optimize, 1 seule requête if(!$colonnes_champs_supp_par_obs) return Array('header' => array(), 'data' => array()); $champs_supp_par_obs = $gestion_champs_etendus->consulterParLots($obsids); self::$cache['champsEtendus']['header'] = self::champsEtendus_prefixHeader($colonnes_champs_supp_par_obs); foreach($champs_supp_par_obs as &$v) { $v = self::champsEtendus_aplatir($v); } self::$cache['champsEtendus']['data'] = $champs_supp_par_obs; // ce return est temporaire, // le temps que toutes les fonctions bougent ici et utilise plutôt le cache statique // encore utilisé pour les entêtes (self::$cache['champsEtendus']['header']) return self::$cache['champsEtendus']; } // XXX: PHP-5.3, fonction anonyme + array_map static function champsEtendus_prefixHeader($array) { return array_map(create_function('$v', 'return "' . PREFIX_CHAMPS_ETENDUS . '".$v;'), $array); } // XXX: PHP-5.3, fonction anonyme + array_map static function champsEtendus_aplatir($ligne_champs_etendus) { $champs_etendus_fmt = array(); if(!$ligne_champs_etendus) return $champs_etendus_fmt; foreach($ligne_champs_etendus as $champ) { $champs_etendus_fmt[PREFIX_CHAMPS_ETENDUS . $champ->cle] = $champ->valeur; } return $champs_etendus_fmt; } static function champsEtendus_ligne($obs, &$ligne) { // si header n'est pas défini, aucune observation ne possède de champ étendu // et nous n'ajoutons ni colonnes, ni valeurs. if(! isset(self::$cache['champsEtendus']['header'])) return; $ligne_etendue_aplatie = @self::$cache['champsEtendus']['data'][$obs['id_observation']]; $ligne_supp = array_fill(0, count(self::$cache['champsEtendus']['header']), ''); $ligne_etendue_fmt = array(); // si, cependant cette seule observation n'a pas de champs étendus, // nous devons rajouter des blancs (notamment dans le cas ou d'autres // champs viennent à être ajoutés en aval à l'avenir // cf: $fonction_dynamique dans FormateurGroupeColonne::GenColInfo() if(! $ligne_etendue_aplatie) { $ligne = array_merge($ligne, $ligne_supp); return; } foreach(self::$cache['champsEtendus']['header'] as $colonne) { if(!isset($ligne_etendue_aplatie[$colonne])) { $ligne_etendue_fmt[$colonne] = ''; } else { $ligne_etendue_fmt[$colonne] = $ligne_etendue_aplatie[$colonne]; } } // XXX/ array_merge() ? $ligne += $ligne_etendue_fmt; } /* HELPERS */ // http://stackoverflow.com/questions/348410/sort-an-array-based-on-another-array // XXX; redéfinition, utilisé aussi par ExportXLS static function sortArrayByArray($array, $orderArray) { $ordered = array(); foreach($orderArray as $key) { if(array_key_exists($key, $array)) { $ordered[$key] = $array[$key]; unset($array[$key]); } } return $ordered + $array; } }