Rev 402 | Blame | Compare with Previous | Last modification | View Log | RSS feed
<?php/*** Indexation dans Algolia des référentiels** Description : formate les données des référentiels choisis et envoie tout ça* dans Algolia** Utilisation : php script.php algolia [-ref "ref1,ref2,..."]* -ref (optionnel): liste de codes de référentiels séparés par des virgules;* par défaut: "apd,bdtfx,bdtxa,isfan"** Exemples:* php script.php algolia* php script.php algolia -ref "bdtfx,isfan"** @note: ignorer le paramètre fasciste -a : on ne s'en sert pas** @author Tela Botanica <equipe-dev@tela-botanica.org>* @licence GPL v3 & CeCILL v2*/restore_error_handler();restore_exception_handler();ini_set("display_errors","1");error_reporting(E_ALL);// composer autoloadrequire dirname(__FILE__) . '/../../../vendor/autoload.php';class Algolia extends ScriptCommande {const SCRIPT_NOM = 'algolia';public $parametres = array('-ref' => array(false, false, 'Celui qui lit ça est un con'),'-update' => array(false, false, 'Timestamp de la dernière mise à jour (YYYY-MM-DD)'));/** connexion PDO à la BDD "referentiels" */protected $bdd;/** client API Algolia */protected $algolia;protected $indexAlgolia;public function executer() {echo "Indexation des référentiels dans Algolia" . PHP_EOL;// Bibliothèque Algolia PHP pour appeler l'APIConfig::charger(dirname(__FILE__) . '/algolia.ini');$this->algolia = new \AlgoliaSearch\Client(Config::get('algolia_application_id'), Config::get('algolia_api_key'));$this->indexAlgolia = $this->algolia->initIndex(Config::get('algolia_index'));// pour obtenir facilement la config existante et la répercuter dans// index_settings.json/*$settings = $this->indexAlgolia->getSettings();var_dump(json_encode($settings));exit;*//*$idsexistants = $this->indexAlgolia->search("", array("attributesToRetrieve" => array("objectID")));var_dump(count($idsexistants));var_dump($idsexistants);exit;*/if ($this->getParam('update')) {$this->miseAJourPartielle($this->getParam('update'));}// Réglages de l'index @TODO tenir à jourif ($this->confirmer("Charger les réglages par défaut (index_settings.json) dans la configuration de l'index Algolia ?")) {// Chargement des réglages par défaut$reglagesJson = file_get_contents(dirname(__FILE__) . '/index_settings.json');$reglages = json_decode($reglagesJson, true);$this->indexAlgolia->setSettings($reglages);echo "Réglages chargés dans Algolia" . PHP_EOL;}// Connexion à la base$this->connecterPDO();// Liste des référentiels à fusionner$refsTexte = Config::get('algolia_referentiels');$refs = explode(",", $refsTexte);// Liste des référentiels à mettre à jour$refsMajTexte = $this->getParam("ref");if ($refsMajTexte === false) {// si le paramètre est vide, on met tout à jour$refsMaj = $refs;} else {$refsMaj = explode(",", $refsMajTexte);}// Déniaisage 1foreach ($refs as $k => $r) {$fichierRequete = dirname(__FILE__) . "/algolia_" . $r . ".sql";if (! file_exists($fichierRequete)) {echo "- fichier [$fichierRequete] non trouvé, fusion de [$r] ignorée" . PHP_EOL;unset($refs[$k]);}}if (empty($refs)) {echo "Aucun référentiel à fusionner" . PHP_EOL;exit;}// Déniaisage 2foreach ($refsMaj as $k => $r) {if (! in_array($r, $refs)) {echo "- le référentiel à mettre à jour [$r] n'est pas présent dans la liste à fusionner, il sera ignoré" . PHP_EOL;unset($refsMaj[$k]);}}if (empty($refsMaj)) {echo "Aucun référentiel à mettre à jour" . PHP_EOL;exit;}// Confirmationif (! $this->confirmer("Fusion des référentiels [" . implode(',', $refs) . "] et mise à jour de [" . implode(',', $refsMaj) . "]. Continuer ?")) {exit;}//var_dump($refs);$donneesBrutes = array();// Exécution des requêtes pour chaque référentielforeach ($refs as $ref) {$fichierRequete = dirname(__FILE__) . "/algolia_" . $ref . ".sql";// Exécution de la requête$requete = file_get_contents($fichierRequete);$resultat = $this->requete($requete);$donneesBrutes[$ref] = $resultat->fetchAll();// Info utilisation mémoire$mem = memory_get_usage(true);$memMio = round($mem / (1024 * 1024));echo "Mémoire utilisée : $memMio Mio" . PHP_EOL;}// Fusion !$index = $this->fusionnerReferentiels($donneesBrutes);//$this->extrait($index, array('Acacia dealbata Link','Acacia Mill.','Fabaceae'));// Mise en forme$index = $this->mettreEnForme($index);//$this->extrait($index, 100);// Stats$taille = count($index);echo "Taille de l'index: [$taille] lignes !" . PHP_EOL;//file_put_contents("couscous.json", json_encode($index));// Calcul des différences ?// @TODO bonjour la galère// Insertion ?if (! $this->confirmer("Prêt à insérer dans l'index Algolia [" . Config::get('algolia_index') . "]. Continuer ?")) {exit;}$this->insererDansAlgolia($index);// Info utilisation mémoire totale$mem = memory_get_peak_usage(true);$memMio = round($mem / (1024 * 1024));echo "Mémoire maximale utilisée : $memMio Mio" . PHP_EOL;}/*** Génère un index unique pour Algolia à partir des données de n référentiels*/protected function fusionnerReferentiels(&$donneesRefs) {$index = array();foreach ($donneesRefs as $ref => &$d) {$nbTaxons = count($d);echo "-- fusion du référentiel [$ref] : $nbTaxons taxons --" . PHP_EOL;$fusions = 0;foreach ($d as $taxon) {// debug/*if ($taxon[$ref . '_num_nom'] == 141) {echo "> Taxon 141 :" . PHP_EOL;var_dump($taxon);echo PHP_EOL;}*/$nomSci = $taxon[$ref . '_nom_sci'];//$nn = $taxon[$ref . '_num_nom'];// Ajout du nom d'auteur pour éviter les collisions dans un même référentielif (! empty ($taxon[$ref . '_auteur'])) {$nomSci .= ' ' . $taxon[$ref . '_auteur'];}// -- ÉLIMINATION DES NOMS SANS CORRESPONDANCEif (empty($taxon[$ref . '_num_nom_retenu'])) {//echo "XX élimination du nom sans correspondance : [$nomSci] (nn $nn)" . PHP_EOL;continue;}if (! isset($index[$nomSci])) {$index[$nomSci] = array('objectID' => $nomSci,'referentiels' => array());} else {//echo "> fusion sur [$nomSci] (nn $nn)" . PHP_EOL;$fusions++;}$index[$nomSci] = array_merge($index[$nomSci], $taxon);$index[$nomSci]['referentiels'][] = $ref;//break;}$taille = count($index);echo "- taille de l'index après ajout de [$ref]: [$taille] lignes ($fusions fusions)" . PHP_EOL;}return $index;}/*** Organise les données de chaque objet conformément à la structure de* l'index Algolia** Voir commentaires sur cette page :* http://taiga.tela-botanica.net/project/mathias-site-web/task/75** L'objectID est le MD5 de la "clef" (nom scientifique avec auteur)*/protected function mettreEnForme($index) {$nouvelIndex = array();foreach ($index as $nomSci => $taxon) {$nouveauTaxon = array('objectID' => md5($nomSci),'referentiels' => $taxon['referentiels']);foreach ($taxon['referentiels'] as $ref) {// ingrédients$nn = $taxon[$ref . '_num_nom'];$ns = $taxon[$ref . '_nom_sci'];$nts = $taxon[$ref . '_num_tax_sup'];$rang = $taxon[$ref . '_rang'];$auteur = $taxon[$ref . '_auteur'];$annee = $taxon[$ref . '_annee'];$biblio = $taxon[$ref . '_biblio'];$nom_supra_generique = $taxon[$ref . '_nom_supra_generique'];$genre = $taxon[$ref . '_genre'];$epithete_sp = $taxon[$ref . '_epithete_sp'];$type_epithete = $taxon[$ref . '_type_epithete'];$epithete_infra_sp = $taxon[$ref . '_epithete_infra_sp'];$cultivar = $taxon[$ref . '_cultivar'];$cultivar_groupe = $taxon[$ref . '_cultivar_groupe'];$nomCommun = (isset($taxon[$ref . '_nom_francais']) ? $taxon[$ref . '_nom_francais'] : '');$url = $taxon[$ref . '_url'];$synonymes = json_decode($taxon[$ref . '_synonymes'], true);// debug synonymes tronqués (group_concat limité en longueur)/*if ($nn == 141) {echo ">> Synonymes bruts: [" . $taxon[$ref . '_synonymes'] . "]" . PHP_EOL;echo ">> Synonymes décodés: [" . print_r($synonymes, true) . "]" . PHP_EOL;exit;}*/$raccourcis = json_decode($taxon[$ref . '_shortcuts'], true);$raccourcis = ($raccourcis != null ? array_values(array_unique($raccourcis)) : null); // array_values réindexe pour obtenir une liste en JSON et non un objet// garniture$donneesRef = array('nomenclatural_number' => intval($nn),'scientific_name' => $ns,'common_name' => $nomCommun,'synonyms' => $synonymes,'permalink' => $url,'parent_taxon_number' => intval($nts),'rank' => intval($rang),'author' => $auteur,'year' => intval($annee),'biblio' => $biblio,'supra_genus_name' => $nom_supra_generique,'genus' => $genre,'species_attribute' => $epithete_sp,'attribute_type' => $type_epithete,'infra_species_attribute' => $epithete_infra_sp,'cultivar' => $cultivar,'cultivar_groupe' => $cultivar_groupe);// dans le cas de BDTFX, ajout de l'illustration de Coste et de// la carte Chorodep pour illustrer les résultats de rechercheif (Config::get("activer_image_cel") == "1") {$this->ajouterImagesEflore($nn, $donneesRef);}$nouveauTaxon[$ref] = $donneesRef;$nouveauTaxon['shortcuts'] = $raccourcis;}$nouvelIndex[] = $nouveauTaxon;}return $nouvelIndex;}/*** Interroge le service eFlore pour récupérer l'illustration de Coste pour* le nn en cours, ainsi que les meilleures images d'eFlore et ajoute une* URL pour obtenir la carte de répartition de Chorodep*/protected function ajouterImagesEflore($nn, &$donnees) {// carte de répartition - le service renvoie directement une image$donnees['thumbnails']['chorodep'] = sprintf(Config::get('url_template_chorodep'), $nn);// Coste$retour = @file_get_contents($sprintf(Config::get('url_template_coste'), $nn));if ($retour) {try {$infosCoste = json_decode($retour, true);if (! empty($infosCoste['resultats']) && is_array($infosCoste['resultats'])) {$res1 = array_shift($infosCoste['resultats']);if (is_array($res1) && ! empty($res1['binaire.href'])) {$donnees['thumbnails']['coste'] = $res1['binaire.href'];}}} catch (Exception $ex) {// pas de bol}}// CEL$retour = @file_get_contents($sprintf(Config::get('url_template_cel'), $nn));if ($retour) {try {$infosCel = json_decode($retour, true);if (! empty($infosCel['resultat']) && is_array($infosCel['resultat'])) {foreach($infosCel['resultat'] as $organe => $imageSrc) {$donnees['thumbnails']['cel'][$organe] = $imageSrc;}}} catch (Exception $ex) {// pas de bol}}// debug/*echo ">> nn : [$nn]" . PHP_EOL;echo ">> image Coste : [" . $donnees['thumbnails']['coste'] . "]" . PHP_EOL;echo ">> carte Chorodep : [" . $donnees['thumbnails']['chorodep'] . "]" . PHP_EOL;echo ">> images CeL :" . PHP_EOL;var_dump($donnees['thumbnails']['cel'])*/}/*** Appelle l'API Algolia pour indexer les données présentes dans $index, par* tranches.*/protected function insererDansAlgolia(&$index) {$tailleTranche = 5000;echo "++++ Insertion dans Algolia (" . count($index) . " objets) !! ++++" . PHP_EOL;// insertion par tranches pour éviter un timeout sur l'API Algoliawhile (count($index) > 0) {echo "++ insertion d'une tranche de $tailleTranche... (" . count($index) . " restant)" . PHP_EOL;$tranche = array_splice($index, 0, $tailleTranche);//var_dump($tranche);$this->indexAlgolia->addObjects($tranche);}}/*** Met à jour les images d'illustration*/protected function miseAJourPartielle($date) {try {$date = new DateTime($date);$date = $date->format('Y-m-d');} catch (Exception $e) {die($e->getMessage());}// récupération des nn mis à jour$this->connecterPDO('agathis_');$sql = 'SELECT DISTINCT nn FROM del_image_top WHERE date_vote > ' . $date . ';';$requete = $this->requete($sql);$nns = $requete->fetchAll();// var_dump($nns); die();$donnees = array();foreach ($nns as $nn) {// récup des infos depuis le service d'images du cel$retour = @file_get_contents($sprintf(Config::get('url_template_cel'), $nn));if ($retour) {try {$infosCel = json_decode($retour, true);if (! empty($infosCel['resultat']) && is_array($infosCel['resultat'])) {foreach($infosCel['resultat'] as $organe => $imageSrc) {$donnees[$nn]['thumbnails']['cel'][$organe] = $imageSrc;}// var_dump($donnees); die();$response = $this->indexAlgolia->getObjects([$nn], ['objectID', 'nomenclatural_number', 'thumbnails']);$response = json_decode($response, true);// var_dump($response); die();if (isset($response['results'])) {$results = array_reduce($response['results'], function($carry, $item) use ($nn) {if ($item['nomenclatural_number'] == $nn) {$carry[] = $item;}return $carry;});// var_dump($results); die();if (count($results) == 1) {unset($results['thumbnails']['cel']);$results = array_merge($results, $donnees[$nn]);$this->indexAlgolia->partialUpdateObject(['thumbnails' => $results['thumbnails'],'objectID' => $results['objectID']]);}}}} catch (Exception $e) {die(var_dump($e));}}}}// ---------------- utilitaires --------------------------------------------protected function extrait($index, $clefsOuNombre) {// Debugecho PHP_EOL . "---- extrait des données --" . PHP_EOL;if (is_array($clefsOuNombre)) {foreach ($clefsOuNombre as $k) {var_dump($index[$k]);}} else {for ($i=0; $i < $clefsOuNombre; $i++) {var_dump($index[$i]);}}}protected function connecterPDO($base = '') {Config::charger(dirname(__FILE__) . '/../../configurations/bdd.ini');try {$dsn = Config::get($base . 'bdd_type').':dbname='.Config::get($base . 'bdd_nom').';host='.Config::get($base . 'bdd_hote');$this->bdd = new PDO($dsn, Config::get($base . 'bdd_utilisateur'), Config::get($base . 'bdd_mot_de_passe'));// Passe en UTF-8 la connexion à la BDD$this->bdd->exec("SET NAMES 'utf8'");// Affiche les erreurs détectées par PDO (sinon mode silencieux => aucune erreur affiché)$this->bdd->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);} catch (PDOException $e) {//print_r($e);echo 'La connexion à la base de données via PDO a échoué : ' . $e->getMessage() . PHP_EOL;exit;}}protected function requete($requete) {$infos = null;try {$infos = $this->bdd->query($requete, PDO::FETCH_ASSOC);/*if ($infos === false) {echo $requete;}*/} catch (PDOException $e) {echo sprintf($e->getFile(), $e->getLine(), $e->getMessage(), $e->getCode(), $requete);}return $infos;}/*** Demande confirmation, et sort du script à moins qu'on tape ce qui est* indiqué (par défaut "o" pour "oui")*/protected function confirmer($question='Continuer ?', $codeAcceptation='o', $messageAnnulation='annulation') {echo $question . ' ("' . $codeAcceptation . '" pour confirmer, autre chose pour annuler)' . PHP_EOL;$handle = fopen ("php://stdin","r");$line = fgets($handle);if(strtolower(trim($line)) != strtolower($codeAcceptation)) {echo $messageAnnulation . PHP_EOL;return false;}fclose($handle);return true;}}?>