Subversion Repositories eFlore/Applications.cel

Rev

Rev 3767 | Rev 3770 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
3755 killian 1
<?php
2
 
3
/*
4
	PHP 7
5
 
6
	Script used to synchronize PN occ with CEL
7
*/
8
 
9
class PullPlantnet extends Script {
10
 
11
	protected $bdd;
12
 
13
	protected $allowedPartners = ['tb', 'pn'];
14
 
15
	// Nom du fichier contenant les correspondances d'ID TB/PN
16
	protected $correspondingIdsFilename = '';
17
 
18
	// Cache d'informations utilisateurs provenant de l'annuaire TB
19
	protected $userInfos = [];
20
 
3757 killian 21
	// Cache des obs id PN traitées pendant cette run
22
	protected $processedObsId = [];
23
 
3755 killian 24
	// Paramètre de suivi et de pagination de l'API des obs PN
25
	protected $currentPage = '';
26
	protected $currentProject = '';
27
	protected $hasMore = false;
28
	protected $count = 0;
29
 
30
	// Traduction des organes de PN
31
	protected $tagsImageTraduits = [
32
		'flower' => 'fleur',
33
		'fruit' => 'fruit',
34
		'leaf' => 'feuille',
35
		'bark' => 'écorce',
36
		'branch' => 'branche',
37
		'habit' => 'port',
38
	];
39
 
3767 killian 40
	// Différents types de lifecle possible pour filtrer les obs
41
	// Modified renvoi les obs en fonction de leur date de modif
42
	// Created, en fonction de la date de création
43
	// Deleted, well, you already got it
44
	protected $lifecycles = [
45
		'created',
46
		'modified',
47
		'deleted', // deleted peut être très lent
48
	];
49
 
3755 killian 50
	/**
51
	 * Paramêtres par défaut disponibles pour la ligne de commande
52
	 * le tableau se construit de la forme suivante :
53
	 * - clé =  nom du paramêtre '-foo'
54
	 * - value = contient un nouveau tableau composé de cette façon :
55
	 *  - booléen: true si le paramêtre est obligatoire
56
	 *  - booléen ou var : true si le paramêtre nécessite un valeur à sa suite ou la valeur par défaut
57
	 *  - string: description du contenu du paramêtre
58
	 * Les paramêtres optionels devraient être déclaré à la fin du tableau.
59
	 * Le dernier parametre du tableau peut avoir la valeur '...',
60
	 * il contiendra alors l'ensemble des paramêtres suivant trouvés sur la ligne de commande.
61
	 * @var array
62
	 */
63
	protected $parametres_autorises = array(
3767 killian 64
		'-startDate' => array(false, true, 'Date de début (parsée par strtotime). Ex: "YYYY:MM:DD HH:II:SS"'),
65
		'-lifecycle' => array(false, true, 'Par défaut : modified ; Au choix parmi : created/modified/deleted (deleted est lent)'),
3755 killian 66
		'-pnObsId' => array(false, true, "ID de l'obs chez PlantNet"),
3761 killian 67
		'-project' => array(false, true, "projectId de l'obs chez PlantNet"),
3755 killian 68
		'-tbObsId' => array(false, true, "ID de l'obs chez Tela"),
69
	);
70
 
71
	public function __construct($script_nom, $parametres_cli) {
72
		parent::__construct($script_nom, $parametres_cli);
73
		$this->bdd = new Bdd();
74
 
75
		$this->correspondingIdsFilename = Config::get('correspondingIdsFilename');
76
	}
77
 
78
	public function executer() {
79
		$cmd = $this->getParametre('a');
80
 
81
		switch ($cmd) {
82
			case 'updateLatest':
83
				$this->updateLatest($this->getParametre('startDate'));
84
				break;
85
			case 'updateOnePN':
3761 killian 86
				$this->updateOnePN((int)$this->getParametre('pnObsId'), $this->getParametre('project'));
3755 killian 87
				break;
88
			case 'updateOneTB':
89
				$this->updateOneTB($this->getParametre('tbObsId'));
90
				break;
91
			case 'updateCorrespondingIdsTable':
92
				$this->updateCorrespondingIdsTable();
93
				break;
94
			default:
95
				$msg = "Erreur : la commande '$cmd' n'existe pas!\n"
96
						. ', utilisez plutôt :' . "\n"
3767 killian 97
						. 'php cli.php PullPlantnet -a updateLatest -startDate "YYYY:MM:DD HH:II:SS" [[-lifecycle [modified]/created/deleted]]' . "\n"
3761 killian 98
						. 'php cli.php PullPlantnet -a updateOnePN -pnObsId "xxxxxx" -project "xxxxxx"' . "\n"
3755 killian 99
						. 'php cli.php PullPlantnet -a updateOneTB -tbObsId "xxxxxx"' . "\n"
100
						. 'php cli.php PullPlantnet -a updateCorrespondingIdsTable' . "\n"
101
						;
102
				throw new Exception($msg);
103
		}
104
	}
105
 
106
	/**
107
	 * Considering all modified pn obs since startDate
108
	 * One pn project after an other
109
	 * Inserting obs in tb db (if new and matching user email)
110
	 */
3767 killian 111
	private function updateLatest(string $startDate = '1917:03:15 00:00:00', string $lifecycle = 'modified') {
112
		$startDate = strtotime($startDate) * 1000; // we need a microtimestamp
3755 killian 113
 
114
		$pnProjects = $this->getProjects(); // refresh projects list
115
		foreach ($pnProjects as $project) {
116
			do {
3767 killian 117
				$observations_PN = $this->getProjectLatest($project, $startDate, $lifecycle);
3755 killian 118
				$this->updateObs($observations_PN);
119
			} while ($this->hasMore);
120
		}
121
	}
122
 
3767 killian 123
	private function getProjectLatest(array $project, int $startDate, string $lifecycle): array {
3755 killian 124
		$this->currentProject = $project;
125
 
126
		if (!$this->currentPage) {
127
			echo 'Projet ' . $project['name'] . "\n";
128
 
129
			// hop, on appelle le service de PN
130
			$url_service = str_replace(
3767 killian 131
				['{project}', '{token}', '{startDate}', '{lifecycle}'],
132
				[$project['id'], Config::get('tokenPlantnet'), $startDate, $lifecycle],
3755 killian 133
				Config::get('urlPlantnetBase').Config::get('urlPlantnetLatestChanges')
134
			);
135
			// die(var_dump($url_service));
136
			$this->currentPage = $url_service;
137
		}
138
 
139
		$ch = curl_init($this->currentPage);
140
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
141
		$reponse = curl_exec($ch);
142
		$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
143
		curl_close($ch);
144
 
145
		// cool, maintenant on va trier ce qu'on a reçu
146
		if (!$reponse) {
147
			throw new Exception("\nPN ne répond rien à l'adresse {$this->currentPage}\n");
148
		} elseif (200 != $code) {
149
			// l'api répond avec une 404 quand y'a une date dans le futur ou simplement pas de nouvelle obs...
150
			if (404 == $code && strpos($reponse, 'No more results')) {
151
				$this->hasMore = false;
152
				$this->currentPage = '';
153
				return [];
154
			}
155
			throw new Exception("\nPN répond avec une $code à l'adresse {$this->currentPage} : $reponse\n");
156
		}
157
		$responseJson = json_decode($reponse, true);
158
		$observations_PN = $responseJson['data'] ?? [];
159
 
160
		$this->hasMore = $responseJson['hasMore'];
161
		if ($this->hasMore) {
162
			$this->count += count($observations_PN);
3761 killian 163
			$this->currentPage = Config::get('urlPlantnetBase').$responseJson['next'];
3755 killian 164
		} else {
165
			$this->currentPage = '';
166
			$this->count = 0;
167
		}
168
 
169
		return $observations_PN;
170
	}
171
 
172
	private function getProjects(): array {
173
		// get PN projects list
174
		$url = str_replace('{token}', Config::get('tokenPlantnet'), Config::get('urlPlantnetBase').Config::get('urlPlantnetProjects'));
175
 
176
		$ch = curl_init($url);
177
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
178
		$reponse = curl_exec($ch);
179
		$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
180
		curl_close($ch);
181
 
182
		if (!$reponse || 200 != $code) {
3767 killian 183
			throw new Exception("\nPN répond avec une $code à l'adresse $url : $reponse\n");
3755 killian 184
		}
185
		$pnProjects = json_decode($reponse, true);
186
 
187
		echo 'Liste de projets récupérée : ' . count($pnProjects) . " projets dans la liste\n";
188
 
189
		return $pnProjects;
190
	}
191
 
192
	/**
193
	 * For a given bunch of plantnet obs, post new one threw saisie widget service
194
	 */
195
	private function updateObs(array $observations_PN) {
196
 
197
		$url_cel_widget_saisie = Config::get('urlCelWidgetSaisie');
198
 
199
		foreach ($observations_PN as $obs) {
3757 killian 200
			// est-ce qu'on a déjà traité cette obs ? (oui une même obs peut être dans plusieurs projects)
201
			if (in_array($obs['id'], $this->processedObsId)) {
202
				continue;
203
			} else {
204
				$this->processedObsId[] = $obs['id'];
205
			}
206
 
3755 killian 207
			// on ne teste pas la suppression de l'obs ici, faut le faire après avoir vérifié si on l'a déjà synchro
208
			if (!isset($obs['currentName'])) {
209
				continue; // pas de nom de taxon, obs inutile
210
			}
211
			if (!isset($obs['geo'])) {
212
				continue; // pas de position, obs inutile
213
			}
214
			if (!isset($obs['dateObs'])) {
215
				continue; // pas de date, obs inutile
216
			}
217
 
218
			if (isset($obs['partner']['id']) && 'tela' === $obs['partner']['id']) {
219
				// c'est une obs tela
220
				// si c'est mis à jour récemment coté PN et qu'on l'a pas supprimée chez nous entre temps
221
					// on update les votes PN
222
					// on update l'identification proposée par PN
223
 
224
				// @TODO
225
				continue;
226
			} elseif (!isset($obs['partner']['id'])) {
227
				// ce n'est pas une obs d'un partenaire, c'est donc une obs PN
228
				// on récupère l'id utilisateur tela via son mail
229
				$email = $obs['author']['email'];
230
				$infos_utilisateur = $this->findUserInfo($email);
231
				if (!$infos_utilisateur) {
232
					continue; // c'est pas un telabotaniste
233
				}
3761 killian 234
				//echo "Synchro d'une obs d'un telabotaniste : $email";
3755 killian 235
				// echo json_encode($obs);
236
				// die(var_dump($obs));
237
 
238
				// vérification que l'obs n'a pas déjà été ajoutée à la BdD de Tela
239
				$sql = "SELECT date_maj, id_observation FROM cel_plantnet WHERE id_plantnet = '{$obs['id']}'"
240
						. ' -- ' . __FILE__ . ':' . __LINE__;
241
				$res = $this->bdd->recupererTous($sql);
242
				// die(var_dump($res));
243
				if ($res) {
244
					// l'obs existe déjà, on vérifie si il faut màj puis on passe à la suite
245
					// la date de l'obs est un microtime, donc on coupe les millièmes
246
					// die(var_dump((int)($obs['dateUpdated']/1000), strtotime($res[0]['date_maj'])));
247
					if ((int)($obs['dateUpdated']/1000) > strtotime($res[0]['date_maj'])) {
3761 killian 248
						echo "Obs déjà en base, mise à jour : ID PN {$obs['id']} ; ID TB {$res[0]['id_observation']} ; utilisateur_tb $email\n";
3755 killian 249
						$this->updateFromPN($obs, $res[0]['id_observation']);
250
					} else {
3761 killian 251
						echo "Obs déjà en base, déjà à jour : ID PN {$obs['id']} ; ID TB {$res[0]['id_observation']} ; utilisateur_tb $email\n";
3755 killian 252
					}
253
					continue;
254
				}
255
 
256
				if (isset($obs['deleted']) && (true === $obs['deleted'])) {
257
					continue; // obs supprimée chez PN sans être passée par nos serveurs
258
				}
259
 
260
				$images = [];
261
				$tags_images = [];
262
				foreach ($obs['images'] ?? [] as $i => $image) {
263
					if ($image['deleted']) {
264
						continue;
265
					}
266
 
267
					// téléchargement de l'image PN
3762 killian 268
					$img = false;
269
					$retry = 3;
270
					do {
271
						$img = file_get_contents($image['o']);
272
						$retry--;
273
					} while (!$img && $retry);
274
 
275
					if (!$img) {
276
						echo "Abandon, image impossible à télécharger : {$image['o']}\n";
277
						continue;
278
					}
279
 
280
					// Écriture dans un fichier temporaire
3755 killian 281
					$tempfile = tempnam("/tmp", "PullPN_") . ".jpg";
282
					$handle = fopen($tempfile, "w");
3762 killian 283
					fwrite($handle, $img);
3755 killian 284
					fclose($handle);
285
 
3761 killian 286
					echo "Image de " . number_format(filesize($tempfile), 0, ',', ' ') . " octets créé : $tempfile\n";
3755 killian 287
 
288
					$params = [
289
						'name' => 'image' . $i,
290
						'fichier' => new CURLFile($tempfile, 'image/jpg')
291
					];
292
 
293
					// envoi de l'image PN à l'api de création
294
					$ch = curl_init(Config::get('urlCelUploadImageTemp'));
295
					curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
296
					curl_setopt($ch, CURLOPT_POST, true);
297
					curl_setopt($ch, CURLOPT_POSTFIELDS, $params);
298
					$reponse = curl_exec($ch);
299
					// var_dump($reponse);
300
					$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
301
					curl_close($ch);
302
 
303
					unlink($tempfile);
304
 
305
					$reponse = simplexml_load_string($reponse);
306
					/* Response format if success:
307
					<?xml version="1.0" encoding="UTF-8"?>
308
					<root>
309
						<miniature-url>https://api-test.tela-botanica.org/tmp/cel/imgtmp/chaflipe_min.jpg</miniature-url>
310
						<image-nom>chaflipe.jpg</image-nom>
311
						<message></message>
312
						<debogage></debogage>
313
					</root>
314
					*/
315
					// var_dump($reponse);
316
					if ($reponse && '' == $reponse->message /* && '' != $reponse->{'image-nom'} */) {
317
						$images[] = (string)$reponse->{'image-nom'};
3769 killian 318
 
319
						$tag = $this->tagsImageTraduits[$image['organ']] ?? $image['organ'];
320
						if (trim($tag)) {
321
							$tags_images[] = $tag;
322
						}
3755 killian 323
					}
324
				}
325
				// var_dump($images, $tags_images);
326
				// die();
327
 
3761 killian 328
				$geo = $this->getGeoInfo($obs['geo']);
329
 
3755 killian 330
				// on insère l'obs via le service CelWidgetSaisie
331
				$infos_obs = [
3761 killian 332
					'obsId1[date]' => date('d/m/Y', intdiv($obs['dateObs'], 1000)),
333
					'obsId1[latitude]' => $geo['lat'] ?? null,
334
					'obsId1[longitude]' => $geo['lon'] ?? null,
335
					'obsId1[pays]' => $geo['countryCode'] ?? null,
336
					'obsId1[code_postal]' => $geo['postcode'] ?? null,
337
					'obsId1[commune_nom]' => $geo['city'] ?? null,
3755 killian 338
					// 'obsId1[commune_code_insee]' => '',
339
					// 'obsId1[lieudit]' => '',
340
					// 'obsId1[milieu]' => '',
3761 killian 341
					'obsId1[nom_sel]' => $obs['currentName'],
3755 killian 342
					// 'obsId1[nom_ret]' => '',
3761 killian 343
					// 'obsId1[famille]' => '',
3755 killian 344
					'obsId1[certitude]' => 'douteux',
345
					// 'obsId1[notes]' => '',
346
					// 'obsId1[num_nom_ret]' => '',
347
					// 'obsId1[num_nom_sel]' => '',
348
					// 'obsId1[num_taxon]' => '',
349
					'obsId1[referentiel]' => $this->findProbableTaxoRepos($this->currentProject['id']),
350
					// 'obsId1[station]' => '',
351
					'obsId1[obs_id]' => $obs['id'],
352
					'projet' => 'PlantNet',
353
					'tag-img' => implode(', ', $tags_images ?? []),
354
					'tag-obs' => 'plantnet, plantnet-mobile, pn:referentiel-' . $this->currentProject['id'],
355
					'utilisateur[courriel]' => $email,
356
					'utilisateur[id_utilisateur]' => $infos_utilisateur['id'],
357
					'utilisateur[nom]' => $infos_utilisateur['nom'],
358
					'utilisateur[prenom]' => $infos_utilisateur['prenom'],
359
					'origin' => 'pullPlantnet',
360
				];
361
 
362
				foreach ($images as $i => $image) {
363
					$infos_obs["obsId1[image_nom][$i]"] = $image;
364
				}
3761 killian 365
				// var_dump($infos_obs);
3755 killian 366
 
367
				// curl post $infos_obs
368
				$ch = curl_init($url_cel_widget_saisie);
369
				curl_setopt($ch, CURLOPT_POST, true);
370
				curl_setopt($ch, CURLOPT_POSTFIELDS, $infos_obs);
371
				curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
372
				$reponse = curl_exec($ch);
373
				$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
374
				curl_close($ch);
375
 
376
				if (!$reponse || 200 != $code) {
377
					throw new Exception("Ça a pété à l'envoi de l'obs :\n $reponse\n avec l'obs : " . json_encode($infos_obs) . "\n");
378
				}
379
 
380
				// var_dump($reponse);
3761 killian 381
				$id_obs_tb = json_decode($reponse, true)['id'];
382
				$id_obs_pn = $obs['id'];
3755 killian 383
				// on insère les ids de correspondance obsPN obsTB dans la table cel_plantnet
384
				$sql = 'INSERT INTO cel_plantnet (id_observation, id_plantnet, date_maj)'
385
						.' VALUES (%s, %s, NOW())'
386
						. ' -- ' . __FILE__ . ':' . __LINE__;
3761 killian 387
				$sql = sprintf($sql, $this->bdd->proteger($id_obs_tb), $this->bdd->proteger($id_obs_pn));
3755 killian 388
				$this->bdd->requeter($sql);
3761 killian 389
 
390
				echo "Obs insérée en base : ID PN $id_obs_pn ; ID TB $id_obs_tb ; utilisateur_tb $email\n";
3755 killian 391
			}
392
		}
393
	}
394
 
395
	/**
396
	 * Return TB taxo repos names for given PN project
397
	 *
398
	 * @return string
399
	 */
400
	private function findProbableTaxoRepos($pnProjectId) {
401
		$selectedRepos = [];
402
		// Référentiels tela botanica indexés par référentiels PN (version octobre 2019)
403
		// @TODO à revoir en fonction des nouvelles mises en prod de nouveaux référentiels
404
		$tbTaxoRepos = [
405
			'afn' => [/*'apd', */'isfan'],
406
			'aft' => ['apd'],
407
			'antilles' => ['bdtxa', 'taxref'],
408
			'canada' => ['vascan'],
409
			'comores' => ['apd'],
410
			'endemia' => ['florical', 'taxref'],
411
			'taxref	' => ['bdtfx'],
412
			'guyane' => ['aublet', 'taxref'],
413
			'hawai' => [],
414
			'lapaz' => ['aublet', 'taxref'],
415
			'martinique' => ['bdtxa', 'taxref'],
416
			'maurice' => ['apd'],
417
			'medor' => ['lbf'],
418
			'namerica' => [],
419
			'polynesiefr' => ['taxref'],
420
			'prosea' => ['asia'],
421
			'prota' => ['isfan', 'apd'],
422
			'provence' => ['bdtfx', 'taxref'],
423
			'reunion' => ['bdtre', 'taxref'],
424
			'salad' => ['bdtfx', 'taxref'],
425
			'useful' => [],
426
			'weurope' => ['bdtfx'],
427
			'the-plant-list' => [],
428
			'iscantree' => ['apd'],
429
			'central-america' => [],
430
			'invasion' => ['bdtfx'],
431
			'weeds' => ['bdtfx'],
432
			'malaysia' => [],
433
		];
434
 
435
		if (array_key_exists($pnProjectId, $tbTaxoRepos)) {
436
			array_map(function($repo) use ($selectedRepos) {
437
				$selectedRepos[] = $repo;
438
			}, $tbTaxoRepos[$pnProjectId]);
439
		}
440
 
441
		// @TODO chercher le nom dans plusieurs référentiels avant d'envoyer l'obs
442
		// prendre "enrichirDonneesTaxonomiques" du service de saisie et le coller ici
443
		// en attendant on envoi juste le premier référentiel trouvé
444
		return $selectedRepos[0] ?? '';
445
	}
446
 
447
	/*
448
	 * Retrouve les infos d'un utilisateur tela à partir de son email
449
	 * @param string $email User email
450
	 * @return mixed 		Tableau des infos de l'utilisateur
451
	 *						False si l'email n'est pas trouvé dans l'annuaire
452
	 */
453
	private function findUserInfo($email) {
454
		// cherche dans le cache
455
		if (array_key_exists($email, $this->userInfos)) {
456
			$infosUtilisateur = $this->userInfos[$email];
457
			// echo("Email in cache : $email\n");
458
		} else {
459
			$infosUtilisateur = false;
460
			$urlInfosUtilisateur = str_replace('{email}', $email, Config::get('urlAnnuaireIdParEmail'));
461
 
462
			$ch = curl_init($urlInfosUtilisateur);
463
			curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
464
			$reponse = curl_exec($ch);
465
			$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
466
			curl_close($ch);
467
 
468
			// on peut pas tester le code de réponse de cette api, si l'utilisateur n'est pas trouvé ça fait une 500 >_<
469
			// une bonne réponse ressemble à ça :
470
			// {"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"}}
471
			$reponse = json_decode($reponse, true);
472
			if (!isset($reponse)) {
473
				throw new Exception("\nL'annuaire n'a pas répondu avec du json : code $code à l'adresse $urlInfosUtilisateur : $reponse\n");
474
			}
475
 
476
			if (!isset($reponse['error'])) {
477
				$infosUtilisateur = array_shift($reponse);
478
			}
479
 
480
			// met en cache
481
			$this->userInfos[$email] = $infosUtilisateur;
482
			// echo("Email cached : $email\n");
483
		}
484
 
485
		return $infosUtilisateur;
486
	}
487
 
3761 killian 488
	private function getGeoInfo($obs) {
489
		$geo = [];
490
		if (!isset($obs['lat']) && !isset($obs['lon'])) {
491
			return $geo;
492
		}
493
 
494
		// $data = [
495
		// 	'hitsPerPage' => 1,
496
		// 	'aroundLatLng' => "{$obs['lat']},{$obs['lon']}"
497
		// ];
498
		$headers = [
499
			'X-Algolia-Application-Id' => Config::get('algoliaApplicationId'),
500
			'X-Algolia-API-Key' => Config::get('algoliaAPIKey'),
501
		];
502
 
503
		$lat = number_format($obs['lat'], 6, '.', '');
504
		$lon = number_format($obs['lon'], 6, '.', '');
505
 
506
		$ch = curl_init(Config::get('urlReverseGeocodingLatLng')."$lat,$lon");
507
		curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
508
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
509
		$reponse = curl_exec($ch);
510
		$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
511
		curl_close($ch);
512
 
513
		if ($reponse) {
514
			$reponse = json_decode($reponse, true);
515
			// die(var_dump($reponse));
516
			$infos = $reponse['hits'][0];
517
 
518
			$geo = [
519
				'lat' => (string) $obs['lat'],
520
				'lon' => (string) $obs['lon'],
521
				'country' => $infos['country']['fr'] ?? $infos['country']['default'],
522
				'city' => $infos['city']['default'][0] ?? null,
523
				'postcode' => $infos['postcode'][0] ?? null,
524
				'countryCode' => strtoupper($infos['country_code']),
525
			];
526
		}
527
 
528
		return $geo;
529
	}
530
 
3755 killian 531
	private function updateFromPN(array $pnObs, string $tbObsId) {
532
		if (!is_array($pnObs) || !is_string($tbObsId)) {
533
			// die(var_dump($pnObs, $tbObsId));
3761 killian 534
			throw new Exception("\nBad params types, give me an array and an integer plz\n");
3755 killian 535
		}
536
		// die(var_dump($pnObs));
537
 
3761 killian 538
		// log update date to cel_plantnet
539
		$sql = "UPDATE cel_plantnet SET date_maj = NOW() WHERE id_observation = $tbObsId"
540
				. ' -- ' . __FILE__ . ':' . __LINE__;
541
		$this->bdd->requeter($sql);
542
 
3755 killian 543
		// check for deleted
544
		if (isset($pnObs['deleted']) && (true === $pnObs['deleted'])) {
545
			// est-ce une obs issue de PN ?
546
			//// faut regarder le champ input_source == PlantNet
547
			$sql = "SELECT input_source FROM occurrence WHERE id = '$tbObsId'"
548
					. ' -- ' . __FILE__ . ':' . __LINE__;
549
			$res = $this->bdd->recupererTous($sql);
3761 killian 550
 
3755 killian 551
			if (isset($res[0]) && ('PlantNet' === $res[0]['input_source'])) {
552
				// oui ? alors supprimer obs !
3761 killian 553
				echo "Obs supprimée coté PN, suppression : ID PN {$obs['id']} ; ID TB {$res[0]['id_observation']}\n";
554
 
555
				$sql = "UPDATE photos SET occurrence_id = NULL WHERE occurrence_id = '$tbObsId'"
3755 killian 556
						. ' -- ' . __FILE__ . ':' . __LINE__;
557
				$this->bdd->requeter($sql);
3761 killian 558
 
559
				$sql = "DELETE FROM occurrence WHERE id = '$tbObsId'"
3755 killian 560
						. ' -- ' . __FILE__ . ':' . __LINE__;
561
				$this->bdd->requeter($sql);
562
			}
563
		}
564
 
565
		// check for deleted images
566
		foreach ($pnObs['images'] as $image) {
567
			if ($image['deleted']) {
568
				// no idea
569
				// y'a pas encore de lien tangible entre les images PN et les notres
570
			}
571
		}
572
 
573
		// @TODO : finir la comparaison des proposition entre celles de Tela et celles PN
574
		// // check for votes (notes et taxon)
575
		// $names = [];
576
		// foreach ($pnObs['computed']['votes'] as $vote) {
577
		// 	echo($vote['name']);
578
		// 	echo($vote['score']['pn']);
579
		// 	echo($vote['score']['total']);
580
 
581
		// 	$names[$vote['score']['pn']] = $vote['name'];
582
		// }
583
		// ksort($names, SORT_NUMERIC); // votes sorted by score asc
584
		// $pn_sorted_votes = array_reverse($names, true); // desc
585
 
586
		// // get existing votes
587
		// $sql = "SELECT id_commentaire, nom_sel, nom_sel_nn, nom_referentiel, nom_referentiel, utilisateur_courriel, proposition_initiale, proposition_retenue"
588
		// 		. "WHERE ce_observation = '$tbObsId' AND ce_commentaire_parent = 0"
589
		// 		. ' -- ' . __FILE__ . ':' . __LINE__;
590
		// $existing_votes = $this->bdd->recupererTous($sql);
591
 
592
		// // @TODO : compare votes
593
		// // insert_new_votes($pn_sorted_votes, $existing_votes);
594
	}
595
 
3761 killian 596
	private function updateOnePN($pnObsId, $pnProjectId) {
597
		if (!is_int($pnObsId) || !is_string($pnProjectId)) {
598
			die(var_dump($pnObsId, $pnProjectId));
599
			throw new Exception("\nBad params types, give me an integer and a string plz\n");
3755 killian 600
		}
3761 killian 601
		// get PN project list
602
		$list = [];
603
		$pnProjects = $this->getProjects(); // refresh projects list
604
		foreach ($pnProjects as $project) {
605
			$list[$project['id']] = $project['name'];
606
		}
3755 killian 607
 
3761 killian 608
		// if project not in list display list
609
		if (!array_key_exists($pnProjectId, $list)) {
610
			echo "Available projects:\n";
611
			foreach ($list as $projectId => $projectName) {
612
				echo " - $projectId ($projectName)\n";
613
			}
614
			throw new Exception("Project $pnProjectId does not exist\n");
615
		} else {
616
			$this->currentProject = [
617
				'id' => $pnProjectId,
618
				'name' => $list[$pnProjectId],
619
			];
620
		}
621
 
622
		// get PN obs
3755 killian 623
		$urlInfosObs = str_replace(
3761 killian 624
			['{token}', '{project}', '{id}'],
625
			[Config::get('tokenPlantnet'), $pnProjectId, $pnObsId],
3755 killian 626
			Config::get('urlPlantnetBase').Config::get('urlPlantnetObsById')
627
		);
628
		$ch = curl_init($urlInfosObs);
629
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
3761 killian 630
		$response = curl_exec($ch);
3755 killian 631
		$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
632
		curl_close($ch);
633
 
3761 killian 634
		if (!$response || 200 != $code) {
635
			throw new Exception("\nPlantnet api ($urlInfosObs) replied with code $code : $response\n");
3755 killian 636
		}
637
 
3761 killian 638
		// change last modification date to force update
639
		$obs = json_decode($response, true);
640
		$date = new DateTime();
641
		$date->setTimestamp(intdiv($obs['dateUpdated'], 1000) - 1);
642
		$date = $this->bdd->proteger($date->format('Y-m-d H:i:s'));
643
		$sql = "UPDATE cel_plantnet SET date_maj = $date WHERE id_plantnet = '$pnObsId'"
644
				. ' -- ' . __FILE__ . ':' . __LINE__;
645
		$this->bdd->requeter($sql);
646
 
647
		$this->updateObs([$obs]);
3755 killian 648
	}
649
 
650
	private function updateOneTB($id) {
651
		$corres = $this->findMatchingPartnerId($id, 'tb');
652
		echo "L'obs tela avec l'id $id a été trouvée chez plantnet avec l'id : " .$corres['pn'] . "\n";
653
		echo "La suite n'a pas été implémentée.\n";
654
	}
655
 
656
	/*
657
	 * Trouve les ids d'une obs chez les partenaires
658
	 * Il faut lancer updateMatchingPartnersIds pour obtenir une liste fraiche
659
	 *
660
	 * @param 	string $id			L'id de l'obs
661
	 * @param 	string $partner		Le partenaire correspondant à l'id de l'obs
662
	 *
663
	 * @return  mixed 				Le tableau de correspondances d'ids
664
	 *								False si l'id n'est pas trouvé
665
	 */
666
	private function findMatchingPartnerId($id, $partner) {
667
		if (!in_array($partner, $this->allowedPartners)) {
668
			throw new Exception("\nUnknown partner : $partner. Available Partners : " . implode(', ', $this->allowedPartners) . "\n");
669
		}
670
 
671
		// le json ressemble à ça :
672
		// {"tb":"2369732","pn":"1001020199"}, {"tb":"2369733","pn":"1001020176"}, ...
673
		$ids = json_decode(file_get_contents($this->correspondingIdsFilename), true);
674
		// extrait la colonne du partenaire recherché du tableau multidimensionnel fraichement décodé
675
		$partnerColumn = array_column($ids, $partner);
676
		// cherche l'index de l'id recherché
677
		$index = array_search($id, $partnerColumn);
678
 
679
		return $index ? $ids[$index] : false;
680
	}
681
 
682
	private function updateCorrespondingIdsTable() {
683
		// first update cache file if necessary
684
		if (!file_exists($this->correspondingIdsFilename) || time() > (filemtime($this->correspondingIdsFilename) + (24 * 3600))) {
685
			$this->updateMatchingPartnersIds();
686
		}
687
 
688
		$data = file_get_contents($this->correspondingIdsFilename);
689
		$data = json_decode($data, true);
690
 
691
		$now = new DateTime;
692
		$now = $this->bdd->proteger($now->format('Y-m-d H:i:s'));
693
 
694
		$sqlInsert = 'INSERT INTO cel_plantnet (id_observation, id_plantnet, date_maj)'
695
					. ' VALUES %s'
696
					. ' ON DUPLICATE KEY UPDATE id_observation=VALUES(id_observation), id_plantnet=VALUES(id_plantnet)'
697
					. ' -- ' . __FILE__ . ':' . __LINE__;
698
		$values = '';
699
 
3761 killian 700
		echo "Updating matching partners ids table\n";
3755 killian 701
		foreach ($data as $id => $corres) {
702
			$id_obs_tb = $this->bdd->proteger($corres['tb']);
703
			$id_obs_pn = $this->bdd->proteger($corres['pn']);
704
			// on insère les ids de correspondance obsPN obsTB dans la table cel_plantnet
705
			$values .= " ($id_obs_tb, $id_obs_pn, $now),";
706
 
707
			if (0 === $id % 100) {
708
				$values = substr($values, 0, -1); // retire la dernière virgule
709
				$sql = sprintf($sqlInsert, $values);
710
				// var_dump($sql);
711
				$this->bdd->requeter($sql);
712
				$values = '';
713
			}
714
		}
715
		// Faut envoyer les dernières données qui sont pas passées dans le modulo
716
		$values = substr($values, 0, -1); // retire la dernière virgule
717
		$sql = sprintf($sqlInsert, $values);
718
		// var_dump($sql);
719
		$this->bdd->requeter($sql);
720
 
3761 killian 721
		$count = count($data);
722
		echo "Success: #$count updated\n";
3755 killian 723
	}
724
 
725
	private function updateMatchingPartnersIds() {
726
		// curl -X GET --header 'Accept: application/json' 'https://bourbonnais.cirad.fr:8081/v1/edi/observations/partnerids?token=f0ca433febe320675c24ee2ddfab8b82f65d556b' > partnerids.txt
727
		$matchingPartnersIdsServiceUrl = str_replace(
728
			'{token}',
729
			Config::get('tokenPlantnet'),
730
			Config::get('urlPlantnetBase').Config::get('urlPlantnetMatchingPartner')
731
		);
732
		$ch = curl_init($matchingPartnersIdsServiceUrl);
733
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
734
 
735
		echo "Updating matching partners ids...\n";
736
		$reponse = curl_exec($ch);
737
		$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
738
		curl_close($ch);
739
 
740
 
741
		if (!$reponse || 200 != $code) {
742
			throw new Exception("\nError updating partners ids, http error code \"$code\" whith url \"$matchingPartnersIdsServiceUrl\"...\n");
743
		}
744
		echo "Response data from PN api gathered, know computing...\n";
745
 
746
		if (!file_put_contents($this->correspondingIdsFilename, $reponse)) {
747
			throw new Exception("\nError writing correspondingIdsFilename with path : " . $this->correspondingIdsFilename . "\n");
748
		}
749
		echo "Matching partners ids saved!";
750
	}
751
}