* @licence GPL v3 & CeCILL v2 */ restore_error_handler(); restore_exception_handler(); ini_set("display_errors","1"); error_reporting(E_ALL); // composer autoload require 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'API Config::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 à jour if ($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 1 foreach ($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 2 foreach ($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; } // Confirmation if (! $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érentiel foreach ($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érentiel if (! empty ($taxon[$ref . '_auteur'])) { $nomSci .= ' ' . $taxon[$ref . '_auteur']; } // -- ÉLIMINATION DES NOMS SANS CORRESPONDANCE if (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 recherche if (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 Algolia while (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) { // Debug echo 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; } } ?>