Rev 3779 | Rev 3785 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed
<?php/*PHP 7Script used to synchronize PN occ with Flora DataÉcrit en franglish pour une meilleure compréhension (lol)*/class PullPlantnet extends Script {protected $bdd;protected $debug = false;protected $allowedPartners = ['tb', 'pn'];// Nom du fichier contenant les correspondances d'ID TB/PNprotected $correspondingIdsFilename = '';// Cache d'informations utilisateurs provenant de l'annuaire TBprotected $userInfos = [];// Cache des obs id PN traitées pendant cette runprotected $processedObsId = [];// Paramètre de suivi et de pagination de l'API des obs PNprotected $currentPage = '';protected $currentProject = '';protected $hasMore = false;// Traduction des organes de PNprotected $tagsImageTraduits = ['flower' => 'fleur','fruit' => 'fruit','leaf' => 'feuille','bark' => 'écorce','branch' => 'branche','habit' => 'port',];// Différents types de lifecle possible pour filtrer les obs// Modified renvoi les obs en fonction de leur date de modif// Created, en fonction de la date de création// Deleted, well, you already got itprotected $lifecycles = ['created','modified','deleted', // deleted peut être très lent (pas d'index coté PN pour le moment)];/*** Paramêtres par défaut disponibles pour la ligne de commande* le tableau se construit de la forme suivante :* - clé = nom du paramêtre '-foo'* - value = contient un nouveau tableau composé de cette façon :* - booléen: true si le paramêtre est obligatoire* - booléen ou var : true si le paramêtre nécessite un valeur à sa suite ou la valeur par défaut* - string: description du contenu du paramêtre* Les paramêtres optionels devraient être déclaré à la fin du tableau.* Le dernier parametre du tableau peut avoir la valeur '...',* il contiendra alors l'ensemble des paramêtres suivant trouvés sur la ligne de commande.* @var array*/protected $parametres_autorises = array('-startDate' => array(false, true, 'Date de début (parsée par strtotime). Ex: "YYYY:MM:DD HH:II:SS"'),'-lifecycle' => array(false, true, 'Par défaut : modified ; Au choix parmi : created/modified/deleted (deleted est lent)'),'-pnObsId' => array(false, true, "ID de l'obs chez PlantNet"),'-project' => array(false, true, "projectId de l'obs chez PlantNet"),'-tbObsId' => array(false, true, "ID de l'obs chez Tela"),'-debug' => array(false, false, "Mode débug (ajoute du log) Au choix parmi : debug/log (log est plus verbeux)"),);public function __construct($script_nom, $parametres_cli) {parent::__construct($script_nom, $parametres_cli);$this->bdd = new Bdd();$this->correspondingIdsFilename = Config::get('correspondingIdsFilename');$this->debug = $this->getParametre('debug');}public function executer() {$cmd = $this->getParametre('a');switch ($cmd) {case 'updateLatest':$this->updateLatest($this->getParametre('startDate'), $this->getParametre('lifecycle'));break;case 'updateOnePN':$this->updateOnePN((int)$this->getParametre('pnObsId'), $this->getParametre('project'));break;case 'updateOneTB':$this->updateOneTB((int)$this->getParametre('tbObsId'));break;case 'updateCorrespondingIdsTable':$this->updateCorrespondingIdsTable();break;default:$msg = "Erreur : la commande '$cmd' n'existe pas!\n". ', utilisez plutôt :' . "\n". 'php cli.php PullPlantnet -a updateLatest -startDate "YYYY:MM:DD HH:II:SS" [[-lifecycle [modified]/created/deleted]]' . "\n". 'php cli.php PullPlantnet -a updateOnePN -pnObsId "1234567890" -project "xxxxxx"' . "\n". 'php cli.php PullPlantnet -a updateOneTB -tbObsId "1234567890"' . "\n". 'php cli.php PullPlantnet -a updateCorrespondingIdsTable' . "\n";throw new Exception($msg);}}/*** Considering all modified pn obs since startDate* One pn project after an other* Inserting obs in tb db (if new and matching user email)*/private function updateLatest(string $startDate = '1917:03:15 00:00:00', string $lifecycle = 'modified') {$startDate = strtotime($startDate) * 1000; // we need a microtimestamp$pnProjects = $this->getProjects(); // refresh projects listforeach ($pnProjects as $project) {do {$observations_PN = $this->getProjectLatest($project, $startDate, $lifecycle);$this->updateObs($observations_PN);} while ($this->hasMore);}}private function getProjectLatest(array $project, int $startDate, string $lifecycle): array {$this->currentProject = $project;$this->refreshBddConnexion();if (!$this->currentPage) {echo 'Projet ' . $project['name'] . "\n";// hop, on appelle le service de PN$url_service = str_replace(['{project}', '{token}', '{startDate}', '{lifecycle}'],[$project['id'], Config::get('tokenPlantnet'), $startDate, $lifecycle],Config::get('urlPlantnetBase').Config::get('urlPlantnetLatestChanges'));$this->debug("URL service derniers changements : $url_service");$this->currentPage = $url_service;}$ch = curl_init($this->currentPage);curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);$reponse = curl_exec($ch);$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);curl_close($ch);// cool, maintenant on va trier ce qu'on a reçuif (!$reponse) {throw new Exception("\nPN ne répond rien à l'adresse {$this->currentPage}\n");} elseif (200 != $code) {// l'api répond avec une 404 quand y'a une date dans le futur ou simplement pas de nouvelle obs...if (404 == $code && strpos($reponse, 'No more results')) {$this->log("Pas d'autres résultats");$this->hasMore = false;$this->currentPage = '';return [];}throw new Exception("\nPN répond avec une $code à l'adresse {$this->currentPage} : $reponse\n");}$responseJson = json_decode($reponse, true);$observations_PN = $responseJson['data'] ?? [];$this->hasMore = $responseJson['hasMore'];if ($this->hasMore) {$this->currentPage = Config::get('urlPlantnetBase').$responseJson['next'];$this->debug("URL service derniers changements, page suivante : {$this->currentPage}");} else {$this->currentPage = '';}return $observations_PN;}private function getProjects(): array {// get PN projects list$url = str_replace('{token}', Config::get('tokenPlantnet'), Config::get('urlPlantnetBase').Config::get('urlPlantnetProjects'));$this->debug("URL service liste projets : $url");$ch = curl_init($url);curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);$reponse = curl_exec($ch);$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);curl_close($ch);if (!$reponse || 200 != $code) {throw new Exception("\nPN répond avec une $code à l'adresse $url : $reponse\n");}$pnProjects = json_decode($reponse, true);echo 'Liste de projets récupérée : ' . count($pnProjects) . " projets dans la liste\n";$this->debug("Liste projets : " . json_encode($pnProjects));return $pnProjects;}/*** For a given bunch of plantnet obs, post new one threw saisie widget service*/private function updateObs(array $observations_PN) {$url_cel_widget_saisie = Config::get('urlCelWidgetSaisie');foreach ($observations_PN as $obs) {// est-ce qu'on a déjà traité cette obs ? (oui une même obs peut être dans plusieurs projects)if (in_array($obs['id'], $this->processedObsId)) {$this->log("Obs {$obs['id']} déjà traitée pendant cette run");continue;} else {$this->processedObsId[] = $obs['id'];$this->log("Obs {$obs['id']} ajoutée à la liste de cette run");}// on ne teste pas la suppression de l'obs ici, faut le faire après avoir vérifié si on l'a déjà synchroif (!isset($obs['currentName'])) {$this->log("Obs {$obs['id']} sans nom de taxon, on zap");continue; // pas de nom de taxon, obs inutile}if (!isset($obs['geo'])) {$this->log("Obs {$obs['id']} sans geom, on zap");continue; // pas de position, obs inutile}if (!isset($obs['dateObs'])) {$this->log("Obs {$obs['id']} sans date, on zap");continue; // pas de date, obs inutile}if (isset($obs['partner']['id']) && 'tela' === $obs['partner']['id']) {// c'est une obs tela// si c'est mis à jour récemment coté PN et qu'on l'a pas supprimée chez nous entre temps// on update les votes PN// on update l'identification proposée par PN// @TODO$this->log("Obs {$obs['id']} venant de Tela, mise à jour pas implémentée, on zap");continue;} elseif (!isset($obs['partner']['id'])) {// ce n'est pas une obs d'un partenaire, c'est donc une obs PN// on récupère l'id utilisateur tela via son mail$email = $obs['author']['email'];$infos_utilisateur = $this->findUserInfo($email);if (!$infos_utilisateur) {$this->log("Obs {$obs['id']} email $email ne provient pas d'un telabotaniste");continue; // c'est pas un telabotaniste}$this->log("Obs {$obs['id']} email $email provient d'un telabotaniste");$this->log(json_encode($obs));// die(var_dump($obs));// vérification que l'obs n'a pas déjà été ajoutée à la BdD de Tela$sql = "SELECT date_maj, id_observation FROM cel_plantnet WHERE id_plantnet = '{$obs['id']}'". ' -- ' . __FILE__ . ':' . __LINE__;$res = $this->bdd->recupererTous($sql);// die(var_dump($res));if ($res) {// l'obs existe déjà, on vérifie si il faut màj puis on passe à la suite// la date de l'obs est un microtime, donc on coupe les millièmes// die(var_dump((int)($obs['dateUpdated']/1000), strtotime($res[0]['date_maj'])));if ((int)($obs['dateUpdated']/1000) > strtotime($res[0]['date_maj'])) {echo "Obs déjà en base, mise à jour : ID PN {$obs['id']} ; ID TB {$res[0]['id_observation']} ; utilisateur_tb $email\n";$this->updateFromPN($obs, $res[0]['id_observation']);} else {echo "Obs déjà en base, déjà à jour : ID PN {$obs['id']} ; ID TB {$res[0]['id_observation']} ; utilisateur_tb $email\n";}continue;}if (isset($obs['deleted']) && (true === $obs['deleted'])) {$this->log("Obs {$obs['id']} supprimée coté PlantNet");continue; // obs supprimée chez PN sans être passée par nos serveurs}$images = [];$tags_images = [];$images_size = 0;foreach ($obs['images'] ?? [] as $i => $image) {if ($image['deleted']) {$this->log("Obs {$obs['id']} image {$image['id']} supprimée coté PlantNet");continue;}// téléchargement de l'image PN$img = false;$retry = 3;do {$img = file_get_contents($image['o']);if (!$img) {$retry--;$this->log("Obs {$obs['id']} lecture image {$image['id']} tentatives restantes : $retry");}} while (!$img && $retry);if (!$img) {echo "Abandon, image impossible à télécharger : {$image['o']}\n";continue;}// Écriture dans un fichier temporaire$tempfile = tempnam("/tmp", "PullPN_") . ".jpg";$handle = fopen($tempfile, "w");fwrite($handle, $img);fclose($handle);$images_size += filesize($tempfile);$this->log("Obs {$obs['id']} image {$image['id']} créée " . number_format(filesize($tempfile), 0, ',', ' ') . " octets : $tempfile");$params = ['name' => 'image' . $i,'fichier' => new CURLFile($tempfile, 'image/jpg')];// envoi de l'image PN à l'api de création$ch = curl_init(Config::get('urlCelUploadImageTemp'));curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);curl_setopt($ch, CURLOPT_POST, true);curl_setopt($ch, CURLOPT_POSTFIELDS, $params);$reponse = curl_exec($ch);$this->log("Obs {$obs['id']} image {$image['id']} envoyée à l'api de création. Réponse : $reponse");$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);curl_close($ch);unlink($tempfile);$reponse = simplexml_load_string($reponse);/* Response format if success:<?xml version="1.0" encoding="UTF-8"?><root><miniature-url>https://api-test.tela-botanica.org/tmp/cel/imgtmp/chaflipe_min.jpg</miniature-url><image-nom>chaflipe.jpg</image-nom><message></message><debogage></debogage></root>*/// var_dump($reponse);if ($reponse && '' == $reponse->message /* && '' != $reponse->{'image-nom'} */) {$images[] = (string)$reponse->{'image-nom'};$tag = $this->tagsImageTraduits[$image['organ']] ?? $image['organ'];if (trim($tag)) {$tags_images[] = $tag;}}}// var_dump($images, $tags_images);// die();$geo = $this->getGeoInfo($obs['geo']);$this->log("Obs {$obs['id']} décodage du geom : " . json_encode($obs['geo']));$this->log("Obs {$obs['id']} geom décodé : " . json_encode($geo));// on insère l'obs via le service CelWidgetSaisie$infos_obs = ['obsId1[date]' => date('d/m/Y', intdiv($obs['dateObs'], 1000)),'obsId1[latitude]' => $geo['lat'] ?? null,'obsId1[longitude]' => $geo['lon'] ?? null,'obsId1[pays]' => $geo['countryCode'] ?? null,'obsId1[code_postal]' => $geo['postcode'] ?? null,'obsId1[commune_nom]' => $geo['city'] ?? null,// 'obsId1[commune_code_insee]' => '',// 'obsId1[lieudit]' => '',// 'obsId1[milieu]' => '','obsId1[nom_sel]' => $obs['currentName'],// 'obsId1[nom_ret]' => '',// 'obsId1[famille]' => '','obsId1[certitude]' => 'douteux',// 'obsId1[notes]' => '',// 'obsId1[num_nom_ret]' => '',// 'obsId1[num_nom_sel]' => '',// 'obsId1[num_taxon]' => '','obsId1[referentiel]' => $this->findProbableTaxoRepos($this->currentProject['id']), // @TODO faire une fois et mettre en cache// 'obsId1[station]' => '','obsId1[obs_id]' => $obs['id'],'projet' => 'PlantNet','tag-img' => implode(', ', $tags_images ?? []),'tag-obs' => 'plantnet, plantnet-mobile, pn:referentiel-' . $this->currentProject['id'],'utilisateur[courriel]' => $email,'utilisateur[id_utilisateur]' => $infos_utilisateur['id'],'utilisateur[nom]' => $infos_utilisateur['nom'],'utilisateur[prenom]' => $infos_utilisateur['prenom'],'origin' => 'pullPlantnet',];foreach ($images as $i => $image) {$infos_obs["obsId1[image_nom][$i]"] = $image;}$this->log("Obs {$obs['id']} prête à être insérée : " . json_encode($infos_obs));// curl post $infos_obs$ch = curl_init($url_cel_widget_saisie);curl_setopt($ch, CURLOPT_POST, true);curl_setopt($ch, CURLOPT_POSTFIELDS, $infos_obs);curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);$reponse = curl_exec($ch);$this->log("Obs {$obs['id']} réponse de la requête d'insertion : " . json_encode($reponse));$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);curl_close($ch);if (!$reponse || 200 != $code) {throw new Exception("Ça a pété à l'envoi de l'obs :\n $reponse\n avec l'obs : " . json_encode($infos_obs) . "\n");}$id_obs_tb = json_decode($reponse, true)['id'];$id_obs_pn = $obs['id'];// on insère les ids de correspondance obsPN obsTB dans la table cel_plantnet$sql = 'INSERT INTO cel_plantnet (id_observation, id_plantnet, date_maj)'.' VALUES (%s, %s, NOW())'. ' -- ' . __FILE__ . ':' . __LINE__;$sql = sprintf($sql, $this->bdd->proteger($id_obs_tb), $this->bdd->proteger($id_obs_pn));$this->bdd->requeter($sql);$date = date('d/m/Y H:i:s', intdiv($obs['dateObs'], 1000));$count_img = count($images);$images_size = number_format($images_size, 0, ',', ' ');echo "Obs insérée en base : ID PN $id_obs_pn ; ID TB $id_obs_tb ; utilisateur_tb $email ; date_obs $date ; images $count_img ; taille_octets $images_size \n";}}}/*** Return TB taxo repos names for given PN project** @return string*/private function findProbableTaxoRepos($pnProjectId) {$selectedRepos = [];// Référentiels tela botanica indexés par référentiels PN (version octobre 2019)// @TODO à revoir en fonction des nouvelles mises en prod de nouveaux référentiels$tbTaxoRepos = ['afn' => [/*'apd', */'isfan'],'aft' => ['apd'],'antilles' => ['bdtxa', 'taxref'],'canada' => ['vascan'],'comores' => ['apd'],'endemia' => ['florical', 'taxref'],'taxref ' => ['bdtfx'],'guyane' => ['aublet', 'taxref'],'hawai' => [],'lapaz' => ['aublet', 'taxref'],'martinique' => ['bdtxa', 'taxref'],'maurice' => ['apd'],'medor' => ['lbf'],'namerica' => [],'polynesiefr' => ['taxref'],'prosea' => ['asia'],'prota' => ['isfan', 'apd'],'provence' => ['bdtfx', 'taxref'],'reunion' => ['bdtre', 'taxref'],'salad' => ['bdtfx', 'taxref'],'useful' => [],'weurope' => ['bdtfx'],'the-plant-list' => [],'iscantree' => ['apd'],'central-america' => [],'invasion' => ['bdtfx'],'weeds' => ['bdtfx'],'malaysia' => [],];if (array_key_exists($pnProjectId, $tbTaxoRepos)) {array_map(function($repo) use ($selectedRepos) {$selectedRepos[] = $repo;}, $tbTaxoRepos[$pnProjectId]);}// @TODO chercher le nom dans plusieurs référentiels avant d'envoyer l'obs// prendre "enrichirDonneesTaxonomiques" du service de saisie et le coller ici// en attendant on envoi juste le premier référentiel trouvéreturn $selectedRepos[0] ?? '';}/** Retrouve les infos d'un utilisateur tela à partir de son email* @param string $email User email* @return mixed Tableau des infos de l'utilisateur* False si l'email n'est pas trouvé dans l'annuaire*/private function findUserInfo($email) {// cherche dans le cacheif (array_key_exists($email, $this->userInfos)) {$infosUtilisateur = $this->userInfos[$email];// echo("Email in cache : $email\n");} else {$infosUtilisateur = false;$urlInfosUtilisateur = str_replace('{email}', $email, Config::get('urlAnnuaireIdParEmail'));$reponse = false;$retry = 3;do {$ch = curl_init($urlInfosUtilisateur);curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);$reponse = curl_exec($ch);$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);curl_close($ch);$retry--;if (!$reponse) {sleep(1);$this->log("Echec annuaire pour la requête $urlInfosUtilisateur tentatives restantes : $retry");}} while (!$reponse && $retry);if (!$reponse) {echo "Abandon, l'annuaire semble injoignable, impossible à télécharger : $urlInfosUtilisateur\n";return false;}// on peut pas tester le code de réponse de cette api, si l'utilisateur n'est pas trouvé ça fait une 500 >_<// une bonne réponse ressemble à ça :// {"killian@tela-botanica.org":{"id":"30489","prenom":"Killian","nom":"Stefanini","pseudo":"Killian Stefanini","pseudoUtilise":true,"intitule":"Killian Stefanini","avatar":"\/\/www.gravatar.com\/avatar\/a9b9b8484076540924c03af816c77fc8?s=50&r=g&d=mm","groupes":{"19226":"adm","18943":"","23152":"","21684":"","21598":"adm","23184":"","23516":""},"permissions":["editor"],"nomWiki":"KillianStefanini"}}$reponse = json_decode($reponse, true);if (!isset($reponse)) {throw new Exception("\nL'annuaire n'a pas répondu avec du json : code $code à l'adresse $urlInfosUtilisateur : $reponse\n");}if (!isset($reponse['error'])) {$infosUtilisateur = array_shift($reponse);}// met en cache$this->userInfos[$email] = $infosUtilisateur;// echo("Email cached : $email\n");}return $infosUtilisateur;}private function getGeoInfo($obs) {$geo = [];if (!isset($obs['lat']) && !isset($obs['lon'])) {return $geo;}// $data = [// 'hitsPerPage' => 1,// 'aroundLatLng' => "{$obs['lat']},{$obs['lon']}"// ];$headers = ['X-Algolia-Application-Id' => Config::get('algoliaApplicationId'),'X-Algolia-API-Key' => Config::get('algoliaAPIKey'),];$lat = number_format($obs['lat'], 6, '.', '');$lon = number_format($obs['lon'], 6, '.', '');$ch = curl_init(Config::get('urlReverseGeocodingLatLng')."$lat,$lon");curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);$reponse = curl_exec($ch);$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);curl_close($ch);if ($reponse) {$reponse = json_decode($reponse, true);// die(var_dump($reponse));$infos = $reponse['hits'][0];$geo = ['lat' => (string) $obs['lat'],'lon' => (string) $obs['lon'],'country' => $infos['country']['fr'] ?? $infos['country']['default'],'city' => $infos['city']['default'][0] ?? null,'postcode' => $infos['postcode'][0] ?? null,'countryCode' => strtoupper($infos['country_code']),];}return $geo;}private function updateFromPN(array $pnObs, string $tbObsId) {if (!is_array($pnObs) || !is_string($tbObsId)) {// die(var_dump($pnObs, $tbObsId));throw new Exception("\nBad params types, give me an array and an integer plz\n");}// die(var_dump($pnObs));// log update date to cel_plantnet$sql = "UPDATE cel_plantnet SET date_maj = NOW() WHERE id_observation = $tbObsId". ' -- ' . __FILE__ . ':' . __LINE__;$this->bdd->requeter($sql);// check for deletedif (isset($pnObs['deleted']) && (true === $pnObs['deleted'])) {// est-ce une obs issue de PN ?//// faut regarder le champ input_source == PlantNet$sql = "SELECT input_source FROM occurrence WHERE id = '$tbObsId'". ' -- ' . __FILE__ . ':' . __LINE__;$res = $this->bdd->recupererTous($sql);if (isset($res[0]) && ('PlantNet' === $res[0]['input_source'])) {// oui ? alors supprimer obs !echo "Obs supprimée coté PN, suppression : ID PN {$pnObs['id']} ; ID TB {$res[0]['id_observation']}\n";$sql = "UPDATE photo SET occurrence_id = NULL WHERE occurrence_id = '$tbObsId'". ' -- ' . __FILE__ . ':' . __LINE__;$this->bdd->requeter($sql);$sql = "DELETE FROM occurrence WHERE id = '$tbObsId'". ' -- ' . __FILE__ . ':' . __LINE__;$this->bdd->requeter($sql);}}// check for deleted imagesforeach ($pnObs['images'] as $image) {if ($image['deleted']) {// no idea// y'a pas encore de lien tangible entre les images PN et les notres}}// @TODO : finir la comparaison des proposition entre celles de Tela et celles PN// // check for votes (notes et taxon)// $names = [];// foreach ($pnObs['computed']['votes'] as $vote) {// echo($vote['name']);// echo($vote['score']['pn']);// echo($vote['score']['total']);// $names[$vote['score']['pn']] = $vote['name'];// }// ksort($names, SORT_NUMERIC); // votes sorted by score asc// $pn_sorted_votes = array_reverse($names, true); // desc// // get existing votes// $sql = "SELECT id_commentaire, nom_sel, nom_sel_nn, nom_referentiel, nom_referentiel, utilisateur_courriel, proposition_initiale, proposition_retenue"// . "WHERE ce_observation = '$tbObsId' AND ce_commentaire_parent = 0"// . ' -- ' . __FILE__ . ':' . __LINE__;// $existing_votes = $this->bdd->recupererTous($sql);// // @TODO : compare votes// // insert_new_votes($pn_sorted_votes, $existing_votes);}private function updateOnePN($pnObsId, $pnProjectId) {if (!is_int($pnObsId) || !is_string($pnProjectId)) {die(var_dump($pnObsId, $pnProjectId));throw new Exception("\nBad params types, give me an integer and a string plz\n");}// get PN project list$list = [];$pnProjects = $this->getProjects(); // refresh projects listforeach ($pnProjects as $project) {$list[$project['id']] = $project['name'];}// if project not in list display listif (!array_key_exists($pnProjectId, $list)) {echo "Available projects:\n";foreach ($list as $projectId => $projectName) {echo " - $projectId ($projectName)\n";}throw new Exception("Project $pnProjectId does not exist\n");} else {$this->currentProject = ['id' => $pnProjectId,'name' => $list[$pnProjectId],];}// get PN obs$urlInfosObs = str_replace(['{token}', '{project}', '{id}'],[Config::get('tokenPlantnet'), $pnProjectId, $pnObsId],Config::get('urlPlantnetBase').Config::get('urlPlantnetObsById'));$ch = curl_init($urlInfosObs);curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);$response = curl_exec($ch);$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);curl_close($ch);if (!$response || 200 != $code) {throw new Exception("\nPlantnet api ($urlInfosObs) replied with code $code : $response\n");}// change last modification date to force update$obs = json_decode($response, true);$date = new DateTime();$date->setTimestamp(intdiv($obs['dateUpdated'], 1000) - 1);$date = $this->bdd->proteger($date->format('Y-m-d H:i:s'));$sql = "UPDATE cel_plantnet SET date_maj = $date WHERE id_plantnet = '$pnObsId'". ' -- ' . __FILE__ . ':' . __LINE__;$this->bdd->requeter($sql);$this->updateObs([$obs]);}private function updateOneTB($id) {$corres = $this->findMatchingPartnerId($id, 'tb');echo "L'obs tela avec l'id $id a été trouvée chez plantnet avec l'id : " .$corres['pn'] . "\n";echo "La suite n'a pas été implémentée.\n";}/** Trouve les ids d'une obs chez les partenaires* Il faut lancer updateMatchingPartnersIds pour obtenir une liste fraiche** @param string $id L'id de l'obs* @param string $partner Le partenaire correspondant à l'id de l'obs** @return mixed Le tableau de correspondances d'ids* False si l'id n'est pas trouvé*/private function findMatchingPartnerId($id, $partner) {if (!in_array($partner, $this->allowedPartners)) {throw new Exception("\nUnknown partner : $partner. Available Partners : " . implode(', ', $this->allowedPartners) . "\n");}// le json ressemble à ça :// {"tb":"2369732","pn":"1001020199"}, {"tb":"2369733","pn":"1001020176"}, ...$ids = json_decode(file_get_contents($this->correspondingIdsFilename), true);// extrait la colonne du partenaire recherché du tableau multidimensionnel fraichement décodé$partnerColumn = array_column($ids, $partner);// cherche l'index de l'id recherché$index = array_search($id, $partnerColumn);return $index ? $ids[$index] : false;}private function updateCorrespondingIdsTable() {// first update cache file if necessaryif (!file_exists($this->correspondingIdsFilename) || time() > (filemtime($this->correspondingIdsFilename) + (24 * 3600))) {$this->updateMatchingPartnersIds();}$data = file_get_contents($this->correspondingIdsFilename);$data = json_decode($data, true);$now = new DateTime;$now = $this->bdd->proteger($now->format('Y-m-d H:i:s'));$sqlInsert = 'INSERT INTO cel_plantnet (id_observation, id_plantnet, date_maj)'. ' VALUES %s'. ' ON DUPLICATE KEY UPDATE id_observation=VALUES(id_observation), id_plantnet=VALUES(id_plantnet)'. ' -- ' . __FILE__ . ':' . __LINE__;$values = '';echo "Updating matching partners ids table\n";foreach ($data as $id => $corres) {$id_obs_tb = $this->bdd->proteger($corres['tb']);$id_obs_pn = $this->bdd->proteger($corres['pn']);// on insère les ids de correspondance obsPN obsTB dans la table cel_plantnet$values .= " ($id_obs_tb, $id_obs_pn, $now),";if (0 === $id % 100) {$values = substr($values, 0, -1); // retire la dernière virgule$sql = sprintf($sqlInsert, $values);// var_dump($sql);$this->bdd->requeter($sql);$values = '';}}// Faut envoyer les dernières données qui sont pas passées dans le modulo$values = substr($values, 0, -1); // retire la dernière virgule$sql = sprintf($sqlInsert, $values);// var_dump($sql);$this->bdd->requeter($sql);$count = count($data);echo "Success: #$count updated\n";}private function updateMatchingPartnersIds() {// curl -X GET --header 'Accept: application/json' 'https://bourbonnais.cirad.fr:8081/v1/edi/observations/partnerids?token=f0ca433febe320675c24ee2ddfab8b82f65d556b' > partnerids.txt$matchingPartnersIdsServiceUrl = str_replace('{token}',Config::get('tokenPlantnet'),Config::get('urlPlantnetBase').Config::get('urlPlantnetMatchingPartner'));$ch = curl_init($matchingPartnersIdsServiceUrl);curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);echo "Updating matching partners ids...\n";$reponse = curl_exec($ch);$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);curl_close($ch);if (!$reponse || 200 != $code) {throw new Exception("\nError updating partners ids, http error code \"$code\" whith url \"$matchingPartnersIdsServiceUrl\"...\n");}echo "Response data from PN api gathered, know computing...\n";if (!file_put_contents($this->correspondingIdsFilename, $reponse)) {throw new Exception("\nError writing correspondingIdsFilename with path : " . $this->correspondingIdsFilename . "\n");}echo "Matching partners ids saved!";}private function debug($text) {if ($this->debug) {echo 'DEBUG - ' . $text . "\n";}}private function log($text) {if ('log' == $this->debug) {echo 'LOG - ' . $text . "\n";}}private function refreshBddConnexion() {$this->bdd = null;$this->log("Fermeture et création d'une nouvelle connexion à la BdD");$this->bdd = new Bdd();}}