Subversion Repositories eFlore/Applications.cel

Rev

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

Rev Author Line No. Line
1636 raphael 1
<?php
2
 
3
/**
4
* @category  PHP
5
* @package   jrest
6
* @author    Raphaël Droz <raphael@tela-botania.org>
7
* @copyright 2013 Tela-Botanica
8
* @license   http://www.cecill.info/licences/Licence_CeCILL_V2-fr.txt Licence CECILL
9
* @license GPL v3 <http://www.gnu.org/licenses/gpl.txt>
10
*/
11
 
12
/**
13
 * Service d'import de données d'observation du CEL au format XLS
14
 */
15
 
1638 raphael 16
// sont define()'d commme n° de colonne tous les abbrevs retournés par ExportXLS::nom_d_ensemble_vers_liste_de_colonnes()
1636 raphael 17
// préfixés par C_  cf: detectionEntete()
18
 
19
set_include_path(get_include_path() . PATH_SEPARATOR . dirname(dirname(realpath(__FILE__))) . '/lib');
20
// TERM
21
error_reporting(-1);
22
ini_set('html_errors', 0);
23
ini_set('xdebug.cli_color', 2);
24
require_once('lib/PHPExcel/Classes/PHPExcel.php');
25
require_once('ExportXLS.php');
26
 
1640 raphael 27
 
28
date_default_timezone_set("Europe/Paris");
29
 
30
// nombre d'INSERT à cumuler par requête SQL
31
// (= nombre de lignes XLS à bufferiser)
32
define('NB_LIRE_LIGNE_SIMUL', 30);
33
 
34
// Numbers of days between January 1, 1900 and 1970 (including 19 leap years)
35
// see traiterDateObs()
36
define("MIN_DATES_DIFF", 25569);
37
 
38
 
1636 raphael 39
class MyReadFilter implements PHPExcel_Reader_IReadFilter {
1640 raphael 40
	// exclusion de colonnes
1638 raphael 41
	public $exclues = array();
1640 raphael 42
 
43
	// lecture par morceaux
44
    public $ligne_debut = 0;
45
    public $ligne_fin = 0;
46
 
1636 raphael 47
	public function __construct() {}
1640 raphael 48
	public function def_interval($debut, $nb) {
49
		$this->ligne_debut = $debut;
50
		$this->ligne_fin = $debut + $nb;
51
	}
1638 raphael 52
    public function readCell($colonne, $ligne, $worksheetName = '') {
53
		if(@$this->exclues[$colonne]) return false;
1640 raphael 54
		// si des n° de morceaux ont été initialisés, on filtre...
55
		if($this->ligne_debut && ($ligne < $this->ligne_debut || $ligne >= $this->ligne_fin)) return false;
1636 raphael 56
		return true;
57
    }
58
}
59
 
60
class ImportXLS extends Cel  {
61
 
62
	static $ordre_BDD = Array(
63
		"ce_utilisateur",
64
		"prenom_utilisateur",
65
		"nom_utilisateur",
66
		"courriel_utilisateur",
67
		"ordre",
68
		"nom_sel",
69
		"nom_sel_nn",
70
		"nom_ret",
71
		"nom_ret_nn",
72
		"nt",
73
		"famille",
74
		"nom_referentiel",
75
		"zone_geo",
76
		"ce_zone_geo",
77
		"date_observation",
78
		"lieudit",
79
		"station",
80
		"milieu",
81
		"commentaire",
82
		"transmission",
83
		"date_creation",
84
		"date_modification",
85
		"latitude",
86
		"longitude");
87
 
1640 raphael 88
	/*
89
	  Ces colonnes:
1642 raphael 90
	  - sont propres à l'ensemble des enregistrements uploadés
1640 raphael 91
	  - sont indépendantes du numéro de lignes
92
	  - n'ont pas de valeur par défaut dans la structure de la table
93
	  - nécessitent une initialisation dans le cadre de l'upload
94
	*/
95
	public $colonnes_statiques = Array(
96
		"ce_utilisateur" => NULL,
97
		"prenom_utilisateur" => NULL,
98
		"nom_utilisateur" => NULL,
99
		"courriel_utilisateur" => NULL,
100
 
1642 raphael 101
		// fixes (fonction SQL)
102
		// XXX future: mais pourraient varier dans le futur si la mise-à-jour
1640 raphael 103
		// d'observation est implémentée
1642 raphael 104
		"date_creation" => "now()",
105
		"date_modification" => "now()",
1640 raphael 106
	);
107
 
1642 raphael 108
	public $id_utilisateur = NULL;
109
	// erreurs d'import
110
	public $bilan = Array();
111
 
1636 raphael 112
	function ExportXLS($config) {
113
		parent::__construct($config);
114
	}
115
 
116
	function createElement($pairs) {
117
		if(!isset($pairs['utilisateur']) || trim($pairs['utilisateur']) == '') {
118
			echo '0'; exit;
119
		}
1640 raphael 120
		$id_utilisateur = intval($pairs['utilisateur']);
1642 raphael 121
		$this->id_utilisateur = $id_utilisateur; // pour traiterImage();
1640 raphael 122
 
1636 raphael 123
		if(!isset($_SESSION)) session_start();
1640 raphael 124
        $this->controleUtilisateur($id_utilisateur);
1636 raphael 125
 
1640 raphael 126
        $this->utilisateur = $this->getInfosComplementairesUtilisateur($id_utilisateur);
127
		$this->initialiser_colonnes_statiques($id_utilisateur);
1636 raphael 128
 
1640 raphael 129
 
1636 raphael 130
		$infos_fichier = array_pop($_FILES);
131
 
132
		/*$objPHPExcel = PHPExcel_IOFactory::load($infos_fichier['tmp_name']);
1638 raphael 133
		  $donnees = $objPHPExcel->getActiveSheet()->toArray(NULL,FALSE,FALSE,TRUE);*/
1636 raphael 134
 
135
		/*$objReader = PHPExcel_IOFactory::createReader("Excel5");
136
		$objReader->setReadDataOnly(true);
137
		$objPHPExcel = $objReader->load($infos_fichier['tmp_name']);*/
138
 
1638 raphael 139
		//var_dump($donnees);
1636 raphael 140
 
1642 raphael 141
		// renomme le fichier pour lui ajouter son extension initiale, ce qui
142
		// permet (une sorte) d'autodétection du format.
143
		$fichier = $infos_fichier['tmp_name'];
144
		$extension = pathinfo($infos_fichier['name'], PATHINFO_EXTENSION);
145
		if( (strlen($extension) == 3 || strlen($extension) == 4) &&
146
			(rename($fichier, $fichier . '.' . $extension))) {
147
			$fichier = $fichier . '.' . $extension;
148
		}
149
 
150
		$objReader = PHPExcel_IOFactory::createReaderForFile($fichier);
1636 raphael 151
		$objReader->setReadDataOnly(true);
1640 raphael 152
 
1642 raphael 153
		if(is_a($objReader, 'PHPExcel_Reader_CSV')) {
154
			$objReader->setDelimiter(',')
155
				->setEnclosure('"')
156
				->setLineEnding("\n")
157
				->setSheetIndex(0);
158
		}
159
 
1640 raphael 160
		// on ne conserve que l'en-tête
161
		$filtre = new MyReadFilter();
162
		$filtre->def_interval(1, 2);
163
		$objReader->setReadFilter($filtre);
164
 
1642 raphael 165
		$objPHPExcel = $objReader->load($fichier);
166
		$obj_infos = $objReader->listWorksheetInfo($fichier);
1640 raphael 167
		// XXX: indépendant du readFilter ?
168
		$nb_lignes = $obj_infos[0]['totalRows'];
1636 raphael 169
 
1640 raphael 170
		$donnees = $objPHPExcel->getActiveSheet()->toArray(NULL, FALSE, FALSE, TRUE);
171
		$filtre->exclues = self::detectionEntete($donnees[1]);
1636 raphael 172
 
1640 raphael 173
		$obs_ajouts = 0;
174
		$obs_maj = 0;
175
		$dernier_ordre = $this->requeter("SELECT MAX(ordre) AS ordre FROM cel_obs WHERE ce_utilisateur = $id_utilisateur");
176
		$dernier_ordre = intval($dernier_ordre[0]['ordre']) + 1;
177
		if(! $dernier_ordre) $dernier_ordre = 0;
178
 
1642 raphael 179
		// on catch to les trigger_error(E_USER_NOTICE);
180
		set_error_handler(array($this, 'erreurs_stock'), E_USER_NOTICE);
181
 
1640 raphael 182
		// lecture par morceaux (chunks), NB_LIRE_LIGNE_SIMUL lignes à fois
183
		// pour aboutir des requêtes SQL d'insert groupés.
184
		for($ligne = 2; $ligne < $nb_lignes + NB_LIRE_LIGNE_SIMUL; $ligne += NB_LIRE_LIGNE_SIMUL) {
185
			$filtre->def_interval($ligne, NB_LIRE_LIGNE_SIMUL);
186
			$objReader->setReadFilter($filtre);
187
 
188
			/* recharge avec $filtre actif (filtre sur lignes colonnes):
189
			   - exclue les colonnes inutiles/inutilisables)
190
			   - ne selectionne que les lignes dans le range [$ligne - $ligne + NB_LIRE_LIGNE_SIMUL] */
1642 raphael 191
			$objPHPExcel = $objReader->load($fichier);
1640 raphael 192
			$donnees = $objPHPExcel->getActiveSheet()->toArray(NULL, FALSE, FALSE, TRUE);
193
 
194
			// ici on appel la fonction qui fera effectivement l'insertion multiple
195
			// à partir des (au plus) NB_LIRE_LIGNE_SIMUL lignes
196
 
197
			// TODO: passer $this, ne sert que pour appeler des méthodes public qui pourraient être statiques
198
			// notamment dans RechercheInfosTaxonBeta.php
1642 raphael 199
			list($enregistrements, $images) =
200
				self::chargerLignes($this, $donnees, $this->colonnes_statiques, $dernier_ordre);
201
			if(! $enregistrements) break;
202
 
203
			$dernier_autoinc = self::stockerEnregistrements($this, $enregistrements);
204
			$obs_ajouts += count($enregistrements);
205
			// $obs_maj += count($enregistrements_a_MAJ);
206
			self::stockerImages($this, $enregistrements, $images, $dernier_autoinc);
1640 raphael 207
		}
1642 raphael 208
 
209
		restore_error_handler();
210
 
211
		if($this->bilan) echo implode("\n", $this->bilan) . "\n";
212
		// fin: renvoi summary
213
		die("$obs_ajouts observations ajoutées");
1636 raphael 214
	}
215
 
216
	static function detectionEntete($entete) {
217
		$colonnes_reconnues = Array();
1638 raphael 218
		$cols = ExportXLS::nom_d_ensemble_vers_liste_de_colonnes('standard');
1636 raphael 219
		foreach($entete as $k => $v) {
220
			$entete_simple = iconv('UTF-8', 'ASCII//TRANSLIT', strtolower(trim($v)));
221
			foreach($cols as $col) {
222
				$entete_officiel_simple = iconv('UTF-8', 'ASCII//TRANSLIT', strtolower(trim($col['nom'])));
1638 raphael 223
				$entete_officiel_abbrev = $col['abbrev'];
224
				if($entete_simple == $entete_officiel_simple || $entete_simple == $entete_officiel_abbrev) {
1642 raphael 225
					//debug echo "define C_" . strtoupper($entete_officiel_abbrev) . ", $k ($v)\n";
1638 raphael 226
					define("C_" . strtoupper($entete_officiel_abbrev), $k);
1636 raphael 227
					$colonnes_reconnues[$k] = 1;
228
					break;
229
				}
230
			}
231
		}
232
 
1640 raphael 233
		// prépare le filtre de PHPExcel qui évitera le traitement de toutes les colonnes superflues
234
 
235
		// eg: diff ( Array( H => Commune, I => rien ) , Array( H => 1, K => 1 )
236
		// ==> Array( I => rien )
1636 raphael 237
		$colonnesID_non_reconnues = array_diff_key($entete, $colonnes_reconnues);
238
 
1640 raphael 239
		// des colonnes de ExportXLS::nom_d_ensemble_vers_liste_de_colonnes()
240
		// ne retient que celles marquées "importables"
1636 raphael 241
		$colonnes_automatiques = array_filter($cols, function($v) {	return !$v['importable']; });
1640 raphael 242
 
1636 raphael 243
		// ne conserve que le nom long pour matcher avec la ligne XLS d'entête
244
		array_walk($colonnes_automatiques, function(&$v) {	$v = $v['nom']; });
1640 raphael 245
 
246
		// intersect ( Array ( N => Milieu, S => Ordre ), Array ( ordre => Ordre, phenologie => Phénologie ) )
247
		// ==> Array ( S => Ordre, AA => Phénologie )
1636 raphael 248
		$colonnesID_a_exclure = array_intersect($entete, $colonnes_automatiques);
249
 
1640 raphael 250
		// TODO: pourquoi ne pas comparer avec les abbrevs aussi ?
251
		// merge ( Array( I => rien ) , Array ( S => Ordre, AA => Phénologie ) )
252
		// ==> Array ( I => rien, AA => Phénologie )
1636 raphael 253
		return array_merge($colonnesID_non_reconnues, $colonnesID_a_exclure);
254
	}
255
 
1640 raphael 256
	/*
257
	 * charge un groupe de lignes
258
	 */
1642 raphael 259
	static function chargerLignes($cel, $lignes, $colonnes_statiques, &$dernier_ordre) {
1640 raphael 260
		$enregistrement = NULL;
261
		$enregistrements = Array();
1642 raphael 262
		$toutes_images = Array();
1640 raphael 263
 
264
		foreach($lignes as $ligne) {
1642 raphael 265
			//$ligne = array_filter($ligne, function($cell) { return !is_null($cell); });
266
			//if(!$ligne) continue;
267
			// on a besoin des NULL pour éviter des notice d'index indéfini
268
			if(! array_filter($ligne, function($cell) { return !is_null($cell); })) continue;
1640 raphael 269
 
1642 raphael 270
			if( ($enregistrement = self::chargerLigne($ligne, $dernier_ordre, $cel)) ) {
271
				$enregistrements[] = array_merge($colonnes_statiques, $enregistrement);
1640 raphael 272
 
273
				if(isset($enregistrement['_images'])) {
274
					$pos = count($enregistrements) - 1;
1642 raphael 275
					$last = &$enregistrements[$pos];
1640 raphael 276
					// ne dépend pas de cel_obs, et seront insérées *après* les enregistrements
277
					// mais nous ne voulons pas nous priver de faire des INSERT multiples pour autant
1642 raphael 278
					$toutes_images[] = Array("images" => $last['_images'],
279
											 "obs_pos" => $pos);
1640 raphael 280
					// ce champ n'a pas a faire partie de l'insertion dans cel_obs,
281
					// mais est utile pour cel_obs_images
1642 raphael 282
					unset($last['_images']);
1640 raphael 283
				}
284
 
285
				$dernier_ordre++;
286
			}
1636 raphael 287
		}
1640 raphael 288
 
1642 raphael 289
		// XXX future: return Array($enregistrements_a_inserer, $enregistrements_a_MAJ, $toutes_images);
290
		return Array($enregistrements, $toutes_images);
291
	}
1640 raphael 292
 
1642 raphael 293
 
294
	static function stockerEnregistrements($cel, $enregistrements) {
1640 raphael 295
		$req = '';
296
 
1642 raphael 297
		foreach($enregistrements as $enregistrement) {
298
			$enregistrement = self::sortArrayByArray($enregistrement, self::$ordre_BDD);
299
			array_walk($enregistrement,
300
					   function(&$item, $k, $obj) { $item = is_null($item) ? "NULL" : $item; },
301
					   $cel);
302
			$req .= implode(', ', $enregistrement) . "\n";
303
		}
304
		echo "$req\n";
305
		// TODO: insert
1640 raphael 306
 
307
		// $cel->executer($req);
308
		// transactionnel + auto-inc
1642 raphael 309
		return $cel->bdd->lastInsertId();
310
	}
311
 
312
 
313
	static function stockerImages($cel, $enregistrements, $toutes_images, $lastid) {
314
		if(! $lastid) $lastid = rand();
315
 
316
		$images_insert =
317
			'INSERT INTO cel_obs_images (id_image, id_observation) VALUES'
318
			.' %s '
319
			.'ON DUPLICATE KEY UPDATE id_image = id_image';
320
		$images_obs_assoc = Array();
321
 
322
		foreach($toutes_images as $images_pour_obs) {
323
			$obs = $enregistrements[$images_pour_obs["obs_pos"]];
1640 raphael 324
			$id_obs = $lastid // dernier autoinc inséré
325
				- count($enregistrements) - 1 // correspondrait au premier autoinc
1642 raphael 326
				+ $images_pour_obs["obs_pos"]; // ordre d'insertion = ordre dans le tableau $enregistrements
327
			foreach($images_pour_obs['images'] as $image) {
328
				$images_obs_assoc[] = sprintf('(%d,%d)',
329
											  $image['id_image'], // intval() useless
330
											  $id_obs); // intval() useless
331
			}
1640 raphael 332
		}
1642 raphael 333
 
334
		if($images_obs_assoc) {
335
			$requete = sprintf($images_insert, implode(', ', $images_obs_assoc));
336
			echo "$requete\n";
337
		}
1636 raphael 338
	}
339
 
340
 
1642 raphael 341
	static function chargerLigne($ligne, $dernier_ordre, $cel) {
1636 raphael 342
		// en premier car le résultat est utile pour
343
		// traiter longitude et latitude (traiterLonLat())
1640 raphael 344
		$referentiel = self::identReferentiel($ligne[C_NOM_REFERENTIEL]);
1636 raphael 345
 
1640 raphael 346
		// $espece est rempli de plusieurs informations
1642 raphael 347
		$espece = Array(C_NOM_SEL => NULL, C_NOM_SEL_NN => NULL, C_NOM_RET => NULL,
348
						C_NOM_RET_NN => NULL, C_NT => NULL, C_FAMILLE => NULL);
1640 raphael 349
		self::traiterEspece($ligne, $espece, $cel);
1636 raphael 350
 
1642 raphael 351
		// $localisation est rempli à partir de plusieurs champs: C_ZONE_GEO et C_CE_ZONE_GEO
352
		$localisation = Array(C_ZONE_GEO => NULL, C_CE_ZONE_GEO => NULL);
353
		self::traiterLocalisation($ligne, $localisation, $cel);
1636 raphael 354
 
1642 raphael 355
		// Dans ce tableau, seules devraient apparaître les données variable pour chaque ligne.
356
		// Dans ce tableau, l'ordre des clefs n'importe pas (cf: self::sortArrayByArray())
357
		$enregistrement = Array(
358
			"ordre" => $dernier_ordre,
1640 raphael 359
 
1642 raphael 360
			// $this->quoteNonNull() est déjà appliquée dans traiterEspece()
361
			"nom_sel" => $espece[C_NOM_SEL],
362
			"nom_sel_nn" => $espece[C_NOM_SEL_NN],
363
			"nom_ret" => $espece[C_NOM_RET],
364
			"nom_ret_nn" => $espece[C_NOM_RET_NN],
365
			"nt" => $espece[C_NT],
366
			"famille" => $espece[C_FAMILLE],
1640 raphael 367
 
1642 raphael 368
			"nom_referentiel" => $cel->quoteNonNull($referentiel),
1640 raphael 369
 
1642 raphael 370
			// $this->quoteNonNull() est déjà appliquée dans traiterLocalisation()
371
			"zone_geo" => $localisation[C_ZONE_GEO],
372
			"ce_zone_geo" => $localisation[C_CE_ZONE_GEO],
1640 raphael 373
 
1642 raphael 374
			// $ligne: uniquement pour les infos en cas de gestion d'erreurs (date incompréhensible)
375
			"date_observation" => $cel->quoteNonNull(self::traiterDateObs($ligne[C_DATE_OBSERVATION], $ligne)),
1640 raphael 376
 
1642 raphael 377
			"lieudit" => $cel->quoteNonNull(trim($ligne[C_LIEUDIT])),
378
			"station" => $cel->quoteNonNull(trim($ligne[C_STATION])),
379
			"milieu" => $cel->quoteNonNull(trim($ligne[C_MILIEU])),
380
			"commentaire" => $cel->quoteNonNull(trim($ligne[C_COMMENTAIRE])), // TODO: foreign-key
381
 
382
 
383
			"transmission" => in_array(strtolower(trim($ligne[C_TRANSMISSION])), array(1, 'oui')) ? 1 : 0,
384
 
385
			// $ligne: uniquement pour les infos en cas de gestion d'erreurs (lon/lat incompréhensible)
386
			"latitude" => self::traiterLonLat(NULL, $ligne[C_LATITUDE], $referentiel, $ligne),
387
			"longitude" => self::traiterLonLat($ligne[C_LONGITUDE], NULL, $referentiel, $ligne),
388
		);
389
 
390
		// passage de $enregistrement par référence, ainsi ['_images'] n'est défini
391
		// que si des résultats sont trouvés
392
		// "@" car PHPExcel supprime les colonnes null sur toute la feuille (ou tout le chunk)
393
		if(@$ligne[C_IMAGES]) self::traiterImage($ligne[C_IMAGES], $cel, $enregistrement);
394
 
395
		return $enregistrement;
1636 raphael 396
	}
397
 
1642 raphael 398
	static function traiterImage($str, $cel, &$enregistrement) {
399
		$liste_images = array_filter(explode("/", $str));
400
		array_walk($liste_images,
401
				   function($item, $key, $obj) { $item = $obj->quoteNonNull(trim($item)); },
402
				   $cel);
403
		$requete = sprintf(
404
			"SELECT id_image, nom_original FROM cel_images WHERE ce_utilisateur = %d AND nom_original IN (\"%s\")",
405
			$cel->id_utilisateur,
406
			implode('","', $liste_images));
1640 raphael 407
 
1642 raphael 408
		$resultat = $cel->requeter($requete);
1640 raphael 409
 
1642 raphael 410
		if($resultat) $enregistrement['_images'] = $resultat;
411
	}
412
 
413
 
1640 raphael 414
	/* FONCTIONS de TRANSFORMATION de VALEUR DE CELLULE */
415
 
416
	// TODO: PHP 5.3, utiliser date_parse_from_format()
417
	// TODO: parser les heures (cf product-owner)
418
	// TODO: passer par le timestamp pour s'assurer de la validité
1642 raphael 419
	static function traiterDateObs($date, $ligne) {
1640 raphael 420
		// TODO: see https://github.com/PHPOffice/PHPExcel/issues/208
421
		if(is_double($date)) {
422
			if($date > 0)
423
				return PHPExcel_Style_NumberFormat::toFormattedString($date, PHPExcel_Style_NumberFormat::FORMAT_DATE_YYYYMMDD2) . " 00:00:00";
1642 raphael 424
			trigger_error("ligne \"{$ligne[C_NOM_SEL]}\": " .
425
						  "Attention: date antérieure à 1970 et format de cellule \"DATE\" utilisés ensemble",
426
						  E_USER_NOTICE);
427
 
428
			// throw new Exception("erreur: date antérieure à 1970 et format de cellule \"DATE\" utilisés ensemble");
1640 raphael 429
 
430
			// attention, UNIX timestamp, car Excel les décompte depuis 1900
431
			// cf http://fczaja.blogspot.fr/2011/06/convert-excel-date-into-timestamp.html
432
			// $timestamp = ($date - MIN_DATES_DIFF) * 60 * 60 * 24 - time(); // NON
433
 
434
			// $timestamp = PHPExcel_Calculation::getInstance()->calculateFormula("=" . $date . "-DATE(1970,1,1)*60*60*24"); // NON
435
 
436
			// echo strftime("%Y/%m/%d 00:00:00", $timestamp); // NON
437
		}
438
		else {
439
			$timestamp = strtotime($date);
1642 raphael 440
			if(! $timestamp) {
441
				if($date) trigger_error("ligne \"{$ligne[C_NOM_SEL]}\": Attention: date erronée ($date)", E_USER_NOTICE);
442
				return NULL;
443
			}
1640 raphael 444
			return strftime("%Y-%m-%d 00:00:00", strtotime($date));
445
		}
1636 raphael 446
	}
447
 
448
	static function identReferentiel($referentiel) {
1640 raphael 449
		// SELECT DISTINCT nom_referentiel, COUNT(id_observation) AS count FROM cel_obs GROUP BY nom_referentiel ORDER BY count DESC;
450
		if(strpos(strtolower($referentiel), 'bdtfx') !== FALSE) return 'bdtfx:v1.01';
451
		if(strpos(strtolower($referentiel), 'bdtxa') !== FALSE) return 'bdtxa:v1.00';
452
		if(strpos(strtolower($referentiel), 'bdnff') !== FALSE) return 'bdnff:4.02';
453
		if(strpos(strtolower($referentiel), 'isfan') !== FALSE) return 'isfan:v1.00';
1642 raphael 454
 
455
		if($referentiel) {
456
			trigger_error("ligne \"{$ligne[C_NOM_SEL]}\": Attention: référentiel inconnu", E_USER_NOTICE);
457
		}
1640 raphael 458
		return NULL;
459
		/* TODO: cf story,
1642 raphael 460
		   En cas de NULL faire une seconde passe de détection à partir du nom saisi
461
		   + accepter les n° de version */
1636 raphael 462
	}
463
 
1642 raphael 464
	static function traiterLonLat($lon = NULL, $lat = NULL, $referentiel = 'bdtfx:v1.01', $ligne) {
465
		// en CSV ces valeurs sont des string, avec séparateur en français (","; cf défauts dans ExportXLS)
466
		if($lon && is_string($lon)) $lon = str_replace(',', '.', $lon);
467
		if($lat && is_string($lat)) $lat = str_replace(',', '.', $lat);
1640 raphael 468
 
1642 raphael 469
		// sprintf applique une précision à 5 décimale (comme le ferait MySQL)
470
		// tout en uniformisant le format de séparateur des décimales (le ".")
471
		if($lon && is_numeric($lon) && $lon >= -180 && $lon <= 180) return sprintf('%.5F', $lon);
472
		if($lat && is_numeric($lat) && $lat >= -90 && $lat <= 90) return sprintf('%.5F', $lat);
473
 
474
		if($lon || $lat) {
475
			trigger_error("ligne \"{$ligne[C_NOM_SEL]}\": " .
476
						  "Attention: longitude ou latitude erronée",
477
						  E_USER_NOTICE);
478
		}
479
		return NULL;
480
 
481
		/* limite france métropole si bdtfx ? ou bdtxa ? ...
482
		   NON!
483
		   Un taxon d'un référentiel donné peut être théoriquement observé n'importe où sur le globe.
484
		   Il n'y a pas lieu d'effectuer des restriction ici.
485
		   Cependant des erreurs fréquentes (0,0 ou lon/lat inversées) peuvent être détectés ici.
486
		   TODO */
1640 raphael 487
		$bbox = self::getReferentielBBox($referentiel);
488
		if(!$bbox) return NULL;
489
 
490
		if($lon) {
491
			if($lon < $bbox['EST'] && $lon > $bbox['OUEST']) return is_numeric($lon) ? $lon : NULL;
492
			else return NULL;
493
		}
494
		if($lat) {
495
			if($lat < $bbox['NORD'] && $lat > $bbox['SUD']) return is_numeric($lat) ? $lat : NULL;
496
			return NULL;
497
		}
1636 raphael 498
	}
499
 
1640 raphael 500
 
501
	static function traiterEspece($ligne, Array &$espece, $cel) {
1642 raphael 502
		if(!$ligne[C_NOM_SEL]) return;
503
 
1640 raphael 504
		$taxon_info_webservice = new RechercheInfosTaxonBeta($cel->config);
505
 
506
		$ascii = iconv('UTF-8', 'ASCII//TRANSLIT', $ligne[C_NOM_SEL]);
1642 raphael 507
		// FALSE = recherche étendue (LIKE x%)
1640 raphael 508
		$resultat_recherche_espece = $taxon_info_webservice->rechercherInfosSurTexteCodeOuNumTax($ligne[C_NOM_SEL]);
509
 
510
		// on supprime les noms retenus et renvoi tel quel
511
		// on réutilise les define pour les noms d'indexes, tant qu'à faire
512
		if (empty($resultat_recherche_espece['en_id_nom'])) {
1642 raphael 513
			$espece[C_NOM_SEL] = $cel->quoteNonNull(trim($ligne[C_NOM_SEL]));
1640 raphael 514
 
1642 raphael 515
			// le reste reste à NULL
1640 raphael 516
			// TODO: si empty(C_NOM_SEL) et !empty(C_NOM_SEL_NN) : recherche info à partir de C_NOM_SEL_NN
1642 raphael 517
			$espece[C_NOM_SEL_NN] = $ligne[C_NOM_SEL_NN];
1640 raphael 518
			$espece[C_NOM_RET] = $ligne[C_NOM_RET];
519
			$espece[C_NOM_RET_NN] = $ligne[C_NOM_RET_NN];
520
			$espece[C_NT] = $ligne[C_NT];
521
			$espece[C_FAMILLE] = $ligne[C_FAMILLE];
522
 
523
			return;
524
		}
525
 
526
		// succès de la détection, récupération des infos
1642 raphael 527
		$espece[C_NOM_SEL] = $cel->quoteNonNull($resultat_recherche_espece['nom_sel']);
528
		$espece[C_NOM_SEL_NN] = $cel->quoteNonNull($resultat_recherche_espece['en_id_nom']);
1640 raphael 529
 
530
		$complement = $taxon_info_webservice->rechercherInformationsComplementairesSurNumNom($resultat_recherche_espece['en_id_nom']);
1642 raphael 531
		$espece[C_NOM_RET] = $cel->quoteNonNull($complement['Nom_Retenu']);
532
		$espece[C_NOM_RET_NN] = $cel->quoteNonNull($complement['Num_Nom_Retenu']);
533
		$espece[C_NT] = $cel->quoteNonNull($complement['Num_Taxon']);
534
		$espece[C_FAMILLE] = $cel->quoteNonNull($complement['Famille']);
1640 raphael 535
	}
536
 
537
 
1642 raphael 538
	static function traiterLocalisation($ligne, Array &$localisation, $cel) {
539
	    $identifiant_commune = trim($ligne[C_ZONE_GEO]);
540
		if(!$identifiant_commune) {
541
			$departement = trim($ligne[C_CE_ZONE_GEO]);
542
			goto testdepartement;
543
		}
544
 
545
 
546
		$select = "SELECT DISTINCT nom, code  FROM cel_zones_geo";
547
 
548
		if (preg_match('/(.*) \((\d+)\)/', $identifiant_commune, $elements)) {
549
			// commune + departement : montpellier (34)
550
			$nom_commune=$elements[1];
551
			$code_commune=$elements[2];
552
	 	    $requete = sprintf("%s WHERE nom = %s AND code LIKE %s",
553
							   $select, $cel->quoteNonNull($nom_commune), $cel->quoteNonNull($code_commune.'%'));
554
		}
555
		elseif (preg_match('/^(\d+|(2[ab]\d+))$/i', $identifiant_commune, $elements)) {
556
			// Code insee seul
557
			$code_insee_commune=$elements[1];
558
	 	    $requete = sprintf("%s WHERE code = %s", $select, $cel->quoteNonNull($code_insee_commune));
559
		}
560
		else {
561
			// Commune seule (le departement sera recupere dans la colonne departement si elle est presente)
562
			// on prend le risque ici de retourner une mauvaise Commune
563
			$nom_commune = str_replace(" ", "%", iconv('UTF-8', 'ASCII//TRANSLIT', $identifiant_commune));
564
	 	    $requete = sprintf("%s WHERE nom LIKE %s", $select, $cel->quoteNonNull($nom_commune.'%'));
565
		}
566
 
567
		$resultat_commune = $cel->requeter($requete);
568
		// TODO: levenstein sort ?
569
 
570
		// cas de la commune introuvable dans le référentiel
571
		// réinitialisation aux valeurs du fichier XLS
572
		if(! $resultat_commune) {
573
			$localisation[C_ZONE_GEO] = trim($ligne[C_ZONE_GEO]);
574
			$localisation[C_CE_ZONE_GEO] = trim($ligne[C_CE_ZONE_GEO]);
575
		} else {
576
			$localisation[C_ZONE_GEO] = $resultat_commune[0]['nom'];
577
			$localisation[C_CE_ZONE_GEO] = $resultat_commune[0]['code'];
578
		}
579
 
580
		$departement = &$localisation[C_CE_ZONE_GEO];
581
 
582
	testdepartement:
583
		if(strpos($departement, "INSEE-C:", 0) === 0) goto protectloc;
584
 
585
		if(!is_numeric($departement)) goto protectloc; // TODO ?
586
		if(strlen($departement) == 4) $departement = "INSEE-C:0" . $departement;
587
		if(strlen($departement) == 5) $departement = "INSEE-C:" . $departement;
1640 raphael 588
		// if(strlen($departement) <= 9) return "INSEE-C:0" . $departement; // ? ... TODO
1642 raphael 589
 
590
		$departement = trim($departement); // TODO
591
 
592
	protectloc:
593
		$localisation[C_ZONE_GEO] = $cel->quoteNonNull($localisation[C_ZONE_GEO]);
594
		$localisation[C_CE_ZONE_GEO] = $cel->quoteNonNull($localisation[C_CE_ZONE_GEO]);
1640 raphael 595
	}
596
 
597
 
1642 raphael 598
 
1640 raphael 599
	/* HELPERS */
600
 
1636 raphael 601
	// http://stackoverflow.com/questions/348410/sort-an-array-based-on-another-array
1640 raphael 602
	static function sortArrayByArray($array, $orderArray) {
1636 raphael 603
		$ordered = array();
604
		foreach($orderArray as $key) {
605
			if(array_key_exists($key, $array)) {
606
				$ordered[$key] = $array[$key];
607
				unset($array[$key]);
608
			}
609
		}
610
		return $ordered + $array;
611
	}
1640 raphael 612
 
613
	// retourne une BBox [N,S,E,O) pour un référentiel donné
614
	static function getReferentielBBox($referentiel) {
615
		if($referentiel == 'bdtfx:v1.01') return Array(
616
			'NORD' => 51.2, // Dunkerque
617
			'SUD' => 41.3, // Bonifacio
618
			'EST' => 9.7, // Corse
619
			'OUEST' => -5.2); // Ouessan
620
		return FALSE;
621
	}
622
 
623
 
1642 raphael 624
	public function initialiser_colonnes_statiques() {
1640 raphael 625
		$this->colonnes_statiques = array_merge($this->colonnes_statiques,
626
												Array(
1642 raphael 627
													"ce_utilisateur" => $this->id_utilisateur,
628
													"prenom_utilisateur" => $this->quoteNonNull($this->utilisateur['prenom']),
629
													"nom_utilisateur" => $this->quoteNonNull($this->utilisateur['nom']),
630
													"courriel_utilisateur" => $this->quoteNonNull($this->utilisateur['courriel']),
1640 raphael 631
												));
632
 
633
	}
1642 raphael 634
 
635
	// équivalent à CEL->Bdd->proteger() (qui wrap PDO::quote),
636
	// sans transformer NULL en ""
637
	private function quoteNonNull($chaine) {
638
		if(is_null($chaine)) return "NULL";
639
		if(!is_string($chaine)) die("erreur __FILE__, __LINE__");
640
		return $this->bdd->quote($chaine);
641
	}
642
 
643
	public function erreurs_stock($errno, $errstr) {
644
		$this->bilan[] = $errstr;
645
	}
1636 raphael 646
}