Subversion Repositories eFlore/Applications.cel

Rev

Rev 2394 | Rev 2458 | Go to most recent revision | Show entire file | Ignore whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 2394 Rev 2447
Line 1... Line 1...
1
<?php
1
<?php
2
/**
-
 
3
 * @category  PHP
-
 
4
 * @package   jrest
-
 
5
 * @author    Raphaël Droz <raphael@tela-botania.org>
-
 
6
 * @copyright 2013 Tela-Botanica
2
// declare(encoding='UTF-8');
7
 * @license   http://www.cecill.info/licences/Licence_CeCILL_V2-fr.txt Licence CECILL
-
 
8
 * @license GPL v3 <http://www.gnu.org/licenses/gpl.txt>
-
 
9
 */
-
 
10
 
-
 
11
/**
3
/**
12
 * Service d'import de données d'observation du CEL au format XLS
4
 * Service d'import de données d'observation du CEL au format XLS
13
 *
5
 *
14
 * Sont define()'d commme n° de colonne tous les abbrevs retournés par
6
 * Sont define()'d commme n° de colonne tous les abbrevs retournés par
15
 * FormateurGroupeColonne::nomEnsembleVersListeColonnes() préfixés par C_  cf: detectionEntete()
7
 * FormateurGroupeColonne::nomEnsembleVersListeColonnes() préfixés par C_  cf: detectionEntete()
Line 17... Line 9...
17
 * Exemple d'un test:
9
 * Exemple d'un test:
18
 * $ GET "/jrest/ExportXLS/22506?format=csv&range=*&limite=13" \
10
 * $ GET "/jrest/ExportXLS/22506?format=csv&range=*&limite=13" \
19
 *   | curl -F "upload=@-" -F utilisateur=22506 "/jrest/ImportXLS"
11
 *   | curl -F "upload=@-" -F utilisateur=22506 "/jrest/ImportXLS"
20
 * # 13 observations importées
12
 * # 13 observations importées
21
 * + cf MySQL general_log = 1
13
 * + cf MySQL general_log = 1
22
 *  
14
 *
-
 
15
 * @category   DEL
-
 
16
 * @package    Services
-
 
17
 * @subpackage Communes
-
 
18
 * @version    0.1
-
 
19
 * @author     Mathias CHOUET <mathias@tela-botanica.org>
-
 
20
 * @author     Raphaël DROZ <raphael@tela-botanica.org>
-
 
21
 * @author     Jean-Pascal MILCENT <jpm@tela-botanica.org>
-
 
22
 * @author     Aurelien PERONNET <aurelien@tela-botanica.org>
-
 
23
 * @license    GPL v3 <http://www.gnu.org/licenses/gpl.txt>
-
 
24
 * @license    CECILL v2 <http://www.cecill.info/licences/Licence_CeCILL_V2-en.txt>
-
 
25
 * @copyright  1999-2014 Tela Botanica <accueil@tela-botanica.org>
23
 **/
26
 */
24
 
-
 
25
set_include_path(get_include_path() . PATH_SEPARATOR . dirname(dirname(realpath(__FILE__))) . '/lib');
27
set_include_path(get_include_path() . PATH_SEPARATOR . dirname(dirname(realpath(__FILE__))) . '/lib');
26
// TERM
28
// TERM
27
error_reporting(-1);
29
error_reporting(-1);
28
ini_set('html_errors', 0);
30
ini_set('html_errors', 0);
29
ini_set('xdebug.cli_color', 2);
31
ini_set('xdebug.cli_color', 2);
30
require_once('lib/PHPExcel/Classes/PHPExcel.php');
32
require_once('lib/PHPExcel/Classes/PHPExcel.php');
31
require_once('FormateurGroupeColonne.php');
33
require_once('FormateurGroupeColonne.php');
Line 32... Line -...
32
 
-
 
33
 
34
 
Line 34... Line 35...
34
date_default_timezone_set("Europe/Paris");
35
date_default_timezone_set('Europe/Paris');
35
 
36
 
36
// nombre d'INSERT à cumuler par requête SQL
37
// nombre d'INSERT à cumuler par requête SQL
37
// (= nombre de lignes XLS à bufferiser)
38
// (= nombre de lignes XLS à bufferiser)
Line 43... Line 44...
43
define('QUICK_CSV_IMPORT', TRUE);
44
define('QUICK_CSV_IMPORT', TRUE);
Line 44... Line 45...
44
 
45
 
45
// Numbers of days between January 1, 1900 and 1970 (including 19 leap years)
46
// Numbers of days between January 1, 1900 and 1970 (including 19 leap years)
46
// see traiterDateObs()
47
// see traiterDateObs()
-
 
48
// define("MIN_DATES_DIFF", 25569);
-
 
49
class MyReadFilter implements PHPExcel_Reader_IReadFilter {
-
 
50
	// exclusion de colonnes
Line -... Line 51...
-
 
51
	public $exclues = array();
-
 
52
 
-
 
53
	// lecture par morceaux
Line 47... Line -...
47
// define("MIN_DATES_DIFF", 25569);
-
 
48
 
-
 
49
 
54
	public $ligne_debut = 0;
Line 50... Line -...
50
class MyReadFilter implements PHPExcel_Reader_IReadFilter {
-
 
51
    // exclusion de colonnes
55
	public $ligne_fin = 0;
52
    public $exclues = array();
-
 
53
 
-
 
54
    // lecture par morceaux
-
 
55
    public $ligne_debut = 0; 
56
 
56
    public $ligne_fin = 0;
-
 
57
    
57
	public static $gestion_mots_cles = null;
58
    public static $gestion_mots_cles = null;
58
 
59
 
59
	public function __construct() {}
60
    public function __construct() {}
60
 
-
 
61
	public function def_interval($debut, $nb) {
61
    public function def_interval($debut, $nb) {
62
		$this->ligne_debut = $debut;
62
	$this->ligne_debut = $debut;
63
		$this->ligne_fin = $debut + $nb;
63
	$this->ligne_fin = $debut + $nb;
64
	}
64
    }
65
 
65
    public function readCell($colonne, $ligne, $worksheetName = '') {
66
	public function readCell($colonne, $ligne, $worksheetName = '') {
66
	if(@$this->exclues[$colonne]) return false;
67
		if(@$this->exclues[$colonne]) return false;
67
	// si des n° de morceaux ont été initialisés, on filtre...
68
		// si des n° de morceaux ont été initialisés, on filtre...
Line 68... Line -...
68
	if($this->ligne_debut && ($ligne < $this->ligne_debut || $ligne >= $this->ligne_fin)) return false;
-
 
69
	return true;
69
		if($this->ligne_debut && ($ligne < $this->ligne_debut || $ligne >= $this->ligne_fin)) return false;
70
    } 
70
		return true;
71
} 
71
	}
72
 
72
}
73
// XXX: PHP 5.3
73
 
Line 74... Line 74...
74
function __anonyme_1($v) {	return !$v['importable']; }
74
function __anonyme_1($v) {	return !$v['importable']; }
75
function __anonyme_2(&$v) {	$v = $v['nom']; }
75
function __anonyme_2(&$v) {	$v = $v['nom']; }
-
 
76
function __anonyme_3($cell) { return !is_null($cell); };
-
 
77
function __anonyme_5($item) { return is_null($item) ? '?' : $item; }
-
 
78
function __anonyme_6() { return NULL; }
-
 
79
 
-
 
80
class ImportXLS extends Cel  {
-
 
81
	static function __anonyme_4(&$item, $key) { $item = self::quoteNonNull(trim($item)); }
-
 
82
 
-
 
83
	static $ordre_BDD = Array(
-
 
84
		"ce_utilisateur",
-
 
85
		"prenom_utilisateur",
-
 
86
		"nom_utilisateur",
-
 
87
		"courriel_utilisateur",
-
 
88
		"ordre",
-
 
89
		"nom_sel",
-
 
90
		"nom_sel_nn",
-
 
91
		"nom_ret",
-
 
92
		"nom_ret_nn",
-
 
93
		"nt",
-
 
94
		"famille",
-
 
95
		"nom_referentiel",
-
 
96
		"zone_geo",
-
 
97
		"ce_zone_geo",
-
 
98
		"date_observation",
-
 
99
		"lieudit",
-
 
100
		"station",
-
 
101
		"milieu",
-
 
102
		"mots_cles_texte",
-
 
103
		"commentaire",
-
 
104
		"transmission",
-
 
105
		"date_creation",
-
 
106
		"date_modification",
-
 
107
		"date_transmission",
-
 
108
		"latitude",
-
 
109
		"longitude",
-
 
110
		"altitude",
-
 
111
		"abondance",
-
 
112
		"certitude",
-
 
113
		"phenologie",
-
 
114
		"code_insee_calcule"
-
 
115
	);
-
 
116
 
-
 
117
	// cf: initialiser_pdo_ordered_statements()
-
 
118
	// eg: "INSERT INTO cel_obs (ce_utilisateur, ..., phenologie, code_insee_calcule) VALUES"
-
 
119
	// colonnes statiques d'abord, les autres ensuite, dans l'ordre de $ordre_BDD
-
 
120
	static $insert_prefix_ordre;
-
 
121
	// eg: "(<id>, <prenom>, <nom>, <email>, now(), now(), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
-
 
122
	// dont le nombre de placeholder dépend du nombre de colonnes non-statiques
-
 
123
	// colonnes statiques d'abord, les autres ensuite, dans l'ordre de $ordre_BDD
-
 
124
	static $insert_ligne_pattern_ordre;
-
 
125
 
-
 
126
	// seconde (meilleure) possibilité
-
 
127
	// cf: initialiser_pdo_statements()
-
 
128
	// eg: "INSERT INTO cel_obs (ce_utilisateur, ..., date_creation, ...phenologie, code_insee_calcule) VALUES"
-
 
129
	static $insert_prefix;
-
 
130
	// eg: "(<id>, <prenom>, <nom>, <email>, ?, ?, ?, now(), now(), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
-
 
131
	// dont le nombre de placeholder dépend du nombre de colonnes non-statiques
-
 
132
	static $insert_ligne_pattern;
-
 
133
 
-
 
134
	/*
-
 
135
	 Ces colonnes:
-
 
136
	 - sont propres à l'ensemble des enregistrements uploadés
-
 
137
	 - sont indépendantes du numéro de lignes
-
 
138
	 - n'ont pas de valeur par défaut dans la structure de la table
-
 
139
	 - nécessitent une initialisation dans le cadre de l'upload
-
 
140
	 initialiser_colonnes_statiques() y merge les données d'identification utilisateur
-
 
141
	*/
-
 
142
	public $colonnes_statiques = array(
-
 
143
		"ce_utilisateur" => NULL,
-
 
144
		"prenom_utilisateur" => NULL,
-
 
145
		"nom_utilisateur" => NULL,
-
 
146
		"courriel_utilisateur" => NULL,
-
 
147
 
-
 
148
		// fixes (fonction SQL)
-
 
149
		// XXX future: mais pourraient varier dans le futur si la mise-à-jour
-
 
150
		// d'observation est implémentée
-
 
151
		"date_creation" => "now()",
-
 
152
		"date_modification" => "now()",
-
 
153
	);
-
 
154
 
-
 
155
	public static $prefixe_colonnes_etendues = 'ext:';
-
 
156
	public static $indexes_colonnes_etendues = Array();
-
 
157
	public static $gestion_champs_etendus = null;
-
 
158
 
-
 
159
	public $id_utilisateur = NULL;
-
 
160
 
-
 
161
	// erreurs d'import
-
 
162
	public $bilan = Array();
-
 
163
 
Line 272... Line 217...
272
 
217
				->setLineEnding("\n")
Line 273... Line -...
273
 
-
 
274
	// lecture par morceaux (chunks), NB_LIRE_LIGNE_SIMUL lignes à fois
218
				->setSheetIndex(0);
275
	// pour aboutir des requêtes SQL d'insert groupés.
-
 
276
	for($ligne = 2; $ligne < $nb_lignes + NB_LIRE_LIGNE_SIMUL; $ligne += NB_LIRE_LIGNE_SIMUL) {
219
		}
277
	    if(!$IS_CSV) {
220
 
278
		$filtre->def_interval($ligne, NB_LIRE_LIGNE_SIMUL);
221
		// on ne conserve que l'en-tête
-
 
222
		$filtre = new MyReadFilter();
279
		$objReader->setReadFilter($filtre);
223
		$filtre->def_interval(1, 2);
-
 
224
		$objReader->setReadFilter($filtre);
280
 
225
 
281
		/* recharge avec $filtre actif (filtre sur lignes colonnes):
-
 
282
		   - exclue les colonnes inutiles/inutilisables)
226
		$objPHPExcel = $objReader->load($fichier);
283
		   - ne selectionne que les lignes dans le range [$ligne - $ligne + NB_LIRE_LIGNE_SIMUL] */
227
		$obj_infos = $objReader->listWorksheetInfo($fichier);
284
		$objPHPExcel = $objReader->load($fichier)->getActiveSheet();
228
 
285
 
-
 
286
		// set col typing
229
		if ($IS_CSV) {
287
		if(C_CE_ZONE_GEO != 'C_CE_ZONE_GEO')
-
 
288
		    $objPHPExcel->getStyle(C_CE_ZONE_GEO . '2:' . C_CE_ZONE_GEO . $objPHPExcel->getHighestRow())->getNumberFormat()->setFormatCode('00000');
230
			// $nb_lignes est déjà défini ci-dessus
289
 
231
			$csvFileHandler = fopen($fichier, 'r');
290
		// TODO: set to string type
232
			// nous utilisons la valeur de retour dans un but informatif de l'utilisateur à la
291
		if(C_ZONE_GEO != 'C_ZONE_GEO')
-
 
292
		    $objPHPExcel->getStyle(C_ZONE_GEO . '2:' . C_ZONE_GEO . $objPHPExcel->getHighestRow())->getNumberFormat()->setFormatCode('00000');
233
			// fin de l'import, *mais aussi* dans un array_diff_key() ci-dessous car bien que dans le
293
 
-
 
294
		$donnees = $objPHPExcel->toArray(NULL, FALSE, TRUE, TRUE);
234
			// fond le "parser" fgetcsv() n'ait pas d'intérêt à connaître les colonnes à ignorer,
295
	    }
235
			// il se trouve que celles-ci peuvent interférer sur des fonctions comme traiterEspece()
296
	    else {
236
			// cf test "ref-nom-num.test.php" pour lequel l'élément C_NOM_SEL vaudrait 3 et $ligne serait array(3 => -42)
297
		$i = NB_LIRE_LIGNE_SIMUL;
237
			$filtre->exclues = self::detectionEntete(fgetcsv($csvFileHandler), TRUE);
298
		$donnees = array();
-
 
299
		while($i--) {
238
		} else {
300
		    $tab = fgetcsv($csvFileHandler);
-
 
301
		    if(!$tab) continue;
239
			// XXX: indépendant du readFilter ?
302
		    $donnees[] = array_diff_key($tab, $filtre->exclues);
-
 
303
		}
-
 
304
 
240
			$nb_lignes = $obj_infos[0]['totalRows'];
305
	    }
-
 
306
 
241
			$donnees = $objPHPExcel->getActiveSheet()->toArray(NULL, FALSE, TRUE, TRUE);
307
	    // var_dump($donnees, get_defined_constants(true)['user']);die;
242
			$filtre->exclues = self::detectionEntete($donnees[1]);
308
	    // ici on appel la fonction qui fera effectivement l'insertion multiple
-
 
309
	    // à partir des (au plus) NB_LIRE_LIGNE_SIMUL lignes
243
		}
310
 
-
 
311
	    // TODO: passer $this, ne sert que pour appeler des méthodes publiques qui pourraient être statiques
-
 
312
	    list($enregistrements, $images, $mots_cle, $champs_etendus) =
244
 
313
		self::chargerLignes($this, $donnees, $this->colonnes_statiques, $dernier_ordre);
245
		$obs_ajouts = 0;
314
	    if(! $enregistrements) break;
-
 
315
 
246
		$obs_maj = 0;
316
	    self::trierColonnes($enregistrements);
247
		$nb_images_ajoutees = 0;
317
	    // normalement: NB_LIRE_LIGNE_SIMUL, sauf si une enregistrement ne semble pas valide
-
 
318
	    // ou bien lors du dernier chunk
-
 
319
 
-
 
320
	    $nb_rec = count($enregistrements);
248
		$nb_mots_cle_ajoutes = 0;
321
	    $sql_pattern = self::$insert_prefix .
-
 
322
		str_repeat(self::$insert_ligne_pattern_ordre . ', ', $nb_rec - 1) .
249
		$nb_champs_etendus_inseres = 0;
323
		self::$insert_ligne_pattern_ordre;
-
 
324
 
250
 
325
	    $sql_pattern = self::$insert_prefix .
-
 
326
		str_repeat(self::$insert_ligne_pattern . ', ', $nb_rec - 1) .
251
		$dernier_ordre = Cel::db()->requeter("SELECT MAX(ordre) AS ordre FROM cel_obs WHERE ce_utilisateur = $id_utilisateur");
327
		self::$insert_ligne_pattern;
-
 
328
 
-
 
329
	    Cel::db()->beginTransaction();
252
		$dernier_ordre = intval($dernier_ordre[0]['ordre']) + 1;
330
	    $stmt = Cel::db()->prepare($sql_pattern);
253
		if (! $dernier_ordre) {
331
	    $donnees = array();
254
			$dernier_ordre = 0;
332
	    foreach($enregistrements as $e) $donnees = array_merge($donnees, array_values($e));
-
 
333
 
-
 
334
	     // echo $sql_pattern . "\n"; var_dump($enregistrements, $donnees); die; // debug ici
255
		}
335
 
-
 
336
	    $stmt->execute($donnees);
256
 
337
 
257
		// on catch to les trigger_error(E_USER_NOTICE);
338
	    // $stmt->debugDumpParams(); // https://bugs.php.net/bug.php?id=52384
258
		set_error_handler(array($this, 'erreurs_stock'), E_USER_NOTICE);
339
	    $dernier_autoinc = Cel::db()->lastInsertId();
259
		$this->taxon_info_webservice = new RechercheInfosTaxonBeta($this->config, NULL);
340
	    Cel::db()->commit();
260
 
341
 
261
		// lecture par morceaux (chunks), NB_LIRE_LIGNE_SIMUL lignes à fois
342
	    if(! $dernier_autoinc) trigger_error("l'insertion semble avoir échoué", E_USER_NOTICE);
262
		// pour aboutir des requêtes SQL d'insert groupés.
343
 
263
		for ($ligne = 2; $ligne < $nb_lignes + NB_LIRE_LIGNE_SIMUL; $ligne += NB_LIRE_LIGNE_SIMUL) {
344
	    $obs_ajouts += count($enregistrements);
264
			if (!$IS_CSV) {
345
	    // $obs_ajouts += count($enregistrements['insert']);
265
				$filtre->def_interval($ligne, NB_LIRE_LIGNE_SIMUL);
346
	    // $obs_maj += count($enregistrements['update']);
-
 
347
	    
266
				$objReader->setReadFilter($filtre);
348
	    $ordre_ids = self::chargerCorrespondancesIdOrdre($this, $enregistrements);
-
 
349
	    
-
 
350
	    $nb_images_ajoutees += self::stockerImages($enregistrements, $images, $ordre_ids);
-
 
351
	    $nb_mots_cle_ajoutes += self::stockerMotsCle($enregistrements, $mots_cle, $dernier_autoinc);
267
 
352
	    $nb_champs_etendus_inseres += self::stockerChampsEtendus($champs_etendus, $ordre_ids, $this->config);
-
 
353
	}
268
				$objPHPExcel = $objReader->load($fichier)->getActiveSheet();
354
 
269
 
355
	restore_error_handler();
-
 
356
 
-
 
357
	if($this->bilan) echo implode("\n", $this->bilan) . "\n";
-
 
358
	printf('%1$d observation%2$s ajoutée%2$s' . "\n" .
-
 
359
	       '%3$d image%4$s attachée%4$s' . "\n" .
-
 
360
	       // '%5$d mot%6$c-clef ajouté%6$c [TODO]' . "\n" . // TODO
270
				// set col typing
361
	       (count($filtre->exclues) > 0 ? 'colonne%7$s non-traitée%7$s: %8$s' . "\n" : ''),
-
 
362
 
-
 
363
	       $obs_ajouts,
271
				if (C_CE_ZONE_GEO != 'C_CE_ZONE_GEO') {
364
	       $obs_ajouts > 1 ? 's' : '',
-
 
365
	       $nb_images_ajoutees,
-
 
366
	       $nb_images_ajoutees > 1 ? 's' : '',
272
					$objPHPExcel->getStyle(C_CE_ZONE_GEO . '2:' . C_CE_ZONE_GEO . $objPHPExcel->getHighestRow())->getNumberFormat()->setFormatCode('00000');
367
	       $nb_mots_cle_ajoutes,
-
 
368
	       $nb_mots_cle_ajoutes > 1 ? 's' : '',
-
 
369
	       count($filtre->exclues) > 1 ? 's' : '',
-
 
370
	       implode(', ', $filtre->exclues));
-
 
371
	die();
-
 
372
    }
-
 
373
 
-
 
374
    /* detectionEntete() sert deux rôles:
-
 
375
       1) détecter le type de colonne attendu à partir des textes de la ligne d'en-tête afin de define()
-
 
376
       2) permet d'identifier les colonnes non-supportées/inutiles afin d'alléger le processus de parsing de PHPExcel
-
 
377
       grace au ReadFilter (C'est le rôle de la valeur de retour)
-
 
378
 
273
				}
379
       La raison de la présence du paramètre $numeric_keys est que pour réussir à identifier les colonnes à exclure nous
-
 
380
       devons traiter un tableau représentant la ligne d'en-tête aussi bien:
-
 
381
       - sous forme associative pour PHPExcel (les clefs sont les lettres de l'alphabet)
274
				// TODO: set to string type
382
       - sous forme de clefs numériques (fgetcsv())
275
				if (C_ZONE_GEO != 'C_ZONE_GEO') {
383
       Le détecter après coup est difficile et pourtant cette distinction est importante car le comportement
-
 
384
       d'array_merge() (réordonnancement des clefs numérique) n'est pas souhaitable dans le second cas. */
276
					$objPHPExcel->getStyle(C_ZONE_GEO . '2:' . C_ZONE_GEO . $objPHPExcel->getHighestRow())->getNumberFormat()->setFormatCode('00000');
385
    static function detectionEntete($entete, $numeric_keys = FALSE) {
-
 
386
	$colonnes_reconnues = Array();
277
				}
387
	$cols = FormateurGroupeColonne::nomEnsembleVersListeColonnes('standard,avance');
-
 
388
	
-
 
-
 
278
				$donnees = $objPHPExcel->toArray(NULL, FALSE, TRUE, TRUE);
389
	foreach($entete as $k => $v) {
279
			} else {
390
	    // traite les colonnes en faisant fi de la casse et des accents
-
 
391
	    $entete_simple = iconv('UTF-8', 'ASCII//TRANSLIT', strtolower(trim($v)));
280
				$i = NB_LIRE_LIGNE_SIMUL;
392
	    foreach($cols as $col) {
281
				$donnees = array();
393
			$entete_officiel_simple = iconv('UTF-8', 'ASCII//TRANSLIT', strtolower(trim($col['nom'])));
282
				while ($i--) {
394
			$entete_officiel_abbrev = $col['abbrev'];
283
					$tab = fgetcsv($csvFileHandler);
395
			if($entete_simple == $entete_officiel_simple || $entete_simple == $entete_officiel_abbrev) {
284
					if (!$tab) {
396
			    // debug echo "define C_" . strtoupper($entete_officiel_abbrev) . ", $k ($v)\n";
-
 
397
			    define("C_" . strtoupper($entete_officiel_abbrev), $k);
285
						continue;
398
			    $colonnes_reconnues[$k] = 1;
286
					}
399
			    break;
-
 
400
			}
-
 
401
 
-
 
402
			if(strpos($v, self::$prefixe_colonnes_etendues) === 0) {
-
 
403
				$colonnes_reconnues[$k] = 1;
-
 
404
				self::$indexes_colonnes_etendues[$k] = $v;
-
 
405
				break;
-
 
406
			}
-
 
407
	    }
-
 
408
	}
-
 
409
	
-
 
410
	// défini tous les index que nous utilisons à une valeur d'index de colonne Excel qui n'existe pas dans
-
 
411
	// le tableau renvoyé par PHPExcel
-
 
412
	// Attention cependant d'utiliser des indexes différenciés car traiterLonLat() et traiterEspece()
-
 
413
	// les utilisent
-
 
414
	foreach($cols as $col) {
-
 
415
	    if(!defined("C_" . strtoupper($col['abbrev'])))
-
 
416
		define("C_" . strtoupper($col['abbrev']), "C_" . strtoupper($col['abbrev']));
-
 
417
	}
-
 
418
 
-
 
419
	// prépare le filtre de PHPExcel qui évitera le traitement de toutes les colonnes superflues
-
 
420
 
-
 
421
	// eg: diff ( Array( H => Commune, I => rien ) , Array( H => 1, K => 1 )
-
 
422
	// ==> Array( I => rien )
-
 
423
	$colonnesID_non_reconnues = array_diff_key($entete, $colonnes_reconnues);
-
 
424
 
-
 
425
	// des colonnes de FormateurGroupeColonne::nomEnsembleVersListeColonnes()
-
 
426
	// ne retient que celles marquées "importables"
-
 
427
	$colonnes_automatiques = array_filter($cols, '__anonyme_1');
-
 
428
 
-
 
429
	// ne conserve que le nom long pour matcher avec la ligne XLS d'entête
-
 
430
	array_walk($colonnes_automatiques, '__anonyme_2');
-
 
431
 
-
 
432
	// intersect ( Array ( N => Milieu, S => Ordre ), Array ( ordre => Ordre, phenologie => Phénologie ) )
-
 
433
	// ==> Array ( S => Ordre, AA => Phénologie )
-
 
434
	$colonnesID_a_exclure = array_intersect($entete, $colonnes_automatiques);
-
 
435
 
-
 
436
	if($numeric_keys) {
-
 
437
	    return $colonnesID_non_reconnues + $colonnesID_a_exclure;
-
 
438
	}
-
 
439
	// TODO: pourquoi ne pas comparer avec les abbrevs aussi ?
-
 
440
	// merge ( Array( I => rien ) , Array ( S => Ordre, AA => Phénologie ) )
-
 
441
	// ==> Array ( I => rien, AA => Phénologie )
-
 
442
	return array_merge($colonnesID_non_reconnues, $colonnesID_a_exclure);
-
 
443
    }
-
 
444
    
-
 
445
    static function chargerCorrespondancesIdOrdre($cel, $lignes) {
-
 
446
    	
-
 
447
    	$ordre_ids = array();
-
 
448
    
-
 
449
    	$requete_obs_ids = "SELECT id_observation, ordre FROM cel_obs WHERE ordre IN (";
-
 
450
    	foreach($lignes as &$ligne) {
-
 
451
    		$requete_obs_ids .= $ligne['ordre'].',';
-
 
452
    	}
-
 
453
    	$requete_obs_ids = rtrim($requete_obs_ids, ',');
-
 
454
    	$requete_obs_ids .= ") AND ce_utilisateur = ".Cel::db()->proteger($cel->id_utilisateur);
-
 
455
    	
-
 
456
    	
-
 
457
    	$obs_ids = Cel::db()->requeter($requete_obs_ids);
-
 
458
    	foreach($obs_ids as &$obs) {
-
 
459
    		$ordre_ids[$obs['ordre']] = $obs['id_observation'];
-
 
460
    	}
-
 
461
    	return $ordre_ids;
-
 
462
    }
-
 
463
 
-
 
464
    /*
-
 
465
     * charge un groupe de lignes
-
 
466
     */
-
 
467
    static function chargerLignes($cel, $lignes, $colonnes_statiques, &$dernier_ordre) {
-
 
468
	$enregistrement = NULL;
-
 
469
	$enregistrements = Array();
-
 
470
	$toutes_images = Array();
-
 
471
	$tous_mots_cle = Array();
-
 
472
	$tous_champs_etendus = array();
-
 
473
 
-
 
474
	foreach($lignes as $ligne) {
-
 
475
		
-
 
476
	    // dans le cas de fgetcsv, on peut avoir des false additionnel (cf do/while l. 279)
-
 
477
	    if($ligne === false) continue;
-
 
478
 
-
 
479
	    //$ligne = array_filter($ligne, function($cell) { return !is_null($cell); });
-
 
480
	    //if(!$ligne) continue;
-
 
481
	    // on a besoin des NULL pour éviter des notice d'index indéfini
-
 
482
	    if(! array_filter($ligne, '__anonyme_3')) continue;
-
 
483
 
-
 
484
	    if( ($enregistrement = self::chargerLigne($ligne, $dernier_ordre, $cel)) ) {
-
 
485
		// $enregistrements[] = array_merge($colonnes_statiques, $enregistrement);
-
 
486
		$enregistrements[] = $enregistrement;
-
 
487
		$pos = count($enregistrements) - 1;
-
 
488
		$last = &$enregistrements[$pos];
-
 
489
 
-
 
490
		if(isset($enregistrement['_images'])) {
-
 
491
		    // ne dépend pas de cel_obs, et seront insérées *après* les enregistrements
-
 
492
		    // mais nous ne voulons pas nous priver de faire des INSERT multiples pour autant
-
 
493
		    $toutes_images[] = Array("images" => $last['_images'],
-
 
494
					     "obs_pos" => $pos);
-
 
495
		    // ce champ n'a pas à faire partie de l'insertion dans cel_obs,
-
 
496
		    // mais est utile pour cel_obs_images
-
 
497
		    unset($last['_images']);
-
 
498
		}
-
 
499
 
-
 
500
		if(isset($enregistrement['_mots_cle'])) {
-
 
501
		    // ne dépend pas de cel_obs, et seront insérés *après* les enregistrements
-
 
502
		    // mais nous ne voulons pas nous priver de faire des INSERT multiples pour autant
-
 
503
		    $tous_mots_cle[] = Array("mots_cle" => $last['_mots_cle'],
-
 
504
					     "obs_pos" => $pos);
-
 
505
		    // la version inlinée des mots est enregistrées dans cel_obs
-
 
506
		    // mais cel_mots_cles_obs fait foi.
-
 
507
		    // XXX: postponer l'ajout de ces informations dans cel_obs *après* l'insertion effective
-
 
508
		    // des records dans cel_mots_cles_obs ?
-
 
509
		    unset($last['_mots_cle']);
-
 
510
		}
-
 
511
 
-
 
512
		if(isset($enregistrement['_champs_etendus'])) {
-
 
513
			$tous_champs_etendus[] = Array("champs_etendus" => $last['_champs_etendus'],
-
 
514
					"ordre" => $dernier_ordre);
-
 
515
			unset($last['_champs_etendus']);
-
 
516
		}
-
 
517
		$dernier_ordre++;
-
 
518
	    }
-
 
519
	}
-
 
520
 
-
 
521
	// XXX future: return Array($enregistrements_a_inserer, $enregistrements_a_MAJ, $toutes_images);
-
 
522
	return Array($enregistrements, $toutes_images, $tous_mots_cle, $tous_champs_etendus);
-
 
523
    }
-
 
524
 
-
 
525
 
-
 
526
    static function trierColonnes(&$enregistrements) {
-
 
527
	foreach($enregistrements as &$enregistrement) {
-
 
528
	    $enregistrement = self::sortArrayByArray($enregistrement, self::$ordre_BDD);
-
 
529
	    //array_walk($enregistrement, function(&$item, $k) { $item = is_null($item) ? "NULL" : $item; });
-
 
530
	    //$req .= implode(', ', $enregistrement) . "\n";
-
 
531
	}
-
 
532
    }
-
 
533
 
-
 
534
 
-
 
535
    static function stockerMotsCle($enregistrements, $tous_mots_cle, $lastid) {
-
 
536
	$c = 0;
-
 
537
	// debug: var_dump($tous_mots_cle);die;
-
 
538
	foreach($tous_mots_cle as $v) $c += count($v['mots_cle']['to_insert']);
-
 
539
	return $c;
-
 
540
    }
-
 
541
 
-
 
542
    static function stockerImages($enregistrements, $toutes_images, $ordre_ids) {
-
 
543
	$images_insert = 'INSERT INTO cel_obs_images (id_image, id_observation) VALUES %s ON DUPLICATE KEY UPDATE id_image = id_image';
-
 
544
	$images_obs_assoc = Array();
-
 
545
 
-
 
546
	foreach($toutes_images as $images_pour_obs) {
-
 
547
	    $obs = $enregistrements[$images_pour_obs["obs_pos"]];
-
 
548
	    $id_obs = $ordre_ids[$obs['ordre']]; // id réel de l'observation correspondant à l'ordre
-
 
549
	    foreach($images_pour_obs['images'] as $image) {
-
 
550
		$images_obs_assoc[] = sprintf('(%d,%d)',
-
 
551
					      $image['id_image'], // intval() useless
-
 
552
					      $id_obs); // intval() useless
-
 
553
	    }
-
 
554
	}
-
 
555
 
-
 
556
	if($images_obs_assoc) {
-
 
557
	    $requete = sprintf($images_insert, implode(', ', $images_obs_assoc));
-
 
558
	    // debug echo "$requete\n";
-
 
559
	    Cel::db()->requeter($requete);
-
 
560
	}
-
 
561
 
-
 
562
	return count($images_obs_assoc);
-
 
563
    }
-
 
564
 
-
 
565
    /*
-
 
566
      Aucune des valeurs présentes dans $enregistrement n'est quotée
-
 
567
      cad aucune des valeurs retournée par traiter{Espece|Localisation}()
-
 
568
      car ce tableau est passé à un PDO::preparedStatement() qui applique
-
 
569
      proprement les règle d'échappement.
-
 
570
    */
-
 
571
    static function chargerLigne($ligne, $dernier_ordre, $cel) {
-
 
572
	// évite des notices d'index lors des trigger_error()
-
 
573
	$ref_ligne = !empty($ligne[C_NOM_SEL]) ? trim($ligne[C_NOM_SEL]) : '';
-
 
574
 
-
 
575
	// en premier car le résultat est utile pour
-
 
576
	// * traiter espèce (traiterEspece())
-
 
577
	// * traiter longitude et latitude (traiterLonLat())
-
 
578
	$referentiel = self::identReferentiel(trim(strtolower(@$ligne[C_NOM_REFERENTIEL])), $ligne, $ref_ligne);
-
 
579
 
-
 
580
	// $espece est rempli de plusieurs informations
-
 
581
	$espece = Array(C_NOM_SEL => NULL, C_NOM_SEL_NN => NULL, C_NOM_RET => NULL,
-
 
582
			C_NOM_RET_NN => NULL, C_NT => NULL, C_FAMILLE => NULL);
-
 
583
	self::traiterEspece($ligne, $espece, $referentiel, $cel->taxon_info_webservice);
-
 
584
 
-
 
585
	if(!$espece[C_NOM_SEL]) $referentiel = Cel::$fallback_referentiel;
-
 
586
	if($espece[C_NOM_SEL] && !$espece[C_NOM_SEL_NN]) $referentiel = Cel::$fallback_referentiel;
-
 
587
 
-
 
588
	// $localisation est rempli à partir de plusieurs champs: C_ZONE_GEO et C_CE_ZONE_GEO
-
 
589
	$localisation = Array(C_ZONE_GEO => NULL, C_CE_ZONE_GEO => NULL);
-
 
590
	self::traiterLocalisation($ligne, $localisation);
-
 
591
 
-
 
592
	// $transmission est utilisé pour date_transmission
-
 
593
	// XXX: @ contre "Undefined index"
-
 
594
	@$transmission = in_array(strtolower(trim($ligne[C_TRANSMISSION])), array(1, 'oui')) ? 1 : 0;
-
 
595
 
-
 
596
 
-
 
597
	// Dans ce tableau, seules devraient apparaître les données variable pour chaque ligne.
-
 
598
	// Dans ce tableau, l'ordre des clefs n'importe pas (cf: self::sortArrayByArray())
-
 
599
	$enregistrement = Array(
-
 
600
	    "ordre" => $dernier_ordre,
-
 
601
 
-
 
602
	    "nom_sel" => $espece[C_NOM_SEL],
-
 
603
	    "nom_sel_nn" => $espece[C_NOM_SEL_NN],
-
 
604
	    "nom_ret" => $espece[C_NOM_RET],
-
 
605
	    "nom_ret_nn" => $espece[C_NOM_RET_NN],
-
 
606
	    "nt" => $espece[C_NT],
-
 
607
	    "famille" => $espece[C_FAMILLE],
-
 
608
 
-
 
609
	    "nom_referentiel" => $referentiel,
-
 
610
 
-
 
611
	    "zone_geo" => $localisation[C_ZONE_GEO],
-
 
612
	    "ce_zone_geo" => $localisation[C_CE_ZONE_GEO],
-
 
613
 
-
 
614
	    // $ligne: uniquement pour les infos en cas de gestion d'erreurs (date incompréhensible)
-
 
615
	    "date_observation" => isset($ligne[C_DATE_OBSERVATION]) ? self::traiterDateObs($ligne[C_DATE_OBSERVATION], $ref_ligne) : NULL,
-
 
616
 
-
 
617
	    "lieudit" => isset($ligne[C_LIEUDIT]) ? trim($ligne[C_LIEUDIT]) : NULL,
-
 
618
	    "station" => isset($ligne[C_STATION]) ? trim($ligne[C_STATION]) : NULL,
-
 
619
	    "milieu" => isset($ligne[C_MILIEU]) ? trim($ligne[C_MILIEU]) : NULL,
-
 
620
 
-
 
621
	    "mots_cles_texte" => NULL, // TODO: foreign-key
-
 
622
	    // XXX: @ contre "Undefined index"
-
 
623
	    "commentaire" => isset($ligne[C_COMMENTAIRE]) ? trim($ligne[C_COMMENTAIRE]) : NULL,
-
 
624
 
-
 
625
	    "transmission" => $transmission,
-
 
626
	    "date_transmission" => $transmission ? date("Y-m-d H:i:s") : NULL, // pas de fonction SQL dans un PDO statement, <=> now()
-
 
627
 
-
 
628
	    // $ligne: uniquement pour les infos en cas de gestion d'erreurs (lon/lat incompréhensible)
-
 
Line -... Line 287...
-
 
287
					$donnees[] = array_diff_key($tab, $filtre->exclues);
629
	    "latitude" => isset($ligne[C_LATITUDE]) ? self::traiterLonLat(NULL, $ligne[C_LATITUDE], $referentiel, $ref_ligne) : NULL,
288
				}
-
 
289
			}
-
 
290
 
-
 
291
			list($enregistrements, $images, $mots_cle, $champs_etendus) = self::chargerLignes($this, $donnees, $this->colonnes_statiques, $dernier_ordre);
-
 
292
			if (! $enregistrements) {
-
 
293
				break;
-
 
294
			}
-
 
295
 
-
 
296
			self::trierColonnes($enregistrements);
-
 
297
			// normalement: NB_LIRE_LIGNE_SIMUL, sauf si une enregistrement ne semble pas valide
-
 
298
			// ou bien lors du dernier chunk
-
 
299
 
-
 
300
			$nb_rec = count($enregistrements);
630
	    "longitude" => isset($ligne[C_LONGITUDE]) ? self::traiterLonLat($ligne[C_LONGITUDE], NULL, $referentiel, $ref_ligne) : NULL,
301
			$sql_pattern = self::$insert_prefix.
Line 631... Line -...
631
	    "altitude" => isset($ligne[C_ALTITUDE]) ? intval($ligne[C_ALTITUDE]) : NULL, // TODO: guess alt from lon/lat
-
 
632
 
302
				str_repeat(self::$insert_ligne_pattern.', ', $nb_rec - 1).
633
	    // @ car potentiellement optionnelles ou toutes vides => pas d'index dans PHPExcel (tableau optimisé)
-
 
634
	    "abondance" => @$ligne[C_ABONDANCE],
-
 
635
	    "certitude" => @$ligne[C_CERTITUDE],
303
				self::$insert_ligne_pattern;
636
	    "phenologie" => @$ligne[C_PHENOLOGIE],
304
 
-
 
305
			Cel::db()->beginTransaction();
637
 
306
			$stmt = Cel::db()->prepare($sql_pattern);
638
	    "code_insee_calcule" => substr($localisation[C_CE_ZONE_GEO], -5) // varchar(5)
-
 
639
	);
307
			$donnees = array();
640
 
308
			foreach ($enregistrements as $e) {
641
	// passage de $enregistrement par référence, ainsi ['_images'] n'est défini
309
				$donnees = array_merge($donnees, array_values($e));
642
	// que si des résultats sont trouvés
310
			}
643
	// "@" car PHPExcel supprime les colonnes null sur toute la feuille (ou tout le chunk)
311
 
644
	if(@$ligne[C_IMAGES]) self::traiterImage($ligne[C_IMAGES], $cel->id_utilisateur, $enregistrement);
-
 
645
 
312
			$stmt->execute($donnees);
646
	if(@$ligne[C_MOTS_CLES_TEXTE]) self::traiterMotsCle($ligne[C_MOTS_CLES_TEXTE], $cel->id_utilisateur, $enregistrement);
313
 
-
 
314
			$dernier_autoinc = Cel::db()->lastInsertId();
647
	
315
			Cel::db()->commit();
648
	$champs_etendus = self::traiterChampsEtendus($ligne, self::$indexes_colonnes_etendues);
316
 
649
	if(!empty($champs_etendus)) {
-
 
650
		$enregistrement["_champs_etendus"] = $champs_etendus;
317
			if (! $dernier_autoinc) {
651
	}
318
				trigger_error("l'insertion semble avoir échoué", E_USER_NOTICE);
652
 
319
			}
653
	return $enregistrement;
320
 
654
    }
-
 
655
    
321
			$obs_ajouts += count($enregistrements);
-
 
322
 
-
 
323
			$ordre_ids = self::chargerCorrespondancesIdOrdre($this, $enregistrements);
-
 
324
 
656
    static function traiterChampsEtendus(&$ligne, &$indexes_colonnes_etendues) {
325
			$nb_images_ajoutees += self::stockerImages($enregistrements, $images, $ordre_ids);
657
    	$champs_etendus_indexes = array();
326
			$nb_mots_cle_ajoutes += self::stockerMotsCle($enregistrements, $mots_cle, $dernier_autoinc);
-
 
327
			$nb_champs_etendus_inseres += self::stockerChampsEtendus($champs_etendus, $ordre_ids, $this->config);
658
    	foreach($indexes_colonnes_etendues as $index_num => $label) {
328
		}
-
 
329
 
-
 
330
		restore_error_handler();
659
    		if(isset($ligne[$index_num])) {
331
 
660
    			$champs_etendus_indexes[str_replace(self::$prefixe_colonnes_etendues, '', $label)] = $ligne[$index_num];
332
		if ($this->bilan) {
661
    		}
333
			echo implode("\n", $this->bilan) . "\n";
662
    	}
334
		}
663
    	return $champs_etendus_indexes;
335
		printf('%1$d observation%2$s ajoutée%2$s' . "\n" .
664
    }
336
			'%3$d image%4$s attachée%4$s' . "\n" .
-
 
337
			(count($filtre->exclues) > 0 ? 'colonne%7$s non-traitée%7$s: %8$s' . "\n" : ''),
665
 
338
 
666
    static function traiterImage($str, $id_utilisateur, &$enregistrement) {
-
 
667
	$liste_images = array_filter(explode("/", $str));
339
			$obs_ajouts,
-
 
340
			$obs_ajouts > 1 ? 's' : '',
-
 
341
			$nb_images_ajoutees,
-
 
342
			$nb_images_ajoutees > 1 ? 's' : '',
668
 
343
			$nb_mots_cle_ajoutes,
669
	//array_walk($liste_images, '__anonyme_4');
-
 
670
	array_walk($liste_images, array(__CLASS__, '__anonyme_4'));
344
			$nb_mots_cle_ajoutes > 1 ? 's' : '',
-
 
345
			count($filtre->exclues) > 1 ? 's' : '',
-
 
346
			implode(', ', $filtre->exclues));
671
	$requete = sprintf(
347
		die();
-
 
348
	}
672
	    "SELECT id_image, nom_original FROM cel_images WHERE ce_utilisateur = %d AND nom_original IN (%s)",
349
 
-
 
350
	/* detectionEntete() sert deux rôles:
-
 
351
	 1) détecter le type de colonne attendu à partir des textes de la ligne d'en-tête afin de define()
-
 
352
	 2) permet d'identifier les colonnes non-supportées/inutiles afin d'alléger le processus de parsing de PHPExcel
673
	    $id_utilisateur,
353
	 grace au ReadFilter (C'est le rôle de la valeur de retour)
674
	    implode(',', $liste_images));
354
 
675
 
355
	 La raison de la présence du paramètre $numeric_keys est que pour réussir à identifier les colonnes à exclure nous
676
	$resultat = Cel::db()->requeter($requete);
356
	 devons traiter un tableau représentant la ligne d'en-tête aussi bien:
677
 
-
 
678
	if($resultat) $enregistrement['_images'] = $resultat;
357
	  - sous forme associative pour PHPExcel (les clefs sont les lettres de l'alphabet)
679
    }
358
	  - sous forme de clefs numériques (fgetcsv())
-
 
359
	  Le détecter après coup est difficile et pourtant cette distinction est importante car le comportement
680
 
360
	  d'array_merge() (réordonnancement des clefs numérique) n'est pas souhaitable dans le second cas. */
-
 
361
	static function detectionEntete($entete, $numeric_keys = FALSE) {
-
 
362
		$colonnes_reconnues = Array();
-
 
363
		$cols = FormateurGroupeColonne::nomEnsembleVersListeColonnes('standard,avance');
-
 
364
 
-
 
365
		foreach ($entete as $k => $v) {
681
    static function traiterMotsCle($str, $id_utilisateur, &$enregistrement) {
366
			// traite les colonnes en faisant fi de la casse et des accents
682
	$liste_mots_cle = $liste_mots_cle_recherche = array_map("trim", array_unique(array_filter(explode(",", $str))));
367
			$entete_simple = iconv('UTF-8', 'ASCII//TRANSLIT', strtolower(trim($v)));
683
	array_walk($liste_mots_cle_recherche, array(__CLASS__, '__anonyme_4'));
368
			foreach ($cols as $col) {
684
 
369
				$entete_officiel_simple = iconv('UTF-8', 'ASCII//TRANSLIT', strtolower(trim($col['nom'])));
685
	if(self::$gestion_mots_cles == null) {
370
				$entete_officiel_abbrev = $col['abbrev'];
-
 
371
				if ($entete_simple == $entete_officiel_simple || $entete_simple == $entete_officiel_abbrev) {
686
		$gestion_mots_cles = new GestionMotsCles($this->config, 'obs');
372
					// debug echo "define C_" . strtoupper($entete_officiel_abbrev) . ", $k ($v)\n";
687
	}
373
					define("C_" . strtoupper($entete_officiel_abbrev), $k);
688
	$mots_cles_ids = $gestion_mots_cles->obtenirIdsMotClesPourMotsCles($liste_mots_cle, $id_utilisateur);
374
					$colonnes_reconnues[$k] = 1;
689
	foreach($mots_cles_ids as $mot_cle) {
-
 
690
		$resultat[$mot_cle['id_mot_cle']] = $mot_cle['mot_cle'];
375
					break;
691
	}
376
				}
692
 
377
 
693
	$enregistrement['mots_cles_texte'] = implode(',', $liste_mots_cle);
378
				if (strpos($v, self::$prefixe_colonnes_etendues) === 0) {
694
	$enregistrement['_mots_cle'] = array("existing" => $resultat,
-
 
695
					     "to_insert" => array_diff($liste_mots_cle, $resultat));
-
 
696
    }
-
 
697
 
379
					$colonnes_reconnues[$k] = 1;
698
 
380
					self::$indexes_colonnes_etendues[$k] = $v;
699
    /* FONCTIONS de TRANSFORMATION de VALEUR DE CELLULE */
-
 
700
 
381
					break;
701
    // TODO: PHP 5.3, utiliser date_parse_from_format()
382
				}
702
    // TODO: parser les heures (cf product-owner)
383
			}
-
 
384
		}
703
    // TODO: passer par le timestamp pour s'assurer de la validité
385
 
704
    static function traiterDateObs($date, $ref_ligne) {
386
		// défini tous les index que nous utilisons à une valeur d'index de colonne Excel qui n'existe pas dans
-
 
387
		// le tableau renvoyé par PHPExcel
705
	// TODO: see https://github.com/PHPOffice/PHPExcel/issues/208
388
		// Attention cependant d'utiliser des indexes différenciés car traiterLonLat() et traiterEspece()
706
	// TODO: PHPExcel_Shared_Date::ExcelToPHP()
389
		// les utilisent
707
	if(is_double($date)) {
390
		foreach ($cols as $col) {
708
	    if($date > 0)
391
			if (!defined('C_'.strtoupper($col['abbrev']))) {
709
		return PHPExcel_Style_NumberFormat::toFormattedString($date, PHPExcel_Style_NumberFormat::FORMAT_DATE_YYYYMMDD2) . " 00:00:00";
392
				define('C_'.strtoupper($col['abbrev']), 'C_'.strtoupper($col['abbrev']));
710
	    trigger_error("ligne \"{$ref_ligne}\": " .
393
			}
-
 
394
		}
711
			  "Attention: date antérieure à 1970 et format de cellule \"DATE\" utilisés ensemble",
395
 
712
			  E_USER_NOTICE);
396
		// prépare le filtre de PHPExcel qui évitera le traitement de toutes les colonnes superflues
713
 
397
		$colonnesID_non_reconnues = array_diff_key($entete, $colonnes_reconnues);
714
	    // throw new Exception("erreur: date antérieure à 1970 et format de cellule \"DATE\" utilisés ensemble");
-
 
715
 
-
 
716
	    // attention, UNIX timestamp, car Excel les décompte depuis 1900
-
 
717
	    // cf http://fczaja.blogspot.fr/2011/06/convert-excel-date-into-timestamp.html
-
 
718
	    // $timestamp = ($date - MIN_DATES_DIFF) * 60 * 60 * 24 - time(); // NON
398
 
719
 
-
 
720
	    // $timestamp = PHPExcel_Calculation::getInstance()->calculateFormula("=" . $date . "-DATE(1970,1,1)*60*60*24"); // NON
-
 
721
 
399
		// des colonnes de FormateurGroupeColonne::nomEnsembleVersListeColonnes()
722
	    // echo strftime("%Y/%m/%d 00:00:00", $timestamp); // NON
-
 
723
	}
-
 
724
	else {
-
 
725
	    // attend l'un des formats de
-
 
726
	    // http://www.php.net/manual/fr/datetime.formats.date.php
-
 
727
	    // le plus simple: YYYY/MM/DD (utilisé à l'export), mais DD-MM-YYYY est aussi supporté
400
		// ne retient que celles marquées "importables"
728
	    $matches = NULL;
401
		$colonnes_automatiques = array_filter($cols, '__anonyme_1');
729
	    // et on essaie d'être sympa et supporter aussi DD/MM/YYYY
402
 
730
	    if(preg_match(';^([0-3]?\d)/([01]\d)/([12]\d\d\d)$;', $date, $matches)) {
-
 
Line 731... Line 403...
731
		$date = $matches[3] . '/' . $matches[2] . '/' . $matches[1];
403
		// ne conserve que le nom long pour matcher avec la ligne XLS d'entête
732
	    }
-
 
733
	    $timestamp = strtotime($date);
-
 
734
	    if(! $timestamp || $timestamp > time() + 3600 * 24 * 1) { // une journée d'avance maxi autorisée (décallage horaire ?)
-
 
735
		if($date) trigger_error("ligne \"{$ref_ligne}\": Attention: date erronée ($date)", E_USER_NOTICE);
-
 
736
		return NULL;
-
 
737
	    }
-
 
738
	    return strftime("%Y-%m-%d 00:00:00", $timestamp);
-
 
739
	}
404
		array_walk($colonnes_automatiques, '__anonyme_2');
740
    }
-
 
741
 
405
 
742
    static function identReferentiel($referentiel, $ligne, $ref_ligne) {
-
 
743
	// SELECT DISTINCT nom_referentiel, COUNT(id_observation) AS count FROM cel_obs GROUP BY nom_referentiel ORDER BY count DESC;
406
		$colonnesID_a_exclure = array_intersect($entete, $colonnes_automatiques);
744
	if(strpos($referentiel, 'bdtfx') !== FALSE) return 'bdtfx'; //:v1.01';
407
 
745
	if(strpos($referentiel, 'bdtxa') !== FALSE) return 'bdtxa'; //:v1.00';
-
 
746
	//if(strpos($referentiel, 'bdnff') !== FALSE) return 'bdnff'; //:4.02';
-
 
747
	if(strpos($referentiel, 'bdnff') !== FALSE) return 'bdtfx';
-
 
748
	if(strpos($referentiel, 'isfan') !== FALSE) return 'isfan'; //:v1.00';
-
 
749
	if(strpos($referentiel, 'apd') !== FALSE) return 'apd'; //:v1.00';
-
 
750
	if(strpos($referentiel, 'autre') !== FALSE) return 'autre';
-
 
751
 
-
 
752
	if($referentiel && isset($ligne[C_NOM_SEL]) && $ligne[C_NOM_SEL]) {
-
 
753
	    trigger_error("ligne \"{$ref_ligne}\": Attention: référentiel \"{$referentiel}\" inconnu", E_USER_NOTICE);
-
 
754
	    return 'autre';
-
 
755
	}
408
		if ($numeric_keys) {
756
 
409
			return $colonnesID_non_reconnues + $colonnesID_a_exclure;
757
	// pas de référentiel ou pas de NOM_SEL: NULL
-
 
758
	return NULL;
-
 
759
	/* TODO: cf story,
410
		}
760
	   En cas de NULL faire une seconde passe de détection à partir du nom saisi
-
 
761
	   + accepter les n° de version */
-
 
762
    }
-
 
763
 
411
		return array_merge($colonnesID_non_reconnues, $colonnesID_a_exclure);
764
    static function traiterLonLat($lon = NULL, $lat = NULL, $referentiel = 'bdtfx', $ref_ligne) {
412
	}
765
	// en CSV ces valeurs sont des string, avec séparateur en français (","; cf défauts dans ExportXLS)
413
 
766
	if($lon && is_string($lon)) $lon = str_replace(',', '.', $lon);
414
	static function chargerCorrespondancesIdOrdre($cel, $lignes) {
767
	if($lat && is_string($lat)) $lat = str_replace(',', '.', $lat);
-
 
768
 
-
 
769
	// sprintf applique une précision à 5 décimale (comme le ferait MySQL)
-
 
770
	// tout en uniformisant le format de séparateur des décimales (le ".")
-
 
771
	if($lon && is_numeric($lon) && $lon >= -180 && $lon <= 180) return sprintf('%.5F', $lon);
-
 
772
	if($lat && is_numeric($lat) && $lat >= -90 && $lat <= 90) return sprintf('%.5F', $lat);
-
 
773
 
-
 
774
	if($lon || $lat) {
-
 
775
	    trigger_error("ligne \"{$ref_ligne}\": " .
-
 
776
			  "Attention: longitude ou latitude erronée",
415
		$ordresObs = array();
777
			  E_USER_NOTICE);
416
		foreach ($lignes as &$ligne) {
778
	}
-
 
779
	return NULL;
417
			$ordresObs[] = $ligne['ordre'];
780
 
418
		}
781
	/* limite france métropole si bdtfx ? ou bdtxa ? ...
-
 
782
	   NON!
419
		$ordresObsConcat = implode(',', $ordresObs);
783
	   Un taxon d'un référentiel donné peut être théoriquement observé n'importe où sur le globe.
-
 
784
	   Il n'y a pas lieu d'effectuer des restriction ici.
-
 
785
	   Cependant des erreurs fréquentes (0,0 ou lon/lat inversées) peuvent être détectés ici.
420
		$idUtilisateurP = Cel::db()->proteger($cel->id_utilisateur);
786
	   TODO */
421
		$requete = 'SELECT id_observation, ordre '.
787
	$bbox = self::getReferentielBBox($referentiel);
-
 
788
	if(!$bbox) return NULL;
-
 
789
 
-
 
790
	if($lon) {
-
 
791
	    if($lon < $bbox['EST'] && $lon > $bbox['OUEST']) return is_numeric($lon) ? $lon : NULL;
-
 
792
	    else return NULL;
-
 
793
	}
-
 
794
	if($lat) {
-
 
795
	    if($lat < $bbox['NORD'] && $lat > $bbox['SUD']) return is_numeric($lat) ? $lat : NULL;
-
 
796
	    return NULL;
-
 
797
	}
-
 
798
    }
-
 
799
 
-
 
800
    /*
-
 
801
      TODO: s'affranchir du webservice pour la détermination du nom scientifique en s'appuyant sur cel_references,
-
 
802
      pour des questions de performances
-
 
803
    */
-
 
804
    static function traiterEspece($ligne, Array &$espece, &$referentiel, $taxon_info_webservice) {
-
 
805
	if(empty($ligne[C_NOM_SEL])) {
-
 
806
	    // TODO: nous ne déclarons pas "Numéro nomenclatural" comme colonne importable
-
 
807
	    // Nous ne pouvons donc pas tenter d'être sympa sur la détermination par num_nom
-
 
808
	    /* if(!empty($ligne[C_NOM_SEL_NN]) && $referentiel != Cel::$fallback_referentiel)
-
 
809
	       $ligne[C_NOM_SEL] = $referentiel . ':nn:' . $ligne[C_NOM_SEL_NN];
-
 
Line 810... Line -...
810
	       else */
-
 
811
	    return;
-
 
812
	}
422
			'FROM cel_obs '.
-
 
423
			"WHERE ordre IN ($ordresObsConcat) ".
-
 
424
			"AND ce_utilisateur = $idUtilisateurP ".
-
 
425
			' -- '.__FILE__.':'.__LINE__;
-
 
426
		$resultats = Cel::db()->requeter($requete);
-
 
427
		$ordresIds = array();
-
 
428
		foreach ($resultats as &$infos) {
-
 
429
			$ordresIds[$infos['ordre']] = $infos['id_observation'];
-
 
430
		}
-
 
431
		return $ordresIds;
-
 
432
	}
813
 
433
 
-
 
434
	/*
-
 
435
	 * charge un groupe de lignes
-
 
436
	 */
-
 
437
	static function chargerLignes($cel, $lignes, $colonnes_statiques, &$dernier_ordre) {
-
 
438
		$enregistrement = NULL;
-
 
439
		$enregistrements = array();
-
 
440
		$toutes_images = array();
814
	// nom_sel reste toujours celui de l'utilisateur
441
		$tous_mots_cle = array();
-
 
442
		$tous_champs_etendus = array();
-
 
443
 
-
 
444
		foreach ($lignes as $ligne) {
-
 
445
			// dans le cas de fgetcsv, on peut avoir des false additionnel (cf do/while l. 279)
-
 
446
			if ($ligne === false) {
-
 
447
				continue;
-
 
448
			}
-
 
449
 
815
	$espece[C_NOM_SEL] = trim($ligne[C_NOM_SEL]);
450
			// on a besoin des NULL pour éviter des notice d'index indéfini
816
 
451
			if (! array_filter($ligne, '__anonyme_3')) {
-
 
452
				continue;
-
 
453
			}
-
 
454
 
-
 
455
			if ($enregistrement = self::chargerLigne($ligne, $dernier_ordre, $cel)) {
-
 
456
				// $enregistrements[] = array_merge($colonnes_statiques, $enregistrement);
-
 
457
				$enregistrements[] = $enregistrement;
-
 
458
				$pos = count($enregistrements) - 1;
-
 
459
				$last = &$enregistrements[$pos];
-
 
460
 
-
 
461
				if (isset($enregistrement['_images'])) {
817
	// XXX/attention, nous ne devrions pas accepter un référentiel absent !
462
					// ne dépend pas de cel_obs, et seront insérées *après* les enregistrements
-
 
463
					// mais nous ne voulons pas nous priver de faire des INSERT multiples pour autant
-
 
464
					$toutes_images[] = array(
-
 
465
						'images' => $last['_images'],
-
 
466
						'obs_pos' => $pos);
-
 
467
					// ce champ n'a pas à faire partie de l'insertion dans cel_obs,
-
 
468
					// mais est utile pour la liaison avec les images
-
 
469
					unset($last['_images']);
-
 
470
				}
-
 
471
 
-
 
472
				if (isset($enregistrement['_mots_cle'])) {
-
 
473
					// ne dépend pas de cel_obs, et seront insérés *après* les enregistrements
-
 
474
					// mais nous ne voulons pas nous priver de faire des INSERT multiples pour autant
-
 
475
					$tous_mots_cle[] = array(
818
	if(!$referentiel) $referentiel = 'bdtfx';
476
						'mots_cle' => $last['_mots_cle'],
-
 
477
						'obs_pos' => $pos);
-
 
478
					unset($last['_mots_cle']);
-
 
479
				}
Line -... Line 480...
-
 
480
 
-
 
481
				if (isset($enregistrement['_champs_etendus'])) {
-
 
482
					$tous_champs_etendus[] = array(
-
 
483
						'champs_etendus' => $last['_champs_etendus'],
-
 
484
						'ordre' => $dernier_ordre);
-
 
485
					unset($last['_champs_etendus']);
-
 
486
				}
-
 
487
				$dernier_ordre++;
-
 
488
			}
-
 
489
		}
-
 
490
		return array($enregistrements, $toutes_images, $tous_mots_cle, $tous_champs_etendus);
-
 
491
	}
-
 
492
 
-
 
493
	static function trierColonnes(&$enregistrements) {
-
 
494
		foreach ($enregistrements as &$enregistrement) {
-
 
495
			$enregistrement = self::sortArrayByArray($enregistrement, self::$ordre_BDD);
-
 
496
		}
-
 
497
	}
-
 
498
 
819
	$taxon_info_webservice->setReferentiel($referentiel);
499
	static function stockerMotsCle($enregistrements, $tous_mots_cle, $lastid) {
-
 
500
		$c = 0;
-
 
501
		// debug: var_dump($tous_mots_cle);die;
-
 
502
		foreach ($tous_mots_cle as $v) {
-
 
503
			$c += count($v['mots_cle']['to_insert']);
-
 
504
		}
-
 
505
		return $c;
-
 
506
	}
-
 
507
 
-
 
508
	static function stockerImages($enregistrements, $toutes_images, $ordre_ids) {
-
 
509
		$idsImagesObs = array();
-
 
510
		foreach ($toutes_images as $images_pour_obs) {
-
 
511
			$obs = $enregistrements[$images_pour_obs['obs_pos']];
-
 
512
			$id_obs = $ordre_ids[$obs['ordre']]; // id réel de l'observation correspondant à l'ordre
-
 
513
			foreach ($images_pour_obs['images'] as $image) {
-
 
514
				$idsImagesObs[] = sprintf('(%d,%d, NOW())', $image['id_image'], $id_obs);
-
 
515
			}
-
 
516
		}
-
 
517
 
-
 
518
		if ($idsImagesObs) {
-
 
519
			$clauseValues = implode(', ', $idsImagesObs);
-
 
520
			// Utilisation de INSET pour faire des UPDATE multiples en une seule requête
-
 
521
			$requete = 'INSERT INTO cel_images (id_image, ce_observation, date_liaison) '.
820
	$ascii = iconv('UTF-8', 'ASCII//TRANSLIT', $ligne[C_NOM_SEL]);
522
				"VALUES $clauseValues ".
Line 866... Line 755...
866
	// nom_sel est remplacé, mais seulement si un motif spécial à été utilisé (bdtfx:nn:4567)
755
			return sprintf('%.5F', $lon);
867
	if($taxon_info_webservice->is_notation_spe) {
756
		}
868
	    $espece[C_NOM_SEL] = $determ->nom_sci;
-
 
869
	}
757
		if ($lat && is_numeric($lat) && $lat >= -90 && $lat <= 90) {
870
 
-
 
871
	// écrasement des numéros (nomenclatural, taxonomique) saisis...
-
 
872
	$espece[C_NOM_SEL_NN] = $determ->id;
758
			return sprintf('%.5F', $lat);
-
 
759
		}
-
 
760
 
-
 
761
		if ($lon || $lat) {
-
 
762
			trigger_error("ligne \"{$ref_ligne}\": " .
Line -... Line 763...
-
 
763
				  "Attention: longitude ou latitude erronée",
-
 
764
				  E_USER_NOTICE);
Line -... Line 765...
-
 
765
		}
-
 
766
		return NULL;
873
	$espece[C_NOM_RET] = RechercheInfosTaxonBeta::supprimerBiblio($determ->nom_retenu_complet);
767
	}
874
	$espece[C_NOM_RET_NN] = $determ->{"nom_retenu.id"};
768
 
-
 
769
	/*
-
 
770
	  TODO: s'affranchir du webservice pour la détermination du nom scientifique en s'appuyant sur cel_references,
-
 
771
	  pour des questions de performances
-
 
772
	*/
Line 875... Line -...
875
	$espece[C_NT] = $determ->num_taxonomique;
-
 
876
	$espece[C_FAMILLE] = $determ->famille;
773
	static function traiterEspece($ligne, Array &$espece, &$referentiel, $taxon_info_webservice) {
877
	return;
-
 
878
	// et des info complémentaires
-
 
879
 
774
		if (empty($ligne[C_NOM_SEL])) {
880
	/*
-
 
881
	// GET /service:eflore:0.1/bdtfx/noms/31468?retour.champs=nom_sci,auteur,id,nom_retenu_complet,nom_retenu.id,num_taxonomique,famille
-
 
882
	/home/raphael/eflore/projets/services/modules/0.1/bdtfx/Noms.php:280
775
			return;
883
	SELECT  *, nom_sci   FROM bdtfx_v1_01	 WHERE num_nom = '31468' 
-
 
884
	SELECT nom_sci, num_nom_retenu, nom_sci_html, auteur, annee, biblio_origine FROM bdtfx_v1_01 WHERE num_nom = 31468
776
		}
885
	SELECT nom_sci, num_nom_retenu, nom_sci_html, auteur, annee, biblio_origine FROM bdtfx_v1_01 WHERE num_nom = 86535
-
 
886
	*/
-
 
887
 
-
 
888
 
-
 
889
	//var_dump($complement, $espece);die;
-
 
890
    }
777
 
891
 
-
 
892
    static function detectFromNom($nom) {
-
 
893
	$r = Cel::db()->requeter(sprintf("SELECT num_nom, num_tax_sup FROM bdtfx_v1_01 WHERE (nom_sci LIKE '%s') ".
-
 
894
					 "ORDER BY nom_sci ASC LIMIT 0, 1",
-
 
895
					 Cel::db()->proteger($nom)));
-
 
896
	if($r) return $r;
-
 
897
 
-
 
898
	Cel::db()->requeter(sprintf("SELECT num_nom, num_tax_sup FROM bdtfx_v1_01 WHERE (nom_sci LIKE '%s' OR nom LIKE '%s') ".
-
 
899
				    "ORDER BY nom_sci ASC LIMIT 0, 1",
-
 
900
				    Cel::db()->proteger($nom),
-
 
901
				    Cel::db()->proteger(str_replace(' ', '% ', $nom))));
-
 
902
	return $r;
778
		// nom_sel reste toujours celui de l'utilisateur
903
    }
779
		$espece[C_NOM_SEL] = trim($ligne[C_NOM_SEL]);
904
 
-
 
905
 
780
 
906
    /*
781
		// XXX/attention, nous ne devrions pas accepter un référentiel absent !
907
     * TODO: analyse rigoureuse:
782
		if (!$referentiel) {
908
     * == Identifiant Commune
783
			$referentiel = 'bdtfx';
909
     * - INSEE-C:\d{5}
-
 
910
     * - \d{5}
-
 
911
     * - \d{2}
-
 
912
     * == Commune
-
 
913
     * - \w+ (\d{2})
-
 
914
     * - \w+ (\d{5})
784
		}
915
     * - \w+
-
 
916
     *
-
 
917
     */
-
 
918
    static function traiterLocalisation($ligne, Array &$localisation) {
-
 
919
	if(empty($ligne[C_ZONE_GEO])) $ligne[C_ZONE_GEO] = NULL;
785
		$taxon_info_webservice->setReferentiel($referentiel);
920
	if(empty($ligne[C_CE_ZONE_GEO])) $ligne[C_CE_ZONE_GEO] = NULL;
-
 
921
 
-
 
Line -... Line 786...
-
 
786
		$ascii = iconv('UTF-8', 'ASCII//TRANSLIT', $ligne[C_NOM_SEL]);
922
	$identifiant_commune = trim($ligne[C_ZONE_GEO]);
787
 
923
	if(!$identifiant_commune) {
788
		$determ = $taxon_info_webservice->rechercherInfosSurTexteCodeOuNumTax(trim($ligne[C_NOM_SEL]));
924
	    $departement = trim($ligne[C_CE_ZONE_GEO]);
-
 
925
 
789
 
Line 926... Line 790...
926
	    if(strpos($departement, "INSEE-C:", 0) === 0) {
790
		// note: rechercherInfosSurTexteCodeOuNumTax peut ne retourner qu'une seule clef "nom_sel"
927
		$localisation[C_CE_ZONE_GEO] = trim($ligne[C_CE_ZONE_GEO]);
-
 
928
		if(array_key_exists($localisation[C_CE_ZONE_GEO], self::$cache['geo'])) {
791
		if (! $determ) {
929
		    $localisation[C_ZONE_GEO] = self::$cache['geo'][$localisation[C_CE_ZONE_GEO]];
792
			// on supprime les noms retenus et renvoi tel quel
-
 
793
			// on réutilise les define pour les noms d'indexes, tant qu'à faire
-
 
794
			// XXX; tout à NULL sauf C_NOM_SEL ci-dessus ?
930
		}
795
			$espece[C_NOM_SEL_NN] = @$ligne[C_NOM_SEL_NN];
931
		else {
796
			$espece[C_NOM_RET] = @$ligne[C_NOM_RET];
-
 
797
			$espece[C_NOM_RET_NN] = @$ligne[C_NOM_RET_NN];
932
		    $nom = Cel::db()->requeter(sprintf("SELECT nom FROM cel_zones_geo WHERE code = %s LIMIT 1",
798
			$espece[C_NT] = @$ligne[C_NT];
933
						       self::quoteNonNull(substr($localisation[C_CE_ZONE_GEO], strlen("INSEE-C:")))));
799
			$espece[C_FAMILLE] = @$ligne[C_FAMILLE];
-
 
800
 
-
 
801
			return;
-
 
802
		}
-
 
803
 
-
 
804
		// succès de la détection, mais résultat partiel
934
		    if($nom) $localisation[C_ZONE_GEO] = $nom[0]['nom'];
805
		if (!isset($determ->id)) {
935
		    self::$cache['geo'][$localisation[C_CE_ZONE_GEO]] = @$nom[0]['nom'];
806
			$determ = $taxon_info_webservice->effectuerRequeteInfosComplementairesSurNumNom($determ->{"nom_retenu.id"});
936
		}
807
		}
937
		return;
808
 
-
 
809
		// ne devrait jamais arriver !
938
	    }
810
		if (!$determ) {
939
 
811
			die("erreur critique: " . __FILE__ . ':' . __LINE__);
940
	    if(!is_numeric($departement)) {
812
		}
941
		$localisation[C_CE_ZONE_GEO] = $ligne[C_CE_ZONE_GEO];
813
 
942
		return;
-
 
943
	    }
-
 
944
 
-
 
945
	    $cache_attempted = FALSE;
-
 
946
	    if(array_key_exists($departement, self::$cache['geo'])) {
-
 
947
		$cache_attempted = TRUE;
-
 
948
		if(self::$cache['geo'][$departement][0] && self::$cache['geo'][$departement][1]) {
-
 
949
		    $localisation[C_ZONE_GEO] = self::$cache['geo'][$departement][0];
-
 
950
		    $localisation[C_CE_ZONE_GEO] = self::$cache['geo'][$departement][1];
-
 
951
		    return;
-
 
952
		}
-
 
953
	    }
-
 
954
 
-
 
955
	    if(! $cache_attempted && ($resultat_commune = Cel::db()->requeter(sprintf("SELECT DISTINCT nom, CONCAT('INSEE-C:', code) AS code FROM cel_zones_geo WHERE code = %s LIMIT 1",
-
 
956
										      self::quoteNonNull($departement)))) ) {
-
 
957
		$localisation[C_ZONE_GEO] = $resultat_commune[0]['nom'];
-
 
958
		$localisation[C_CE_ZONE_GEO] = $resultat_commune[0]['code'];
-
 
959
		self::$cache['geo'][$departement] = array($resultat_commune[0]['nom'], $resultat_commune[0]['code']);
-
 
960
		return;
-
 
961
	    }
-
 
962
	    ;
-
 
963
	    // if(strlen($departement) == 4) $departement = "INSEE-C:0" . $departement;
-
 
964
	    // if(strlen($departement) == 5) $departement = "INSEE-C:" . $departement;
-
 
965
	    // if(strlen($departement) <= 9) return "INSEE-C:0" . $departement; // ? ... TODO
-
 
966
 
-
 
967
	    $departement = trim($departement); // TODO
-
 
968
 
-
 
969
	    $localisation[C_CE_ZONE_GEO] = $ligne[C_CE_ZONE_GEO];
-
 
970
	    return;
-
 
971
	}
-
 
972
 
-
 
973
 
-
 
974
	$select = "SELECT DISTINCT nom, code FROM cel_zones_geo";
-
 
975
	
-
 
976
	if (preg_match('/(.+) \((\d{1,5})\)/', $identifiant_commune, $elements)) {
-
 
977
	    // commune + departement : montpellier (34)
-
 
978
	    $nom_commune=$elements[1];
-
 
979
	    $code_commune=$elements[2];
-
 
980
	    if(strlen($code_commune) <= 2) {
-
 
981
		$requete = sprintf("%s WHERE nom = %s AND code LIKE %s",
-
 
982
				   $select, self::quoteNonNull($nom_commune),
-
 
983
				   self::quoteNonNull($code_commune.'%'));
-
 
984
	    }
-
 
985
	    else {
-
 
986
		$requete = sprintf("%s WHERE nom = %s AND code = %d",
-
 
987
				   $select, self::quoteNonNull($nom_commune),
-
 
988
				   $code_commune);
-
 
989
	    }
-
 
990
	}
-
 
991
	elseif (preg_match('/^(\d+|(2[ab]\d+))$/i', $identifiant_commune, $elements)) {
-
 
992
	    // Code insee seul  
-
 
993
	    $code_insee_commune=$elements[1];
-
 
994
	    $requete = sprintf("%s WHERE code = %s", $select, self::quoteNonNull($code_insee_commune));
-
 
995
	}
-
 
996
	else {
-
 
997
	    // Commune seule (le departement sera recupere dans la colonne departement si elle est presente)
-
 
998
	    // on prend le risque ici de retourner une mauvaise Commune
-
 
999
	    $nom_commune = str_replace(" ", "%", iconv('UTF-8', 'ASCII//TRANSLIT', $identifiant_commune));
-
 
1000
	    $requete = sprintf("%s WHERE nom LIKE %s", $select, self::quoteNonNull($nom_commune.'%'));
-
 
1001
	}
-
 
1002
 
-
 
1003
 
-
 
1004
	if(array_key_exists($identifiant_commune, self::$cache['geo'])) {
-
 
1005
	    $resultat_commune = self::$cache['geo'][$identifiant_commune];
-
 
1006
	}
-
 
1007
	else {
-
 
1008
	    $resultat_commune = Cel::db()->requeter($requete);
-
 
1009
	    self::$cache['geo'][$identifiant_commune] = $resultat_commune;
-
 
1010
	}
-
 
1011
	// TODO: levenstein sort ?
-
 
1012
	// TODO: count résultat !
-
 
1013
 
-
 
1014
	// cas de la commune introuvable dans le référentiel
-
 
1015
	// réinitialisation aux valeurs du fichier XLS
-
 
1016
	if(! $resultat_commune) {
-
 
1017
	    $localisation[C_ZONE_GEO] = trim($ligne[C_ZONE_GEO]);
-
 
1018
	    $localisation[C_CE_ZONE_GEO] = trim($ligne[C_CE_ZONE_GEO]);
-
 
1019
	} else {
-
 
1020
	    $localisation[C_ZONE_GEO] = $resultat_commune[0]['nom'];
-
 
1021
	    $localisation[C_CE_ZONE_GEO] = "INSEE-C:" . $resultat_commune[0]['code'];
-
 
1022
	    return;
-
 
1023
	}
-
 
1024
 
-
 
1025
	$departement = &$localisation[C_CE_ZONE_GEO];
-
 
1026
 
-
 
1027
	if(strpos($departement, "INSEE-C:", 0) === 0) {
-
 
1028
	    $localisation[C_ZONE_GEO] = $localisation[C_ZONE_GEO];
-
 
1029
	    $localisation[C_CE_ZONE_GEO] = $localisation[C_CE_ZONE_GEO];
-
 
1030
	}
-
 
1031
 
-
 
1032
 
-
 
1033
	if(!is_numeric($departement)) {
-
 
1034
	    $localisation[C_ZONE_GEO] = $localisation[C_ZONE_GEO];
-
 
1035
	    $localisation[C_CE_ZONE_GEO] = $localisation[C_CE_ZONE_GEO];
-
 
1036
	}
-
 
1037
 
-
 
1038
	if(strlen($departement) == 4) $departement = "INSEE-C:0" . $departement;
-
 
1039
	if(strlen($departement) == 5) $departement = "INSEE-C:" . $departement;
-
 
1040
	// if(strlen($departement) <= 9) return "INSEE-C:0" . $departement; // ? ... TODO
-
 
1041
 
-
 
1042
	$departement = trim($departement); // TODO
-
 
1043
 
-
 
1044
	$localisation[C_ZONE_GEO] = $localisation[C_ZONE_GEO];
-
 
1045
	$localisation[C_CE_ZONE_GEO] = $localisation[C_CE_ZONE_GEO];
-
 
1046
    }
-
 
1047
    
-
 
1048
    
-
 
1049
    public static function stockerChampsEtendus($champs_etendus, $ordre_ids, $config) {   	
-
 
1050
    	// singleton du pauvre mais l'export est suffisamment inefficace pour s'en priver
-
 
1051
    	self::$gestion_champs_etendus = self::$gestion_champs_etendus == null ?
-
 
1052
    										new GestionChampsEtendus($config, 'obs') : 
-
 
1053
    										self::$gestion_champs_etendus;
-
 
1054
    	
-
 
1055
    	$champs_etendus_obs = array();
-
 
1056
    	foreach($champs_etendus as $champ_etendu_a_obs) {
-
 
1057
    		$id_obs = $ordre_ids[$champ_etendu_a_obs['ordre']]; // id réel de l'observation correspondant à l'ordre
-
 
1058
    		foreach($champ_etendu_a_obs['champs_etendus'] as $label => $champ) {
-
 
1059
				// XXX: insère t'on des valeurs vides ?
-
 
1060
    			$valeur = $champ;    			
-
 
1061
    			$cle = $label;
-
 
1062
    			
-
 
1063
    			if (!empty($cle) && !empty($valeur)) {
-
 
1064
    				$champ_etendu_a_inserer = new ChampEtendu();
-
 
1065
    				$champ_etendu_a_inserer->id = $id_obs;
-
 
1066
    				$champ_etendu_a_inserer->cle = $cle;
-
 
1067
    				$champ_etendu_a_inserer->valeur = $valeur;
-
 
1068
    			
-
 
1069
    				$champs_etendus_obs[] = $champ_etendu_a_inserer;
-
 
1070
    			}
-
 
1071
    		}
814
		// un schéma <ref>:(nt|nn):<num> (ie: bdtfx:nt:8503) a été passé
1072
    	}
-
 
1073
    	
-
 
1074
    	$insertion = self::$gestion_champs_etendus->ajouterParLots($champs_etendus_obs);
-
 
1075
    	//TODO: que faire si l'insertion des champs étendus échoue ?
-
 
1076
    	return count($champs_etendus_obs);	 
-
 
1077
    }
-
 
1078
 
-
 
1079
    /* HELPERS */
-
 
1080
 
-
 
1081
    // http://stackoverflow.com/questions/348410/sort-an-array-based-on-another-array
-
 
1082
    // XXX; utilisé aussi (temporairement ?) par FormateurGroupeColonne.
-
 
1083
    static function sortArrayByArray($array, $orderArray) {
-
 
1084
	$ordered = array();
-
 
1085
	foreach($orderArray as $key) {
-
 
1086
	    if(array_key_exists($key, $array)) {
-
 
1087
		$ordered[$key] = $array[$key];
-
 
1088
		unset($array[$key]);
-
 
1089
	    }
-
 
1090
	}
-
 
1091
	return $ordered + $array;
-
 
1092
    }
-
 
1093
 
-
 
1094
    // retourne une BBox [N,S,E,O) pour un référentiel donné
-
 
1095
    static function getReferentielBBox($referentiel) {
-
 
1096
	if($referentiel == 'bdtfx') return Array(
-
 
1097
	    'NORD' => 51.2, // Dunkerque
-
 
1098
	    'SUD' => 41.3, // Bonifacio
-
 
1099
	    'EST' => 9.7, // Corse
-
 
1100
	    'OUEST' => -5.2); // Ouessan
-
 
1101
	return FALSE;
-
 
1102
    }
-
 
1103
 
-
 
1104
    // ces valeurs ne sont pas inséré via les placeholders du PDO::preparedStatement
-
 
1105
    // et doivent donc être échappées correctement.
-
 
1106
    public function initialiser_colonnes_statiques() {
-
 
1107
	$this->colonnes_statiques = array_merge($this->colonnes_statiques,
-
 
1108
						Array(
-
 
1109
						    "ce_utilisateur" => self::quoteNonNull($this->id_utilisateur), // peut-être un hash ou un id
-
 
1110
						    "prenom_utilisateur" => self::quoteNonNull($this->utilisateur['prenom']),
-
 
Line 1111... Line 815...
1111
						    "nom_utilisateur" => self::quoteNonNull($this->utilisateur['nom']),
815
		// dans ce cas on met à jour le référentiel avec celui passé dans le champ espèce
1112
						    "courriel_utilisateur" => self::quoteNonNull($this->utilisateur['courriel']),
-
 
1113
						));
-
 
1114
 
-
 
1115
    }
-
 
1116
 
-
 
1117
    static function initialiser_pdo_ordered_statements($colonnes_statiques) {
-
 
1118
	return Array(
-
 
1119
	    // insert_ligne_pattern_ordre
-
 
1120
	    sprintf('INSERT INTO cel_obs (%s, %s) VALUES',
816
		if (isset($determ->ref)) {
1121
		    implode(', ', array_keys($colonnes_statiques)),
-
 
1122
		    implode(', ', array_diff(self::$ordre_BDD, array_keys($colonnes_statiques)))),
-
 
1123
 
817
			$referentiel = $determ->ref;
1124
	    // insert_ligne_pattern_ordre
818
		}
1125
	    sprintf('(%s, %s ?)',
-
 
1126
		    implode(', ', $colonnes_statiques),
-
 
1127
		    str_repeat('?, ', count(self::$ordre_BDD) - count($colonnes_statiques) - 1))
-
 
1128
	);
819
 
1129
    }
820
		// succès de la détection
1130
 
821
		// nom_sel est remplacé, mais seulement si un motif spécial à été utilisé (bdtfx:nn:4567)
1131
    static function initialiser_pdo_statements($colonnes_statiques) {
-
 
Line -... Line 822...
-
 
822
		if ($taxon_info_webservice->is_notation_spe) {
-
 
823
			$espece[C_NOM_SEL] = $determ->nom_sci;
-
 
824
		}
-
 
825
 
-
 
826
		// écrasement des numéros (nomenclatural, taxonomique) saisis...
-
 
827
		$espece[C_NOM_SEL_NN] = $determ->id;
-
 
828
		$espece[C_NOM_RET] = RechercheInfosTaxonBeta::supprimerBiblio($determ->nom_retenu_complet);
-
 
829
		$espece[C_NOM_RET_NN] = $determ->{"nom_retenu.id"};
-
 
830
		$espece[C_NT] = $determ->num_taxonomique;
-
 
831
		$espece[C_FAMILLE] = $determ->famille;
-
 
832
		return;
-
 
833
	}
-
 
834
 
-
 
835
	static function detectFromNom($nom) {
-
 
836
		$r = Cel::db()->requeter(sprintf("SELECT num_nom, num_tax_sup FROM bdtfx_v1_01 WHERE (nom_sci LIKE '%s') ".
-
 
837
			"ORDER BY nom_sci ASC LIMIT 0, 1",
-
 
838
			Cel::db()->proteger($nom)));
-
 
839
		if ($r) {
-
 
840
			return $r;
-
 
841
		}
-
 
842
 
-
 
843
		Cel::db()->requeter(sprintf("SELECT num_nom, num_tax_sup FROM bdtfx_v1_01 WHERE (nom_sci LIKE '%s' OR nom LIKE '%s') ".
-
 
844
			"ORDER BY nom_sci ASC LIMIT 0, 1",
-
 
845
			Cel::db()->proteger($nom),
-
 
846
			Cel::db()->proteger(str_replace(' ', '% ', $nom))));
-
 
847
		return $r;
-
 
848
	}
-
 
849
 
-
 
850
	static function traiterLocalisation($ligne, Array &$localisation) {
-
 
851
		if (empty($ligne[C_ZONE_GEO])) {
-
 
852
			$ligne[C_ZONE_GEO] = NULL;
-
 
853
		}
-
 
854
		if (empty($ligne[C_CE_ZONE_GEO])) {
-
 
855
			$ligne[C_CE_ZONE_GEO] = NULL;
-
 
856
		}
-
 
857
 
-
 
858
		$identifiant_commune = trim($ligne[C_ZONE_GEO]);
-
 
859
		if (!$identifiant_commune) {
-
 
860
			$departement = trim($ligne[C_CE_ZONE_GEO]);
-
 
861
 
-
 
862
			if (strpos($departement, 'INSEE-C:', 0) === 0) {
-
 
863
				$localisation[C_CE_ZONE_GEO] = trim($ligne[C_CE_ZONE_GEO]);
-
 
864
				if (array_key_exists($localisation[C_CE_ZONE_GEO], self::$cache['geo'])) {
-
 
865
					$localisation[C_ZONE_GEO] = self::$cache['geo'][$localisation[C_CE_ZONE_GEO]];
-
 
866
				} else {
-
 
867
					$nom = Cel::db()->requeter(sprintf("SELECT nom FROM cel_zones_geo WHERE code = %s LIMIT 1",
-
 
868
						self::quoteNonNull(substr($localisation[C_CE_ZONE_GEO], strlen("INSEE-C:")))));
-
 
869
					if ($nom) {
-
 
870
						$localisation[C_ZONE_GEO] = $nom[0]['nom'];
-
 
871
					}
-
 
872
					self::$cache['geo'][$localisation[C_CE_ZONE_GEO]] = @$nom[0]['nom'];
-
 
873
				}
-
 
874
				return;
-
 
875
			}
-
 
876
 
-
 
877
			if (!is_numeric($departement)) {
-
 
878
				$localisation[C_CE_ZONE_GEO] = $ligne[C_CE_ZONE_GEO];
-
 
879
				return;
-
 
880
			}
-
 
881
 
-
 
882
			$cache_attempted = FALSE;
-
 
883
			if(array_key_exists($departement, self::$cache['geo'])) {
-
 
884
				$cache_attempted = TRUE;
-
 
885
				if (self::$cache['geo'][$departement][0] && self::$cache['geo'][$departement][1]) {
-
 
886
					$localisation[C_ZONE_GEO] = self::$cache['geo'][$departement][0];
-
 
887
					$localisation[C_CE_ZONE_GEO] = self::$cache['geo'][$departement][1];
-
 
888
					return;
-
 
889
				}
-
 
890
			}
-
 
891
 
-
 
892
			$requete = "SELECT DISTINCT nom, CONCAT('INSEE-C:', code) AS code ".
-
 
893
				'FROM cel_zones_geo '.
-
 
894
				'WHERE code = %s '.
-
 
895
				'LIMIT 1 '.
-
 
896
				' -- '.__FILE__.':'.__LINE__;
-
 
897
			$resultat_commune = Cel::db()->requeter(sprintf($requete, self::quoteNonNull($departement)));
-
 
898
			if (! $cache_attempted && $resultat_commune) {
-
 
899
				$localisation[C_ZONE_GEO] = $resultat_commune[0]['nom'];
-
 
900
				$localisation[C_CE_ZONE_GEO] = $resultat_commune[0]['code'];
-
 
901
				self::$cache['geo'][$departement] = array($resultat_commune[0]['nom'], $resultat_commune[0]['code']);
-
 
902
				return;
-
 
903
			}
-
 
904
			$localisation[C_CE_ZONE_GEO] = $ligne[C_CE_ZONE_GEO];
-
 
905
			return;
-
 
906
		}
-
 
907
 
-
 
908
		$select = "SELECT DISTINCT nom, code FROM cel_zones_geo";
-
 
909
 
-
 
910
		if (preg_match('/(.+) \((\d{1,5})\)/', $identifiant_commune, $elements)) {
-
 
911
			// commune + departement : montpellier (34)
-
 
912
			$nom_commune=$elements[1];
-
 
913
			$code_commune=$elements[2];
-
 
914
			if (strlen($code_commune) <= 2) {
-
 
915
				$requete = sprintf("%s WHERE nom = %s AND code LIKE %s",
-
 
916
					$select, self::quoteNonNull($nom_commune),
-
 
917
					self::quoteNonNull($code_commune.'%'));
-
 
918
			} else {
-
 
919
				$requete = sprintf("%s WHERE nom = %s AND code = %d",
-
 
920
					$select, self::quoteNonNull($nom_commune),
-
 
921
					$code_commune);
-
 
922
			}
-
 
923
		} elseif (preg_match('/^(\d+|(2[ab]\d+))$/i', $identifiant_commune, $elements)) {
-
 
924
			// Code insee seul
-
 
925
			$code_insee_commune=$elements[1];
-
 
926
			$requete = sprintf("%s WHERE code = %s", $select, self::quoteNonNull($code_insee_commune));
-
 
927
		} else {
-
 
928
			// Commune seule (le departement sera recupere dans la colonne departement si elle est presente)
-
 
929
			// on prend le risque ici de retourner une mauvaise Commune
-
 
930
			$nom_commune = str_replace(" ", "%", iconv('UTF-8', 'ASCII//TRANSLIT', $identifiant_commune));
-
 
931
			$requete = sprintf("%s WHERE nom LIKE %s", $select, self::quoteNonNull($nom_commune.'%'));
-
 
932
		}
-
 
933
 
-
 
934
		if (array_key_exists($identifiant_commune, self::$cache['geo'])) {
-
 
935
			$resultat_commune = self::$cache['geo'][$identifiant_commune];
-
 
936
		} else {
-
 
937
			$resultat_commune = Cel::db()->requeter($requete);
-
 
938
			self::$cache['geo'][$identifiant_commune] = $resultat_commune;
-
 
939
		}
-
 
940
 
-
 
941
		// cas de la commune introuvable dans le référentiel
-
 
942
		// réinitialisation aux valeurs du fichier XLS
-
 
943
		if (! $resultat_commune) {
-
 
944
			$localisation[C_ZONE_GEO] = trim($ligne[C_ZONE_GEO]);
-
 
945
			$localisation[C_CE_ZONE_GEO] = trim($ligne[C_CE_ZONE_GEO]);
-
 
946
		} else {
-
 
947
			$localisation[C_ZONE_GEO] = $resultat_commune[0]['nom'];
-
 
948
			$localisation[C_CE_ZONE_GEO] = "INSEE-C:" . $resultat_commune[0]['code'];
-
 
949
			return;
-
 
950
		}
-
 
951
 
-
 
952
		$departement =& $localisation[C_CE_ZONE_GEO];
-
 
953
 
-
 
954
		if (strpos($departement, "INSEE-C:", 0) === 0) {
-
 
955
			$localisation[C_ZONE_GEO] = $localisation[C_ZONE_GEO];
-
 
956
			$localisation[C_CE_ZONE_GEO] = $localisation[C_CE_ZONE_GEO];
-
 
957
		}
-
 
958
 
-
 
959
		if (!is_numeric($departement)) {
-
 
960
			$localisation[C_ZONE_GEO] = $localisation[C_ZONE_GEO];
-
 
961
			$localisation[C_CE_ZONE_GEO] = $localisation[C_CE_ZONE_GEO];
-
 
962
		}
-
 
963
 
-
 
964
		if (strlen($departement) == 4) {
-
 
965
			$departement = "INSEE-C:0$departement";
-
 
966
		}
-
 
967
		if (strlen($departement) == 5) {
-
 
968
			$departement = "INSEE-C:$departement";
-
 
969
		}
-
 
970
		$departement = trim($departement);
-
 
971
 
-
 
972
		$localisation[C_ZONE_GEO] = $localisation[C_ZONE_GEO];
-
 
973
		$localisation[C_CE_ZONE_GEO] = $localisation[C_CE_ZONE_GEO];
-
 
974
	}
-
 
975
 
-
 
976
	public static function stockerChampsEtendus($champs_etendus, $ordre_ids, $config) {
-
 
977
		// singleton du pauvre mais l'export est suffisamment inefficace pour s'en priver
-
 
978
		self::$gestion_champs_etendus = self::$gestion_champs_etendus == null ?
-
 
979
			new GestionChampsEtendus($config, 'obs') :
-
 
980
			self::$gestion_champs_etendus;
-
 
981
 
-
 
982
		$champs_etendus_obs = array();
-
 
983
		foreach ($champs_etendus as $champ_etendu_a_obs) {
-
 
984
			$id_obs = $ordre_ids[$champ_etendu_a_obs['ordre']]; // id réel de l'observation correspondant à l'ordre
-
 
985
			foreach ($champ_etendu_a_obs['champs_etendus'] as $label => $champ) {
-
 
986
				// XXX: insère t'on des valeurs vides ?
-
 
987
				$valeur = $champ;
-
 
988
				$cle = $label;
-
 
989
 
-
 
990
				if (!empty($cle) && !empty($valeur)) {
-
 
991
					$champ_etendu_a_inserer = new ChampEtendu();
-
 
992
					$champ_etendu_a_inserer->id = $id_obs;
-
 
993
					$champ_etendu_a_inserer->cle = $cle;
-
 
994
					$champ_etendu_a_inserer->valeur = $valeur;
-
 
995
 
-
 
996
					$champs_etendus_obs[] = $champ_etendu_a_inserer;
-
 
997
				}
-
 
998
			}
-
 
999
		}
-
 
1000
 
-
 
1001
		self::$gestion_champs_etendus->ajouterParLots($champs_etendus_obs);
-
 
1002
		//TODO: que faire si l'insertion des champs étendus échoue ?
-
 
1003
		return count($champs_etendus_obs);
-
 
1004
	}
-
 
1005
 
-
 
1006
	/* HELPERS */
-
 
1007
 
-
 
1008
	// http://stackoverflow.com/questions/348410/sort-an-array-based-on-another-array
-
 
1009
	// XXX; utilisé aussi (temporairement ?) par FormateurGroupeColonne.
-
 
1010
	static function sortArrayByArray($array, $orderArray) {
-
 
1011
		$ordered = array();
-
 
1012
		foreach($orderArray as $key) {
-
 
1013
			if (array_key_exists($key, $array)) {
-
 
1014
				$ordered[$key] = $array[$key];
-
 
1015
				unset($array[$key]);
-
 
1016
			}
-
 
1017
		}
-
 
1018
		return $ordered + $array;
-
 
1019
	}
-
 
1020
 
-
 
1021
	// retourne une BBox [N,S,E,O) pour un référentiel donné
-
 
1022
	static function getReferentielBBox($referentiel) {
-
 
1023
		if ($referentiel == 'bdtfx') {
-
 
1024
			return Array(
-
 
1025
				'NORD' => 51.2, // Dunkerque
-
 
1026
				'SUD' => 41.3, // Bonifacio
-
 
1027
				'EST' => 9.7, // Corse
-
 
1028
				'OUEST' => -5.2); // Ouessan
-
 
1029
		}
-
 
1030
		return FALSE;
-
 
1031
	}
-
 
1032
 
-
 
1033
	// ces valeurs ne sont pas inséré via les placeholders du PDO::preparedStatement
-
 
1034
	// et doivent donc être échappées correctement.
-
 
1035
	public function initialiser_colonnes_statiques() {
-
 
1036
		$this->colonnes_statiques = array_merge($this->colonnes_statiques,
-
 
1037
			array(
-
 
1038
				'ce_utilisateur' => self::quoteNonNull($this->id_utilisateur), // peut-être un hash ou un id
-
 
1039
				'prenom_utilisateur' => self::quoteNonNull($this->utilisateur['prenom']),
-
 
1040
				'nom_utilisateur' => self::quoteNonNull($this->utilisateur['nom']),
-
 
1041
				'courriel_utilisateur' => self::quoteNonNull($this->utilisateur['courriel']),
-
 
1042
			));
-
 
1043
	}
-
 
1044
 
-
 
1045
	static function initialiser_pdo_ordered_statements($colonnes_statiques) {
-
 
1046
		return Array(
-
 
1047
			// insert_ligne_pattern_ordre
-
 
1048
			sprintf('INSERT INTO cel_obs (%s, %s) VALUES',
-
 
1049
				implode(', ', array_keys($colonnes_statiques)),
-
 
1050
				implode(', ', array_diff(self::$ordre_BDD, array_keys($colonnes_statiques)))),
-
 
1051
 
-
 
1052
			// insert_ligne_pattern_ordre
-
 
1053
			sprintf('(%s, %s ?)',
-
 
1054
				implode(', ', $colonnes_statiques),
-
 
1055
				str_repeat('?, ', count(self::$ordre_BDD) - count($colonnes_statiques) - 1))
-
 
1056
		);
-
 
1057
	}
-
 
1058
 
-
 
1059
	static function initialiser_pdo_statements($colonnes_statiques) {
1132
	return Array( 
1060
		return Array(
1133
	    // insert_prefix
1061
			// insert_prefix
1134
	    sprintf('INSERT INTO cel_obs (%s) VALUES ',
1062
			sprintf('INSERT INTO cel_obs (%s) VALUES ',
1135
		    implode(', ', self::$ordre_BDD)),
1063
				implode(', ', self::$ordre_BDD)),
-
 
1064
 
-
 
1065
 
1136
 
1066
			// insert_ligne_pattern, cf: self::$insert_ligne_pattern
1137
 
1067
			'(' .
1138
	    // insert_ligne_pattern, cf: self::$insert_ligne_pattern
1068
			// 3) créé une chaîne de liste de champ à inséré en DB
1139
	    '(' .
1069
			implode(', ', array_values(
1140
	    // 3) créé une chaîne de liste de champ à inséré en DB
1070
			// 2) garde les valeurs fixes (de $colonnes_statiques),
1141
	    implode(', ', array_values(
1071
			// mais remplace les NULL par des "?"
1142
		// 2) garde les valeurs fixes (de $colonnes_statiques),
1072
			array_map('__anonyme_5',
1143
		// mais remplace les NULL par des "?"
1073
				  // 1) créé un tableau genre (nom_sel_nn => NULL) depuis self::$ordre_BDD
1144
		array_map('__anonyme_5',
1074
				  // et écrase certaines valeurs avec $colonnes_statiques (initilisé avec les données utilisateur)
1145
			  // 1) créé un tableau genre (nom_sel_nn => NULL) depuis self::$ordre_BDD
1075
				  array_merge(array_map('__anonyme_6', array_flip(self::$ordre_BDD)), $colonnes_statiques
1146
			  // et écrase certaines valeurs avec $colonnes_statiques (initilisé avec les données utilisateur)
1076
				  )))) .