Subversion Repositories Applications.referentiel

Rev

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 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;
        }
}
?>