* @author Raphaël Droz * @author Jean-Pascal MILCENT * @author Aurelien PERONNET * @license GPL v3 * @license CECILL v2 * @copyright 1999-2014 Tela Botanica */ 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', 'standardexport', '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', $programme = "") { 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; } if ($programme != "") { $groupe_de_champs['avance'] = true; $groupe_de_champs['etendu'] = true; } // 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['standardexport'])) { $colonnes += Array( 'guid' => self::GenColInfo(Array('abbrev' => 'guid', 'nom' => 'Identifiant unique')), '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')), 'pays' => self::GenColInfo(Array('abbrev' => 'pays', 'nom' => 'Pays')), '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)), 'programme' => self::GenColInfo(Array('abbrev' => 'programme', 'nom' => 'Programme de sciences participatives ou observatoire citoyen', 'importable' => FALSE)), 'mots_cles_texte' => self::GenColInfo(Array('abbrev' => 'mots_cles_texte', 'nom' => 'Mots Clés', 'importable' => FALSE)), 'certitude' => self::GenColInfo(Array('abbrev' => 'certitude', 'nom' => 'Certitude', 'extra' => 1)), 'image' => self::GenColInfo(Array('abbrev' => 'image', 'nom' => 'Image(s)', 'extra' => 1)), 'validation_identiplante' => self::GenColInfo(Array('abbrev' => 'validation_identiplante', 'nom' => 'Détermination validée sur IdentiPlante', 'importable' => FALSE)), 'url_identiplante' => self::GenColInfo(Array('abbrev' => 'url_identiplante', 'nom' => "Lien vers l'observation sur IdentiPlante", 'importable' => FALSE)), ); } elseif (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')), 'pays' => self::GenColInfo(Array('abbrev' => 'pays', 'nom' => 'Pays')), '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); } else if(method_exists(__CLASS__, $colonne['fonction'])) { $valeur = call_user_func(array(__CLASS__, $colonne['fonction']), $valeur); } else if($colonne['fonction']) { die("méthode {$colonne['fonction']} introuvable"); } else if(function_exists($colonne['fonction_data'])) {// fonction pour obtenir des champs (étendus) $valeur = $colonne['fonction_data']($obs); } else if(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; } 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 o ON (i.ce_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 o ON (i.ce_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; } /** * Enlève les zéros excédentaires (devenus nomades) au début et à la fin d'une * latitude ou longitude, en prenant garde à ne pas foirer les nombres 0.xyz */ public static function trim0($lonlat) { $retour = trim($lonlat, "0"); // si on a trop enlevé de zéros à gauche, on en remet un (mode synthobois) if (substr($retour, 0, 1) == ".") { $retour = "0" . $retour; } return $retour; } 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()->requeterLigne("SHOW TABLES LIKE 'cel_references'")) return FALSE; self::$is_table = TRUE; } return TRUE; } static function getNomCommun_v4($obs) { // Attention la fonction suppose que l'on ait fait appel à getNomCommun_preload avant // d'être appelée 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; $nom = null; // cache: if(isset(self::$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'])) { $nom = self::$cache['getNomCommun'][$cache_id]; } } 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; // Attention (en attendant de faire une meilleure table et une meilleure requete) le distinct est très important $req = sprintf("SELECT DISTINCT referentiel, num_nom_retenu, %s FROM cel_references r" . " INNER JOIN cel_obs c ON (r.num_nom_retenu = c.nom_ret_nn)" . " AND r.referentiel = IF(LOCATE(':', c.nom_referentiel) = 0, c.nom_referentiel, SUBSTR(c.nom_referentiel FROM 1 FOR LOCATE(':', c.nom_referentiel) - 1))" . " 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 // Des fois les synonymes ont des valeurs pour baseflor et pas le nom retenu et vice versa // on les fusionne pour avoir le maximum d'infos, en attendant de repenser la table référence if(isset(self::$cache['getBaseflor'][$v['referentiel'] . '-' . $v['num_nom_retenu']])) { $orig = array_filter(self::$cache['getBaseflor'][$v['referentiel'] . '-' . $v['num_nom_retenu']], 'strlen'); $data = array_filter($data , 'strlen'); $data = array_merge($orig, $data); } self::$cache['getBaseflor'][$v['referentiel'] . '-' . $v['num_nom_retenu']] = $data; } return NULL; } /** * Attention la fonction suppose que l'on ait fait appel à baseflor_preload avant * d'être appelée * @CASSECOUILLES elle pourrait le détecter et le faire elle-même */ static function baseflor_ligne($obs, &$ligne) { $clefsBF = array_keys(self::$baseflor_col); // par défaut des colonnes vides pour ne pas décaler le bousin $donneesBF = array_fill_keys($clefsBF, ""); // s'il y a des données baseflor if ($obs['nom_ret_nn'] && self::referenceTableExiste() && count(self::$cache['getBaseflor']) > 0) { // l'astuce à un franc vingt list($referentiel) = explode(':', strtolower($obs['nom_referentiel'])); $cache_id = $referentiel . '-' . $obs['nom_ret_nn']; // si les données baseflor existent dans le cache pour ce nom_ret_nn if (array_key_exists($cache_id, self::$cache['getBaseflor'])) { $donneesBFATrous = self::$cache['getBaseflor'][$cache_id]; foreach($clefsBF as $colbf) { // remplit les trous tout en préservant l'ordre if(array_key_exists($colbf, $donneesBFATrous)) { $donneesBF[$colbf] = $donneesBFATrous[$colbf]; } else { $donneesBF[$colbf] = ""; } } } } // Quand les données sont prêtes, on les fusionne $ligne = array_merge($ligne, $donneesBF); } static function champsEtendus_preload($cel, $obsids) { $gestion_champs_etendus = new GestionChampsEtendus($cel->config, 'obs'); $colonnes_champs_supp_par_obs = $gestion_champs_etendus->consulterClesParLots($obsids); // Supprime les champs étendus considérés comme privés dans le cas de l'export public en chargeant // le catalogue et en excluant ceux qui sont explicitement privés if(!$cel->export_prive) { $indices_a_supprimer = array(); $catalogue_champs_etendus = $gestion_champs_etendus->consulterCatalogueChampsEtendusPredefinis(); foreach($catalogue_champs_etendus as $champ_catalogue) { if($champ_catalogue['options']['prive'] == 1) { // Les champs étendus peuvent avoir des variantes lorsqu'ils apparaissent de multiples fois. // Vont donc matcher monChamp mais aussi monChamp:1, monChamp:2 ou bien monChamp1, monChamp: etc... // pour plus de sécurité (ce filtra n'est affectué qu'une fois au début de l'export donc on ne s'en prive pas) $entrees = preg_grep("/".$champ_catalogue['cle']."(?::?\d*)?$/", $colonnes_champs_supp_par_obs); $indices_a_supprimer = array_merge($indices_a_supprimer, array_keys($entrees)); } } // les champs étendus sont renvoyés dans l'export suivant les colonnes présentes dans ce tableau // les éliminer de la liste des colonnes suffit à les faire ignorer par l'export foreach($indices_a_supprimer as $indice_supp) { unset($colonnes_champs_supp_par_obs[$indice_supp]); } } // 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; } }