Subversion Repositories eFlore/Applications.cel

Rev

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