Subversion Repositories eFlore/Applications.cel

Rev

Rev 3077 | Only display areas with differences | Ignore whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 3077 Rev 3474
1
<?php
1
<?php
2
// declare(encoding='UTF-8');
2
// declare(encoding='UTF-8');
3
/**
3
/**
4
 * 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
5
 *
5
 *
6
 * 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
7
 * FormateurGroupeColonne::nomEnsembleVersListeColonnes() préfixés par C_  cf: detectionEntete()
7
 * FormateurGroupeColonne::nomEnsembleVersListeColonnes() préfixés par C_  cf: detectionEntete()
8
 *
8
 *
9
 * Exemple d'un test:
9
 * Exemple d'un test:
10
 * $ GET "/jrest/ExportXLS/22506?format=csv&range=*&limite=13" \
10
 * $ GET "/jrest/ExportXLS/22506?format=csv&range=*&limite=13" \
11
 *   | curl -F "upload=@-" -F utilisateur=22506 "/jrest/ImportXLS"
11
 *   | curl -F "upload=@-" -F utilisateur=22506 "/jrest/ImportXLS"
12
 * # 13 observations importées
12
 * # 13 observations importées
13
 * + cf MySQL general_log = 1
13
 * + cf MySQL general_log = 1
14
 *
14
 *
15
 * @internal   Mininum PHP version : 5.2
15
 * @internal   Mininum PHP version : 5.2
16
 * @category   CEL
16
 * @category   CEL
17
 * @package    Services
17
 * @package    Services
18
 * @subpackage Observations
18
 * @subpackage Observations
19
 * @version    0.1
19
 * @version    0.1
20
 * @author     Mathias CHOUET <mathias@tela-botanica.org>
20
 * @author     Mathias CHOUET <mathias@tela-botanica.org>
21
 * @author     Raphaël DROZ <raphael@tela-botanica.org>
21
 * @author     Raphaël DROZ <raphael@tela-botanica.org>
22
 * @author     Jean-Pascal MILCENT <jpm@tela-botanica.org>
22
 * @author     Jean-Pascal MILCENT <jpm@tela-botanica.org>
23
 * @author     Aurelien PERONNET <aurelien@tela-botanica.org>
23
 * @author     Aurelien PERONNET <aurelien@tela-botanica.org>
24
 * @license    GPL v3 <http://www.gnu.org/licenses/gpl.txt>
24
 * @license    GPL v3 <http://www.gnu.org/licenses/gpl.txt>
25
 * @license    CECILL v2 <http://www.cecill.info/licences/Licence_CeCILL_V2-en.txt>
25
 * @license    CECILL v2 <http://www.cecill.info/licences/Licence_CeCILL_V2-en.txt>
26
 * @copyright  1999-2014 Tela Botanica <accueil@tela-botanica.org>
26
 * @copyright  1999-2014 Tela Botanica <accueil@tela-botanica.org>
27
 */
27
 */
28
set_include_path(get_include_path() . PATH_SEPARATOR . dirname(dirname(realpath(__FILE__))) . '/lib');
28
set_include_path(get_include_path() . PATH_SEPARATOR . dirname(dirname(realpath(__FILE__))) . '/lib');
29
// TERM
29
// TERM
30
error_reporting(-1);
30
error_reporting(-1);
31
ini_set('html_errors', 0);
31
ini_set('html_errors', 0);
32
ini_set('xdebug.cli_color', 2);
32
ini_set('xdebug.cli_color', 2);
33
date_default_timezone_set('Europe/Paris');
33
date_default_timezone_set('Europe/Paris');
34
require_once 'lib/PHPExcel/Classes/PHPExcel.php';
34
require_once 'lib/PHPExcel/Classes/PHPExcel.php';
35
require_once 'bibliotheque/GestionMotsClesChemin.php';
35
require_once 'bibliotheque/GestionMotsClesChemin.php';
36
 
36
 
37
// nombre d'INSERT à cumuler par requête SQL
37
// nombre d'INSERT à cumuler par requête SQL
38
// (= nombre de lignes XLS à bufferiser)
38
// (= nombre de lignes XLS à bufferiser)
39
//define('NB_LIRE_LIGNE_SIMUL', 30);
39
//define('NB_LIRE_LIGNE_SIMUL', 30);
40
define('NB_LIRE_LIGNE_SIMUL', 5);
40
define('NB_LIRE_LIGNE_SIMUL', 5);
41
 
41
 
42
// en cas d'import d'un fichier CSV, utilise fgetcsv() plutôt
42
// en cas d'import d'un fichier CSV, utilise fgetcsv() plutôt
43
// que PHPExcel ce qui se traduit par un gain de performances très substanciel
43
// que PHPExcel ce qui se traduit par un gain de performances très substanciel
44
define('QUICK_CSV_IMPORT', TRUE);
44
define('QUICK_CSV_IMPORT', TRUE);
45
 
45
 
46
// 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)
47
// see traiterDateObs()
47
// see traiterDateObs()
48
// define("MIN_DATES_DIFF", 25569);
48
// define("MIN_DATES_DIFF", 25569);
49
class MyReadFilter implements PHPExcel_Reader_IReadFilter {
49
class MyReadFilter implements PHPExcel_Reader_IReadFilter {
50
	// exclusion de colonnes
50
	// exclusion de colonnes
51
	public $exclues = array();
51
	public $exclues = array();
52
 
52
 
53
	// lecture par morceaux
53
	// lecture par morceaux
54
	public $ligne_debut = 0;
54
	public $ligne_debut = 0;
55
	public $ligne_fin = 0;
55
	public $ligne_fin = 0;
56
 
56
 
57
	public static $gestion_mots_cles = null;
57
	public static $gestion_mots_cles = null;
58
 
58
 
59
	public function __construct() {}
59
	public function __construct() {}
60
 
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
 
66
	public function readCell($colonne, $ligne, $worksheetName = '') {
66
	public function readCell($colonne, $ligne, $worksheetName = '') {
67
		if(@$this->exclues[$colonne]) return false;
67
		if(@$this->exclues[$colonne]) return false;
68
		// si des n° de morceaux ont été initialisés, on filtre...
68
		// si des n° de morceaux ont été initialisés, on filtre...
69
		if($this->ligne_debut && ($ligne < $this->ligne_debut || $ligne >= $this->ligne_fin)) return false;
69
		if($this->ligne_debut && ($ligne < $this->ligne_debut || $ligne >= $this->ligne_fin)) return false;
70
		return true;
70
		return true;
71
	}
71
	}
72
}
72
}
73
 
73
 
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); };
76
function __anonyme_3($cell) { return !is_null($cell); };
77
function __anonyme_5($item) { return is_null($item) ? '?' : $item; }
77
function __anonyme_5($item) { return is_null($item) ? '?' : $item; }
78
function __anonyme_6() { return NULL; }
78
function __anonyme_6() { return NULL; }
79
 
79
 
80
class ImportXLS extends Cel  {
80
class ImportXLS extends Cel  {
81
	static function __anonyme_4(&$item, $key) { $item = self::quoteNonNull(trim($item)); }
81
	static function __anonyme_4(&$item, $key) { $item = self::quoteNonNull(trim($item)); }
82
 
82
 
83
	static $ordre_BDD = Array(
83
	static $ordre_BDD = Array(
84
		'ce_utilisateur',
84
		'ce_utilisateur',
85
		'prenom_utilisateur',
85
		'prenom_utilisateur',
86
		'nom_utilisateur',
86
		'nom_utilisateur',
87
		'courriel_utilisateur',
87
		'courriel_utilisateur',
88
		'ordre',
88
		'ordre',
89
		'nom_sel',
89
		'nom_sel',
90
		'nom_sel_nn',
90
		'nom_sel_nn',
91
		'nom_ret',
91
		'nom_ret',
92
		'nom_ret_nn',
92
		'nom_ret_nn',
93
		'nt',
93
		'nt',
94
		'famille',
94
		'famille',
95
		'nom_referentiel',
95
		'nom_referentiel',
96
		'pays',	
96
		'pays',	
97
		'zone_geo',
97
		'zone_geo',
98
		'ce_zone_geo',
98
		'ce_zone_geo',
99
		'date_observation',
99
		'date_observation',
100
		'lieudit',
100
		'lieudit',
101
		'station',
101
		'station',
102
		'milieu',
102
		'milieu',
103
		'mots_cles_texte',
103
		'mots_cles_texte',
104
		'commentaire',
104
		'commentaire',
105
		'transmission',
105
		'transmission',
106
		'date_creation',
106
		'date_creation',
107
		'date_modification',
107
		'date_modification',
108
		'date_transmission',
108
		'date_transmission',
109
		'latitude',
109
		'latitude',
110
		'longitude',
110
		'longitude',
111
		'altitude',
111
		'altitude',
112
		'abondance',
112
		'abondance',
113
		'certitude',
113
		'certitude',
114
		'phenologie',
114
		'phenologie',
115
		'code_insee_calcule'
115
		'code_insee_calcule'
116
	);
116
	);
117
 
117
 
118
	// cf: initialiser_pdo_ordered_statements()
118
	// cf: initialiser_pdo_ordered_statements()
119
	// eg: "INSERT INTO cel_obs (ce_utilisateur, ..., phenologie, code_insee_calcule) VALUES"
119
	// eg: "INSERT INTO cel_obs (ce_utilisateur, ..., phenologie, code_insee_calcule) VALUES"
120
	// colonnes statiques d'abord, les autres ensuite, dans l'ordre de $ordre_BDD
120
	// colonnes statiques d'abord, les autres ensuite, dans l'ordre de $ordre_BDD
121
	static $insert_prefix_ordre;
121
	static $insert_prefix_ordre;
122
	// eg: "(<id>, <prenom>, <nom>, <email>, now(), now(), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
122
	// eg: "(<id>, <prenom>, <nom>, <email>, now(), now(), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
123
	// dont le nombre de placeholder dépend du nombre de colonnes non-statiques
123
	// dont le nombre de placeholder dépend du nombre de colonnes non-statiques
124
	// colonnes statiques d'abord, les autres ensuite, dans l'ordre de $ordre_BDD
124
	// colonnes statiques d'abord, les autres ensuite, dans l'ordre de $ordre_BDD
125
	static $insert_ligne_pattern_ordre;
125
	static $insert_ligne_pattern_ordre;
126
 
126
 
127
	// seconde (meilleure) possibilité
127
	// seconde (meilleure) possibilité
128
	// cf: initialiser_pdo_statements()
128
	// cf: initialiser_pdo_statements()
129
	// eg: "INSERT INTO cel_obs (ce_utilisateur, ..., date_creation, ...phenologie, code_insee_calcule) VALUES"
129
	// eg: "INSERT INTO cel_obs (ce_utilisateur, ..., date_creation, ...phenologie, code_insee_calcule) VALUES"
130
	static $insert_prefix;
130
	static $insert_prefix;
131
	// eg: "(<id>, <prenom>, <nom>, <email>, ?, ?, ?, now(), now(), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
131
	// eg: "(<id>, <prenom>, <nom>, <email>, ?, ?, ?, now(), now(), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
132
	// dont le nombre de placeholder dépend du nombre de colonnes non-statiques
132
	// dont le nombre de placeholder dépend du nombre de colonnes non-statiques
133
	static $insert_ligne_pattern;
133
	static $insert_ligne_pattern;
134
 
134
 
135
	/*
135
	/*
136
	 Ces colonnes:
136
	 Ces colonnes:
137
	 - sont propres à l'ensemble des enregistrements uploadés
137
	 - sont propres à l'ensemble des enregistrements uploadés
138
	 - sont indépendantes du numéro de lignes
138
	 - sont indépendantes du numéro de lignes
139
	 - n'ont pas de valeur par défaut dans la structure de la table
139
	 - n'ont pas de valeur par défaut dans la structure de la table
140
	 - nécessitent une initialisation dans le cadre de l'upload
140
	 - nécessitent une initialisation dans le cadre de l'upload
141
	 initialiser_colonnes_statiques() y merge les données d'identification utilisateur
141
	 initialiser_colonnes_statiques() y merge les données d'identification utilisateur
142
	*/
142
	*/
143
	public $colonnes_statiques = array(
143
	public $colonnes_statiques = array(
144
		'ce_utilisateur' => NULL,
144
		'ce_utilisateur' => NULL,
145
		'prenom_utilisateur' => NULL,
145
		'prenom_utilisateur' => NULL,
146
		'nom_utilisateur' => NULL,
146
		'nom_utilisateur' => NULL,
147
		'courriel_utilisateur' => NULL,
147
		'courriel_utilisateur' => NULL,
148
 
148
 
149
		// fixes (fonction SQL)
149
		// fixes (fonction SQL)
150
		// XXX future: mais pourraient varier dans le futur si la mise-à-jour
150
		// XXX future: mais pourraient varier dans le futur si la mise-à-jour
151
		// d'observation est implémentée
151
		// d'observation est implémentée
152
		'date_creation' => 'NOW()',
152
		'date_creation' => 'NOW()',
153
		'date_modification' => 'NOW()',
153
		'date_modification' => 'NOW()',
154
	);
154
	);
155
 
155
 
156
	public static $prefixe_colonnes_etendues = 'ext:';
156
	public static $prefixe_colonnes_etendues = 'ext:';
157
	public static $indexes_colonnes_etendues = Array();
157
	public static $indexes_colonnes_etendues = Array();
158
	public static $gestion_champs_etendus = null;
158
	public static $gestion_champs_etendus = null;
159
 
159
 
160
	public $id_utilisateur = NULL;
160
	public $id_utilisateur = NULL;
161
 
161
 
162
	// erreurs d'import
162
	// erreurs d'import
163
	public $bilan = Array();
163
	public $bilan = Array();
164
 
164
 
165
	// cache (pour traiterLocalisation() pour l'instant)
165
	// cache (pour traiterLocalisation() pour l'instant)
166
	static $cache = Array('geo' => array());
166
	static $cache = Array('geo' => array());
167
 
167
 
168
	public function createElement($pairs) {
168
	public function createElement($pairs) {
169
		if (!isset($pairs['utilisateur']) || trim($pairs['utilisateur']) == '') {
169
		if (!isset($pairs['utilisateur']) || trim($pairs['utilisateur']) == '') {
170
			exit('0');
170
			exit('0');
171
		}
171
		}
172
 
172
 
173
		$id_utilisateur = intval($pairs['utilisateur']);
173
		$id_utilisateur = intval($pairs['utilisateur']);
174
		$this->id_utilisateur = $id_utilisateur; // pour traiterImage();
174
		$this->id_utilisateur = $id_utilisateur; // pour traiterImage();
175
 
175
 
176
		if (!isset($_SESSION)) {
176
		if (!isset($_SESSION)) {
177
			session_start();
177
			session_start();
178
		}
178
		}
179
		$this->controleUtilisateur($id_utilisateur);
179
		$this->controleUtilisateur($id_utilisateur);
180
 
180
 
181
		$this->utilisateur = $this->getInfosComplementairesUtilisateur($id_utilisateur);
181
		$this->utilisateur = $this->getInfosComplementairesUtilisateur($id_utilisateur);
182
		$this->initialiser_colonnes_statiques($id_utilisateur);
182
		$this->initialiser_colonnes_statiques($id_utilisateur);
183
		list(self::$insert_prefix, self::$insert_ligne_pattern) = $this->initialiser_pdo_statements($this->colonnes_statiques);
183
		list(self::$insert_prefix, self::$insert_ligne_pattern) = $this->initialiser_pdo_statements($this->colonnes_statiques);
184
 
184
 
185
		$infos_fichier = array_pop($_FILES);
185
		$infos_fichier = array_pop($_FILES);
186
 
186
 
187
		// renomme le fichier pour lui ajouter son extension initiale, ce qui
187
		// renomme le fichier pour lui ajouter son extension initiale, ce qui
188
		// permet (une sorte) d'autodétection du format.
188
		// permet (une sorte) d'autodétection du format.
189
		$fichier = $infos_fichier['tmp_name'];
189
		$fichier = $infos_fichier['tmp_name'];
190
		$extension = pathinfo($infos_fichier['name'], PATHINFO_EXTENSION);
190
		$extension = pathinfo($infos_fichier['name'], PATHINFO_EXTENSION);
191
		if ( (strlen($extension) == 3 || strlen($extension) == 4) && (@rename($fichier, "$fichier.$extension"))) {
191
		if ( (strlen($extension) == 3 || strlen($extension) == 4) && (@rename($fichier, "$fichier.$extension"))) {
192
			$fichier = "$fichier.$extension";
192
			$fichier = "$fichier.$extension";
193
		}
193
		}
194
 
194
 
195
		$objReader = PHPExcel_IOFactory::createReaderForFile($fichier);
195
		$objReader = PHPExcel_IOFactory::createReaderForFile($fichier);
196
		// TODO: check if compatible with toArray(<1>,<2>,TRUE,<4>)
196
		// TODO: check if compatible with toArray(<1>,<2>,TRUE,<4>)
197
		$objReader->setReadDataOnly(true);
197
		$objReader->setReadDataOnly(true);
198
 
198
 
199
		// TODO: is_a obsolete entre 5.0 et 5.3, retirer le @ à terme
199
		// TODO: is_a obsolete entre 5.0 et 5.3, retirer le @ à terme
200
		$IS_CSV = @is_a($objReader, 'PHPExcel_Reader_CSV') && QUICK_CSV_IMPORT;
200
		$IS_CSV = @is_a($objReader, 'PHPExcel_Reader_CSV') && QUICK_CSV_IMPORT;
201
		// en cas d'usage de fgetcsv, testons que nous pouvons compter les lignes
201
		// en cas d'usage de fgetcsv, testons que nous pouvons compter les lignes
202
		if ($IS_CSV) {
202
		if ($IS_CSV) {
203
			$nb_lignes = intval(exec("wc -l $fichier"));
203
			$nb_lignes = intval(exec("wc -l $fichier"));
204
		}
204
		}
205
		// et, le cas échéant, fallback sur PHPExcel à nouveau. La raison de ce test ici est
205
		// et, le cas échéant, fallback sur PHPExcel à nouveau. La raison de ce test ici est
206
		// l'instabilité du serveur (safe_mode, safe_mode_exec_dir, symlink vers binaires pour exec(), ... multiples points-of-failure)
206
		// l'instabilité du serveur (safe_mode, safe_mode_exec_dir, symlink vers binaires pour exec(), ... multiples points-of-failure)
207
		if ($IS_CSV && !$nb_lignes) {
207
		if ($IS_CSV && !$nb_lignes) {
208
			$IS_CSV = FALSE;
208
			$IS_CSV = FALSE;
209
		}
209
		}
210
 
210
 
211
		if ($IS_CSV) {
211
		if ($IS_CSV) {
212
			$objReader->setDelimiter(',')
212
			$objReader->setDelimiter(',')
213
				->setEnclosure('"')
213
				->setEnclosure('"')
214
				->setLineEnding("\n")
214
				->setLineEnding("\n")
215
				->setSheetIndex(0);
215
				->setSheetIndex(0);
216
		}
216
		}
217
 
217
 
218
		// on ne conserve que l'en-tête
218
		// on ne conserve que l'en-tête
219
		$filtre = new MyReadFilter();
219
		$filtre = new MyReadFilter();
220
		$filtre->def_interval(1, 2);
220
		$filtre->def_interval(1, 2);
221
		$objReader->setReadFilter($filtre);
221
		$objReader->setReadFilter($filtre);
222
 
222
 
223
		$objPHPExcel = $objReader->load($fichier);
223
		$objPHPExcel = $objReader->load($fichier);
224
		$obj_infos = $objReader->listWorksheetInfo($fichier);
224
		$obj_infos = $objReader->listWorksheetInfo($fichier);
225
 
225
 
226
		if ($IS_CSV) {
226
		if ($IS_CSV) {
227
			// $nb_lignes est déjà défini ci-dessus
227
			// $nb_lignes est déjà défini ci-dessus
228
			$csvFileHandler = fopen($fichier, 'r');
228
			$csvFileHandler = fopen($fichier, 'r');
229
			// nous utilisons la valeur de retour dans un but informatif de l'utilisateur à la
229
			// nous utilisons la valeur de retour dans un but informatif de l'utilisateur à la
230
			// fin de l'import, *mais aussi* dans un array_diff_key() ci-dessous car bien que dans le
230
			// fin de l'import, *mais aussi* dans un array_diff_key() ci-dessous car bien que dans le
231
			// fond le "parser" fgetcsv() n'ait pas d'intérêt à connaître les colonnes à ignorer,
231
			// fond le "parser" fgetcsv() n'ait pas d'intérêt à connaître les colonnes à ignorer,
232
			// il se trouve que celles-ci peuvent interférer sur des fonctions comme traiterEspece()
232
			// il se trouve que celles-ci peuvent interférer sur des fonctions comme traiterEspece()
233
			// cf test "ref-nom-num.test.php" pour lequel l'élément C_NOM_SEL vaudrait 3 et $ligne serait array(3 => -42)
233
			// cf test "ref-nom-num.test.php" pour lequel l'élément C_NOM_SEL vaudrait 3 et $ligne serait array(3 => -42)
234
			$filtre->exclues = self::detectionEntete(fgetcsv($csvFileHandler), TRUE);
234
			$filtre->exclues = self::detectionEntete(fgetcsv($csvFileHandler), TRUE);
235
		} else {
235
		} else {
236
			// XXX: indépendant du readFilter ?
236
			// XXX: indépendant du readFilter ?
237
			$nb_lignes = $obj_infos[0]['totalRows'];
237
			$nb_lignes = $obj_infos[0]['totalRows'];
238
			$donnees = $objPHPExcel->getActiveSheet()->toArray(NULL, FALSE, TRUE, TRUE);
238
			$donnees = $objPHPExcel->getActiveSheet()->toArray(NULL, FALSE, TRUE, TRUE);
239
			$filtre->exclues = self::detectionEntete($donnees[1]);
239
			$filtre->exclues = self::detectionEntete($donnees[1]);
240
		}
240
		}
241
 
241
 
242
		$obs_ajouts = 0;
242
		$obs_ajouts = 0;
243
		$obs_maj = 0;
243
		$obs_maj = 0;
244
		$nb_images_ajoutees = 0;
244
		$nb_images_ajoutees = 0;
245
		$nb_mots_cle_ajoutes = 0;
245
		$nb_mots_cle_ajoutes = 0;
246
		$nb_champs_etendus_inseres = 0;
246
		$nb_champs_etendus_inseres = 0;
247
 
247
 
248
		$dernier_ordre = Cel::db()->requeter("SELECT MAX(ordre) AS ordre FROM cel_obs WHERE ce_utilisateur = $id_utilisateur");
248
		$dernier_ordre = Cel::db()->requeter("SELECT MAX(ordre) AS ordre FROM cel_obs WHERE ce_utilisateur = $id_utilisateur");
249
		$dernier_ordre = intval($dernier_ordre[0]['ordre']) + 1;
249
		$dernier_ordre = intval($dernier_ordre[0]['ordre']) + 1;
250
		if (! $dernier_ordre) {
250
		if (! $dernier_ordre) {
251
			$dernier_ordre = 0;
251
			$dernier_ordre = 0;
252
		}
252
		}
253
 
253
 
254
		// on catch to les trigger_error(E_USER_NOTICE);
254
		// on catch to les trigger_error(E_USER_NOTICE);
255
		set_error_handler(array($this, 'erreurs_stock'), E_USER_NOTICE);
255
		set_error_handler(array($this, 'erreurs_stock'), E_USER_NOTICE);
256
		$this->taxon_info_webservice = new RechercheInfosTaxonBeta($this->config, NULL);
256
		$this->taxon_info_webservice = new RechercheInfosTaxonBeta($this->config, NULL);
257
 
257
 
258
		// lecture par morceaux (chunks), NB_LIRE_LIGNE_SIMUL lignes à fois
258
		// lecture par morceaux (chunks), NB_LIRE_LIGNE_SIMUL lignes à fois
259
		// pour aboutir des requêtes SQL d'insert groupés.
259
		// pour aboutir des requêtes SQL d'insert groupés.
260
		for ($ligne = 2; $ligne < $nb_lignes + NB_LIRE_LIGNE_SIMUL; $ligne += NB_LIRE_LIGNE_SIMUL) {
260
		for ($ligne = 2; $ligne < $nb_lignes + NB_LIRE_LIGNE_SIMUL; $ligne += NB_LIRE_LIGNE_SIMUL) {
261
			if (!$IS_CSV) {
261
			if (!$IS_CSV) {
262
				$filtre->def_interval($ligne, NB_LIRE_LIGNE_SIMUL);
262
				$filtre->def_interval($ligne, NB_LIRE_LIGNE_SIMUL);
263
				$objReader->setReadFilter($filtre);
263
				$objReader->setReadFilter($filtre);
264
 
264
 
265
				$objPHPExcel = $objReader->load($fichier)->getActiveSheet();
265
				$objPHPExcel = $objReader->load($fichier)->getActiveSheet();
266
 
266
 
267
				// set col typing
267
				// set col typing
268
				if (C_CE_ZONE_GEO != 'C_CE_ZONE_GEO') {
268
				if (C_CE_ZONE_GEO != 'C_CE_ZONE_GEO') {
269
					$objPHPExcel->getStyle(C_CE_ZONE_GEO . '2:' . C_CE_ZONE_GEO . $objPHPExcel->getHighestRow())->getNumberFormat()->setFormatCode('00000');
269
					$objPHPExcel->getStyle(C_CE_ZONE_GEO . '2:' . C_CE_ZONE_GEO . $objPHPExcel->getHighestRow())->getNumberFormat()->setFormatCode('00000');
270
				}
270
				}
271
				// TODO: set to string type
271
				// TODO: set to string type
272
				if (C_ZONE_GEO != 'C_ZONE_GEO') {
272
				if (C_ZONE_GEO != 'C_ZONE_GEO') {
273
					$objPHPExcel->getStyle(C_ZONE_GEO . '2:' . C_ZONE_GEO . $objPHPExcel->getHighestRow())->getNumberFormat()->setFormatCode('00000');
273
					$objPHPExcel->getStyle(C_ZONE_GEO . '2:' . C_ZONE_GEO . $objPHPExcel->getHighestRow())->getNumberFormat()->setFormatCode('00000');
274
				}
274
				}
275
				$donnees = $objPHPExcel->toArray(NULL, FALSE, TRUE, TRUE);
275
				$donnees = $objPHPExcel->toArray(NULL, FALSE, TRUE, TRUE);
276
			} else {
276
			} else {
277
				$i = NB_LIRE_LIGNE_SIMUL;
277
				$i = NB_LIRE_LIGNE_SIMUL;
278
				$donnees = array();
278
				$donnees = array();
279
				while ($i--) {
279
				while ($i--) {
280
					$tab = fgetcsv($csvFileHandler);
280
					$tab = fgetcsv($csvFileHandler);
281
					if (!$tab) {
281
					if (!$tab) {
282
						continue;
282
						continue;
283
					}
283
					}
284
					$donnees[] = array_diff_key($tab, $filtre->exclues);
284
					$donnees[] = array_diff_key($tab, $filtre->exclues);
285
				}
285
				}
286
			}
286
			}
287
 
287
 
288
			list($enregistrements, $images, $mots_cle, $champs_etendus) = self::chargerLignes($this, $donnees, $this->colonnes_statiques, $dernier_ordre, $config);
288
			list($enregistrements, $images, $mots_cle, $champs_etendus) = self::chargerLignes($this, $donnees, $this->colonnes_statiques, $dernier_ordre, $config);
289
			if (! $enregistrements) {
289
			if (! $enregistrements) {
290
				break;
290
				break;
291
			}
291
			}
292
 
292
 
293
			self::trierColonnes($enregistrements);
293
			self::trierColonnes($enregistrements);
294
			// normalement: NB_LIRE_LIGNE_SIMUL, sauf si une enregistrement ne semble pas valide
294
			// normalement: NB_LIRE_LIGNE_SIMUL, sauf si une enregistrement ne semble pas valide
295
			// ou bien lors du dernier chunk
295
			// ou bien lors du dernier chunk
296
 
296
 
297
			$nb_rec = count($enregistrements);
297
			$nb_rec = count($enregistrements);
298
			$sql_pattern = self::$insert_prefix.
298
			$sql_pattern = self::$insert_prefix.
299
				str_repeat(self::$insert_ligne_pattern.', ', $nb_rec - 1).
299
				str_repeat(self::$insert_ligne_pattern.', ', $nb_rec - 1).
300
				self::$insert_ligne_pattern;
300
				self::$insert_ligne_pattern;
301
 
301
 
302
			Cel::db()->beginTransaction();
302
			Cel::db()->beginTransaction();
303
			$stmt = Cel::db()->prepare($sql_pattern);
303
			$stmt = Cel::db()->prepare($sql_pattern);
304
			$donnees = array();
304
			$donnees = array();
305
			foreach ($enregistrements as $e) {
305
			foreach ($enregistrements as $e) {
306
				$donnees = array_merge($donnees, array_values($e));
306
				$donnees = array_merge($donnees, array_values($e));
307
			}
307
			}
308
 
308
 
309
			$stmt->execute($donnees);
309
			$stmt->execute($donnees);
310
 
310
 
311
			# @TODO lier les mots-clées de l'arbre à l'obs nouvellement insérée, sinon seuls les mots-clés texte
311
			# @TODO lier les mots-clées de l'arbre à l'obs nouvellement insérée, sinon seuls les mots-clés texte
312
			# seront à jour, et ils seront écrasés à la prochaine modification de mots-clés pour cette obs...
312
			# seront à jour, et ils seront écrasés à la prochaine modification de mots-clés pour cette obs...
313
			# => nécessite d'insérer les obs une par une pour avoir le dernier id inséré
313
			# => nécessite d'insérer les obs une par une pour avoir le dernier id inséré
314
 
314
 
315
			$dernier_autoinc = Cel::db()->lastInsertId();
315
			$dernier_autoinc = Cel::db()->lastInsertId();
316
			Cel::db()->commit();
316
			Cel::db()->commit();
317
 
317
 
318
			if (! $dernier_autoinc) {
318
			if (! $dernier_autoinc) {
319
				trigger_error("l'insertion semble avoir échoué", E_USER_NOTICE);
319
				trigger_error("l'insertion semble avoir échoué", E_USER_NOTICE);
320
			}
320
			}
321
 
321
 
322
			$obs_ajouts += count($enregistrements);
322
			$obs_ajouts += count($enregistrements);
323
 
323
 
324
			$ordre_ids = self::chargerCorrespondancesIdOrdre($this, $enregistrements);
324
			$ordre_ids = self::chargerCorrespondancesIdOrdre($this, $enregistrements);
325
 
325
 
326
			$nb_images_ajoutees += self::stockerImages($enregistrements, $images, $ordre_ids);
326
			$nb_images_ajoutees += self::stockerImages($enregistrements, $images, $ordre_ids);
327
			$nb_mots_cle_ajoutes += self::stockerMotsCle($enregistrements, $mots_cle, $dernier_autoinc);
327
			$nb_mots_cle_ajoutes += self::stockerMotsCle($enregistrements, $mots_cle, $dernier_autoinc);
328
			$nb_champs_etendus_inseres += self::stockerChampsEtendus($champs_etendus, $ordre_ids, $this->config);
328
			$nb_champs_etendus_inseres += self::stockerChampsEtendus($champs_etendus, $ordre_ids, $this->config);
329
		}
329
		}
330
 
330
 
331
		restore_error_handler();
331
		restore_error_handler();
332
 
332
 
333
		// le cast en string des nombres permet d'unifier le parsing du retour
333
		// le cast en string des nombres permet d'unifier le parsing du retour
334
		// car il n'est destiné qu'à être affiché 
334
		// car il n'est destiné qu'à être affiché 
335
		$retour = array(
335
		$retour = array(
336
					'import_obs_ajoutees' => (string)$obs_ajouts,
336
					'import_obs_ajoutees' => (string)$obs_ajouts,
337
					'import_images_ajoutees' => (string)$nb_images_ajoutees,
337
					'import_images_ajoutees' => (string)$nb_images_ajoutees,
338
					'import_mots_cles_ajoutes' => (string)$nb_mots_cle_ajoutes,
338
					'import_mots_cles_ajoutes' => (string)$nb_mots_cle_ajoutes,
339
					'import_colonnes_non_traitees' => implode(', ', $filtre->exclues)
339
					'import_colonnes_non_traitees' => implode(', ', $filtre->exclues)
340
		);
340
		);
341
		// Ajout d'éventuelles erreurs
341
		// Ajout d'éventuelles erreurs
342
		if ($this->bilan) {
342
		if ($this->bilan) {
343
			$retour += array('import_erreurs' => implode("\n", $this->bilan) . "\n");
343
			$retour += array('import_erreurs' => implode("\n", $this->bilan) . "\n");
344
		}
344
		}
345
		// Dans le cas où le client ne sait pas lire le retour d'upload
345
		// Dans le cas où le client ne sait pas lire le retour d'upload
346
		// on stocke les stats en session pour les appeler plus tard
346
		// on stocke les stats en session pour les appeler plus tard
347
		// car ceci peut poser notamment problème pour les requêtes CORS
347
		// car ceci peut poser notamment problème pour les requêtes CORS
348
		$_SESSION['upload_stats'] = $retour;
348
		$_SESSION['upload_stats'] = $retour;
349
		// On envoie quand même les stats pour les clients qui savent ou peuvent 
349
		// On envoie quand même les stats pour les clients qui savent ou peuvent 
350
		// les lire directement après l'upload
350
		// les lire directement après l'upload
351
		$this->envoyerJson($retour);
351
		$this->envoyerJson($retour);
352
		die();
352
		die();
353
	}
353
	}
354
	
354
	
355
	public function getElement($uid) {
355
	public function getElement($uid) {
356
		if($uid[0] == "template") {
356
		if($uid[0] == "template") {
357
 
357
 
358
			$tpl_dir = dirname(__FILE__).DIRECTORY_SEPARATOR.'squelettes'.DIRECTORY_SEPARATOR;
358
			$tpl_dir = dirname(__FILE__).DIRECTORY_SEPARATOR.'squelettes'.DIRECTORY_SEPARATOR;
359
			$tpl = $tpl_dir.'modele_import.xls';
359
			$tpl = $tpl_dir.'modele_import.xls';
360
 
360
 
361
			$lecteur = PHPExcel_IOFactory::createReaderForFile($tpl);
361
			$lecteur = PHPExcel_IOFactory::createReaderForFile($tpl);
362
			$classeur_tpl = $lecteur->load($tpl);
362
			$classeur_tpl = $lecteur->load($tpl);
363
			$feuille_tpl = $classeur_tpl->getActiveSheet();
363
			$feuille_tpl = $classeur_tpl->getActiveSheet();
364
			
364
			
365
			// Détection de la dernière colonne pour connaitre la position d'ajout des champs étendus
365
			// Détection de la dernière colonne pour connaitre la position d'ajout des champs étendus
366
			// Si un groupe est demandé
366
			// Si un groupe est demandé
367
			$lettre_colonne_max = $feuille_tpl->getHighestColumn();
367
			$lettre_colonne_max = $feuille_tpl->getHighestColumn();
368
			$nb_colonne_max = PHPExcel_Cell::columnIndexFromString($lettre_colonne_max);		
368
			$nb_colonne_max = PHPExcel_Cell::columnIndexFromString($lettre_colonne_max);		
369
			$ligne = 1;
369
			$ligne = 1;
370
			$nb_colonne_en_cours = $nb_colonne_max;	
370
			$nb_colonne_en_cours = $nb_colonne_max;	
371
 
371
 
372
			// Obtention des descriptions de champs communs et spécifiques
372
			// Obtention des descriptions de champs communs et spécifiques
373
			$descriptions = $this->obtenirDescriptions($tpl_dir.'modele_import_description.txt');
373
			$descriptions = $this->obtenirDescriptions($tpl_dir.'modele_import_description.txt');
374
			if(!empty($_GET['groupe'])) {
374
			if(!empty($_GET['groupe'])) {
375
				$descriptions = $this->obtenirDescriptions($tpl_dir.'modele_import_description_'.$_GET['groupe'].'.txt', $descriptions);
375
				$descriptions = $this->obtenirDescriptions($tpl_dir.'modele_import_description_'.$_GET['groupe'].'.txt', $descriptions);
376
			}
376
			}
377
			
377
			
378
			// Association de la description des champs commun
378
			// Association de la description des champs commun
379
			for($i = 0; $i < $nb_colonne_max; $i++) {
379
			for($i = 0; $i < $nb_colonne_max; $i++) {
380
				$lettre_colonne = PHPExcel_Cell::stringFromColumnIndex($i);
380
				$lettre_colonne = PHPExcel_Cell::stringFromColumnIndex($i);
381
				$champ_obl = $feuille_tpl->getCell($lettre_colonne.$ligne)->getValue();
381
				$champ_obl = $feuille_tpl->getCell($lettre_colonne.$ligne)->getValue();
382
				
382
				
383
				if(!empty($descriptions[$champ_obl])) {
383
				if(!empty($descriptions[$champ_obl])) {
384
					$feuille_tpl->getComment($lettre_colonne.$ligne)->getText()->createTextRun($descriptions[$champ_obl]);
384
					$feuille_tpl->getComment($lettre_colonne.$ligne)->getText()->createTextRun($descriptions[$champ_obl]);
385
					$feuille_tpl->getComment($lettre_colonne.$ligne)->setWidth(400); 
385
					$feuille_tpl->getComment($lettre_colonne.$ligne)->setWidth(400); 
386
				}
386
				}
387
			}
387
			}
388
						
388
						
389
			$nom_fichier = 'import';
389
			$nom_fichier = 'import';
390
			// Ajout des colonnes spécifiques si un groupe de champ est demandé
390
			// Ajout des colonnes spécifiques si un groupe de champ est demandé
391
			if(!empty($_GET['groupe'])) {
391
			if(!empty($_GET['groupe'])) {
392
				$requete = "SELECT * FROM cel_catalogue_champs_etendus_liaison WHERE groupe = ".Cel::db()->proteger($_GET['groupe']);
392
				$requete = "SELECT * FROM cel_catalogue_champs_etendus_liaison WHERE groupe = ".Cel::db()->proteger($_GET['groupe']);
393
				$champs = Cel::db()->requeter($requete);
393
				$champs = Cel::db()->requeter($requete);
394
 
394
 
395
				foreach($champs as $champ) {	
395
				foreach($champs as $champ) {	
396
					$lettre_colonne = PHPExcel_Cell::stringFromColumnIndex($nb_colonne_en_cours);
396
					$lettre_colonne = PHPExcel_Cell::stringFromColumnIndex($nb_colonne_en_cours);
397
					// Les champs étendus sont préfixés par "ext:" pour ne pas être ignoré lors d'un import
397
					// Les champs étendus sont préfixés par "ext:" pour ne pas être ignoré lors d'un import
398
					// l'import ignore les noms de colonnes qu'il ne connait pas
398
					// l'import ignore les noms de colonnes qu'il ne connait pas
399
					$feuille_tpl->setCellValue($lettre_colonne.$ligne, 'ext:'.$champ['champ']);
399
					$feuille_tpl->setCellValue($lettre_colonne.$ligne, 'ext:'.$champ['champ']);
400
					// Ajout de la description dans le commentaire si elle est présente
400
					// Ajout de la description dans le commentaire si elle est présente
401
					if(!empty($descriptions[$champ['champ']])) { 
401
					if(!empty($descriptions[$champ['champ']])) { 
402
						$feuille_tpl->getComment($lettre_colonne.$ligne)->getText()->createTextRun($descriptions[$champ['champ']]);
402
						$feuille_tpl->getComment($lettre_colonne.$ligne)->getText()->createTextRun($descriptions[$champ['champ']]);
403
						$feuille_tpl->getComment($lettre_colonne.$ligne)->setWidth(400);
403
						$feuille_tpl->getComment($lettre_colonne.$ligne)->setWidth(400);
404
					}
404
					}
405
					
405
					
406
					$nb_colonne_en_cours++;
406
					$nb_colonne_en_cours++;
407
				}		
407
				}		
408
				$nom_fichier .= '_'.$_GET['groupe'];
408
				$nom_fichier .= '_'.$_GET['groupe'];
409
			}
409
			}
410
			
410
			
411
			// Seul le format xlsx permet l'association de commentaires de colonnes dans PHPExcel
411
			// Seul le format xlsx permet l'association de commentaires de colonnes dans PHPExcel
412
			// C'est triste mais bon mais c'est trop pratique pour qu'on s'en passe
412
			// C'est triste mais bon mais c'est trop pratique pour qu'on s'en passe
413
			header('Content-type: application/vnd.ms-excel');	
413
			header('Content-type: application/vnd.ms-excel');	
414
			header('Content-Disposition: attachment; filename="'.$nom_fichier.'.xlsx"');
414
			header('Content-Disposition: attachment; filename="'.$nom_fichier.'.xlsx"');
415
			$generateur = PHPExcel_IOFactory::createWriter($classeur_tpl, 'Excel2007');
415
			$generateur = PHPExcel_IOFactory::createWriter($classeur_tpl, 'Excel2007');
416
			$generateur->save('php://output');
416
			$generateur->save('php://output');
417
			
417
			
418
			exit;
418
			exit;
419
		}
419
		}
420
	}
420
	}
421
	
421
	
422
	private function obtenirDescriptions($fichier_description, $descriptions = array()) {	
422
	private function obtenirDescriptions($fichier_description, $descriptions = array()) {	
423
		if(file_exists($fichier_description)) {
423
		if(file_exists($fichier_description)) {
424
			$descs_str = file_get_contents($fichier_description);
424
			$descs_str = file_get_contents($fichier_description);
425
			$desc_items = explode("\n", $descs_str);
425
			$desc_items = explode("\n", $descs_str);
426
			
426
			
427
			foreach($desc_items as $item) {
427
			foreach($desc_items as $item) {
428
				$cle_valeur = explode("=", $item);
428
				$cle_valeur = explode("=", $item);
429
				$valeur_desc = trim($cle_valeur[1]);
429
				$valeur_desc = trim($cle_valeur[1]);
430
				// Les clés des fichiers sont écrasées dans l'ordre de lecture ce qui permet 
430
				// Les clés des fichiers sont écrasées dans l'ordre de lecture ce qui permet 
431
				// d'avoir par exemple une description différente pour le champ station suivant le projet
431
				// d'avoir par exemple une description différente pour le champ station suivant le projet
432
				// les "<br />" sont remplacés par des sauts de lignes 
432
				// les "<br />" sont remplacés par des sauts de lignes 
433
				$descriptions[trim($cle_valeur[0])] = str_replace('<br />', "\n", $valeur_desc);
433
				$descriptions[trim($cle_valeur[0])] = str_replace('<br />', "\n", $valeur_desc);
434
			}
434
			}
435
		}
435
		}
436
		
436
		
437
		return $descriptions;
437
		return $descriptions;
438
	}
438
	}
439
	
439
	
440
	public function getRessource() {
440
	public function getRessource() {
441
		return self::getStatsDernierUpload();
441
		return self::getStatsDernierUpload();
442
	}
442
	}
443
	
443
	
444
	static function getStatsDernierUpload() {
444
	static function getStatsDernierUpload() {
445
		// renvoi des statistiques du dernier envoi de fichier
445
		// renvoi des statistiques du dernier envoi de fichier
446
		$stats = !empty($_SESSION['upload_stats']) ? $_SESSION['upload_stats'] : null;
446
		$stats = !empty($_SESSION['upload_stats']) ? $_SESSION['upload_stats'] : null;
447
		header("Content-Type: application/json; charset=utf-8");
447
		header("Content-Type: application/json; charset=utf-8");
448
		echo json_encode($stats);
448
		echo json_encode($stats);
449
		die();
449
		die();
450
	}
450
	}
451
 
451
 
452
	/* detectionEntete() sert deux rôles:
452
	/* detectionEntete() sert deux rôles:
453
	 1) détecter le type de colonne attendu à partir des textes de la ligne d'en-tête afin de define()
453
	 1) détecter le type de colonne attendu à partir des textes de la ligne d'en-tête afin de define()
454
	 2) permet d'identifier les colonnes non-supportées/inutiles afin d'alléger le processus de parsing de PHPExcel
454
	 2) permet d'identifier les colonnes non-supportées/inutiles afin d'alléger le processus de parsing de PHPExcel
455
	 grace au ReadFilter (C'est le rôle de la valeur de retour)
455
	 grace au ReadFilter (C'est le rôle de la valeur de retour)
456
 
456
 
457
	 La raison de la présence du paramètre $numeric_keys est que pour réussir à identifier les colonnes à exclure nous
457
	 La raison de la présence du paramètre $numeric_keys est que pour réussir à identifier les colonnes à exclure nous
458
	 devons traiter un tableau représentant la ligne d'en-tête aussi bien:
458
	 devons traiter un tableau représentant la ligne d'en-tête aussi bien:
459
	  - sous forme associative pour PHPExcel (les clefs sont les lettres de l'alphabet)
459
	  - sous forme associative pour PHPExcel (les clefs sont les lettres de l'alphabet)
460
	  - sous forme de clefs numériques (fgetcsv())
460
	  - sous forme de clefs numériques (fgetcsv())
461
	  Le détecter après coup est difficile et pourtant cette distinction est importante car le comportement
461
	  Le détecter après coup est difficile et pourtant cette distinction est importante car le comportement
462
	  d'array_merge() (réordonnancement des clefs numérique) n'est pas souhaitable dans le second cas. */
462
	  d'array_merge() (réordonnancement des clefs numérique) n'est pas souhaitable dans le second cas. */
463
	static function detectionEntete($entete, $numeric_keys = FALSE) {
463
	static function detectionEntete($entete, $numeric_keys = FALSE) {
464
		$colonnes_reconnues = Array();
464
		$colonnes_reconnues = Array();
465
		$cols = FormateurGroupeColonne::nomEnsembleVersListeColonnes('standard,avance');
465
		$cols = FormateurGroupeColonne::nomEnsembleVersListeColonnes('standard,avance');
466
 
466
 
467
		foreach ($entete as $k => $v) {
467
		foreach ($entete as $k => $v) {
468
			// traite les colonnes en faisant fi de la casse et des accents
468
			// traite les colonnes en faisant fi de la casse et des accents
469
			$entete_simple = iconv('UTF-8', 'ASCII//TRANSLIT', strtolower(trim($v)));
469
			$entete_simple = iconv('UTF-8', 'ASCII//TRANSLIT', strtolower(trim($v)));
470
			foreach ($cols as $col) {
470
			foreach ($cols as $col) {
471
				$entete_officiel_simple = iconv('UTF-8', 'ASCII//TRANSLIT', strtolower(trim($col['nom'])));
471
				$entete_officiel_simple = iconv('UTF-8', 'ASCII//TRANSLIT', strtolower(trim($col['nom'])));
472
				$entete_officiel_abbrev = $col['abbrev'];
472
				$entete_officiel_abbrev = $col['abbrev'];
473
				if ($entete_simple == $entete_officiel_simple || $entete_simple == $entete_officiel_abbrev) {
473
				if ($entete_simple == $entete_officiel_simple || $entete_simple == $entete_officiel_abbrev) {
474
					// debug echo "define C_" . strtoupper($entete_officiel_abbrev) . ", $k ($v)\n";
474
					// debug echo "define C_" . strtoupper($entete_officiel_abbrev) . ", $k ($v)\n";
475
					define("C_" . strtoupper($entete_officiel_abbrev), $k);
475
					define("C_" . strtoupper($entete_officiel_abbrev), $k);
476
					$colonnes_reconnues[$k] = 1;
476
					$colonnes_reconnues[$k] = 1;
477
					break;
477
					break;
478
				}
478
				}
479
 
479
 
480
				if (strpos($v, self::$prefixe_colonnes_etendues) === 0) {
480
				if (strpos($v, self::$prefixe_colonnes_etendues) === 0) {
481
					$colonnes_reconnues[$k] = 1;
481
					$colonnes_reconnues[$k] = 1;
482
					self::$indexes_colonnes_etendues[$k] = $v;
482
					self::$indexes_colonnes_etendues[$k] = $v;
483
					break;
483
					break;
484
				}
484
				}
485
			}
485
			}
486
		}
486
		}
487
 
487
 
488
		// défini tous les index que nous utilisons à une valeur d'index de colonne Excel qui n'existe pas dans
488
		// défini tous les index que nous utilisons à une valeur d'index de colonne Excel qui n'existe pas dans
489
		// le tableau renvoyé par PHPExcel
489
		// le tableau renvoyé par PHPExcel
490
		// Attention cependant d'utiliser des indexes différenciés car traiterLonLat() et traiterEspece()
490
		// Attention cependant d'utiliser des indexes différenciés car traiterLonLat() et traiterEspece()
491
		// les utilisent
491
		// les utilisent
492
		foreach ($cols as $col) {
492
		foreach ($cols as $col) {
493
			if (!defined('C_'.strtoupper($col['abbrev']))) {
493
			if (!defined('C_'.strtoupper($col['abbrev']))) {
494
				define('C_'.strtoupper($col['abbrev']), 'C_'.strtoupper($col['abbrev']));
494
				define('C_'.strtoupper($col['abbrev']), 'C_'.strtoupper($col['abbrev']));
495
			}
495
			}
496
		}
496
		}
497
 
497
 
498
		// prépare le filtre de PHPExcel qui évitera le traitement de toutes les colonnes superflues
498
		// prépare le filtre de PHPExcel qui évitera le traitement de toutes les colonnes superflues
499
		$colonnesID_non_reconnues = array_diff_key($entete, $colonnes_reconnues);
499
		$colonnesID_non_reconnues = array_diff_key($entete, $colonnes_reconnues);
500
 
500
 
501
		// des colonnes de FormateurGroupeColonne::nomEnsembleVersListeColonnes()
501
		// des colonnes de FormateurGroupeColonne::nomEnsembleVersListeColonnes()
502
		// ne retient que celles marquées "importables"
502
		// ne retient que celles marquées "importables"
503
		$colonnes_automatiques = array_filter($cols, '__anonyme_1');
503
		$colonnes_automatiques = array_filter($cols, '__anonyme_1');
504
 
504
 
505
		// ne conserve que le nom long pour matcher avec la ligne XLS d'entête
505
		// ne conserve que le nom long pour matcher avec la ligne XLS d'entête
506
		array_walk($colonnes_automatiques, '__anonyme_2');
506
		array_walk($colonnes_automatiques, '__anonyme_2');
507
 
507
 
508
		$colonnesID_a_exclure = array_intersect($entete, $colonnes_automatiques);
508
		$colonnesID_a_exclure = array_intersect($entete, $colonnes_automatiques);
509
 
509
 
510
		if ($numeric_keys) {
510
		if ($numeric_keys) {
511
			return $colonnesID_non_reconnues + $colonnesID_a_exclure;
511
			return $colonnesID_non_reconnues + $colonnesID_a_exclure;
512
		}
512
		}
513
		return array_merge($colonnesID_non_reconnues, $colonnesID_a_exclure);
513
		return array_merge($colonnesID_non_reconnues, $colonnesID_a_exclure);
514
	}
514
	}
515
 
515
 
516
	static function chargerCorrespondancesIdOrdre($cel, $lignes) {
516
	static function chargerCorrespondancesIdOrdre($cel, $lignes) {
517
		$ordresObs = array();
517
		$ordresObs = array();
518
		foreach ($lignes as &$ligne) {
518
		foreach ($lignes as &$ligne) {
519
			$ordresObs[] = $ligne['ordre'];
519
			$ordresObs[] = $ligne['ordre'];
520
		}
520
		}
521
		$ordresObsConcat = implode(',', $ordresObs);
521
		$ordresObsConcat = implode(',', $ordresObs);
522
		$idUtilisateurP = Cel::db()->proteger($cel->id_utilisateur);
522
		$idUtilisateurP = Cel::db()->proteger($cel->id_utilisateur);
523
		$requete = 'SELECT id_observation, ordre '.
523
		$requete = 'SELECT id_observation, ordre '.
524
			'FROM cel_obs '.
524
			'FROM cel_obs '.
525
			"WHERE ordre IN ($ordresObsConcat) ".
525
			"WHERE ordre IN ($ordresObsConcat) ".
526
			"AND ce_utilisateur = $idUtilisateurP ".
526
			"AND ce_utilisateur = $idUtilisateurP ".
527
			' -- '.__FILE__.':'.__LINE__;
527
			' -- '.__FILE__.':'.__LINE__;
528
		$resultats = Cel::db()->requeter($requete);
528
		$resultats = Cel::db()->requeter($requete);
529
		$ordresIds = array();
529
		$ordresIds = array();
530
		foreach ($resultats as &$infos) {
530
		foreach ($resultats as &$infos) {
531
			$ordresIds[$infos['ordre']] = $infos['id_observation'];
531
			$ordresIds[$infos['ordre']] = $infos['id_observation'];
532
		}
532
		}
533
		return $ordresIds;
533
		return $ordresIds;
534
	}
534
	}
535
 
535
 
536
	/*
536
	/*
537
	 * charge un groupe de lignes
537
	 * charge un groupe de lignes
538
	 */
538
	 */
539
	static function chargerLignes($cel, $lignes, $colonnes_statiques, &$dernier_ordre, $config) {
539
	static function chargerLignes($cel, $lignes, $colonnes_statiques, &$dernier_ordre, $config) {
540
		$enregistrement = NULL;
540
		$enregistrement = NULL;
541
		$enregistrements = array();
541
		$enregistrements = array();
542
		$toutes_images = array();
542
		$toutes_images = array();
543
		$tous_mots_cle = array();
543
		$tous_mots_cle = array();
544
		$tous_champs_etendus = array();
544
		$tous_champs_etendus = array();
545
 
545
 
546
		foreach ($lignes as $ligne) {
546
		foreach ($lignes as $ligne) {
547
			// dans le cas de fgetcsv, on peut avoir des false additionnel (cf do/while l. 279)
547
			// dans le cas de fgetcsv, on peut avoir des false additionnel (cf do/while l. 279)
548
			if ($ligne === false) {
548
			if ($ligne === false) {
549
				continue;
549
				continue;
550
			}
550
			}
551
 
551
 
552
			// on a besoin des NULL pour éviter des notice d'index indéfini
552
			// on a besoin des NULL pour éviter des notice d'index indéfini
553
			if (! array_filter($ligne, '__anonyme_3')) {
553
			if (! array_filter($ligne, '__anonyme_3')) {
554
				continue;
554
				continue;
555
			}
555
			}
556
 
556
 
557
			if ($enregistrement = self::chargerLigne($ligne, $dernier_ordre, $cel, $config)) {
557
			if ($enregistrement = self::chargerLigne($ligne, $dernier_ordre, $cel, $config)) {
558
				// $enregistrements[] = array_merge($colonnes_statiques, $enregistrement);
558
				// $enregistrements[] = array_merge($colonnes_statiques, $enregistrement);
559
				if ($enregistrement['latitude'] == NULL && $enregistrement['longitude'] == NULL) {
559
				if ($enregistrement['latitude'] == NULL && $enregistrement['longitude'] == NULL) {
560
					if (isset($enregistrement['_champs_etendus']['latitudeDebutRue'])) {
560
					if (isset($enregistrement['_champs_etendus']['latitudeDebutRue'])) {
561
						$enregistrement['latitude'] = $enregistrement['_champs_etendus']['latitudeDebutRue'];
561
						$enregistrement['latitude'] = $enregistrement['_champs_etendus']['latitudeDebutRue'];
562
						$enregistrement['longitude'] = $enregistrement['_champs_etendus']['longitudeDebutRue'];
562
						$enregistrement['longitude'] = $enregistrement['_champs_etendus']['longitudeDebutRue'];
563
					}
563
					}
564
				}
564
				}
565
				$enregistrements[] = $enregistrement;
565
				$enregistrements[] = $enregistrement;
566
				$pos = count($enregistrements) - 1;
566
				$pos = count($enregistrements) - 1;
567
				$last = &$enregistrements[$pos];
567
				$last = &$enregistrements[$pos];
568
 
568
 
569
				if (isset($enregistrement['_images'])) {
569
				if (isset($enregistrement['_images'])) {
570
					// ne dépend pas de cel_obs, et seront insérées *après* les enregistrements
570
					// ne dépend pas de cel_obs, et seront insérées *après* les enregistrements
571
					// mais nous ne voulons pas nous priver de faire des INSERT multiples pour autant
571
					// mais nous ne voulons pas nous priver de faire des INSERT multiples pour autant
572
					$toutes_images[] = array(
572
					$toutes_images[] = array(
573
						'images' => $last['_images'],
573
						'images' => $last['_images'],
574
						'obs_pos' => $pos);
574
						'obs_pos' => $pos);
575
					// ce champ n'a pas à faire partie de l'insertion dans cel_obs,
575
					// ce champ n'a pas à faire partie de l'insertion dans cel_obs,
576
					// mais est utile pour la liaison avec les images
576
					// mais est utile pour la liaison avec les images
577
					unset($last['_images']);
577
					unset($last['_images']);
578
				}
578
				}
579
 
579
 
580
				if (isset($enregistrement['_mots_cle'])) {
580
				if (isset($enregistrement['_mots_cle'])) {
581
					// ne dépend pas de cel_obs, et seront insérés *après* les enregistrements
581
					// ne dépend pas de cel_obs, et seront insérés *après* les enregistrements
582
					// mais nous ne voulons pas nous priver de faire des INSERT multiples pour autant
582
					// mais nous ne voulons pas nous priver de faire des INSERT multiples pour autant
583
					$tous_mots_cle[] = array(
583
					$tous_mots_cle[] = array(
584
						'mots_cle' => $last['_mots_cle'],
584
						'mots_cle' => $last['_mots_cle'],
585
						'obs_pos' => $pos);
585
						'obs_pos' => $pos);
586
					unset($last['_mots_cle']);
586
					unset($last['_mots_cle']);
587
				}
587
				}
588
 
588
 
589
				if (isset($enregistrement['_champs_etendus'])) {
589
				if (isset($enregistrement['_champs_etendus'])) {
590
					$tous_champs_etendus[] = array(
590
					$tous_champs_etendus[] = array(
591
						'champs_etendus' => $last['_champs_etendus'],
591
						'champs_etendus' => $last['_champs_etendus'],
592
						'ordre' => $dernier_ordre);
592
						'ordre' => $dernier_ordre);
593
					unset($last['_champs_etendus']);
593
					unset($last['_champs_etendus']);
594
				}
594
				}
595
				
595
				
596
				
596
				
597
				$dernier_ordre++;
597
				$dernier_ordre++;
598
			}
598
			}
599
		}
599
		}
600
		
600
		
601
		return array($enregistrements, $toutes_images, $tous_mots_cle, $tous_champs_etendus);
601
		return array($enregistrements, $toutes_images, $tous_mots_cle, $tous_champs_etendus);
602
	}
602
	}
603
 
603
 
604
	static function trierColonnes(&$enregistrements) {
604
	static function trierColonnes(&$enregistrements) {
605
		foreach ($enregistrements as &$enregistrement) {
605
		foreach ($enregistrements as &$enregistrement) {
606
			$enregistrement = self::sortArrayByArray($enregistrement, self::$ordre_BDD);
606
			$enregistrement = self::sortArrayByArray($enregistrement, self::$ordre_BDD);
607
		}
607
		}
608
	}
608
	}
609
 
609
 
610
	static function stockerMotsCle($enregistrements, $tous_mots_cle) {
610
	static function stockerMotsCle($enregistrements, $tous_mots_cle) {
611
		$c = 0;
611
		$c = 0;
612
		// debug: var_dump($tous_mots_cle);die;
612
		// debug: var_dump($tous_mots_cle);die;
613
		foreach ($tous_mots_cle as $v) {
613
		foreach ($tous_mots_cle as $v) {
614
			$c += count($v['mots_cle']['to_insert']);
614
			$c += count($v['mots_cle']['to_insert']);
615
		}
615
		}
616
		return $c;
616
		return $c;
617
	}
617
	}
618
 
618
 
619
	static function stockerImages($enregistrements, $toutes_images, $ordre_ids) {
619
	static function stockerImages($enregistrements, $toutes_images, $ordre_ids) {
620
		$valuesSql = array();
620
		$valuesSql = array();
621
		foreach ($toutes_images as $images_pour_obs) {
621
		foreach ($toutes_images as $images_pour_obs) {
622
			$obs = $enregistrements[$images_pour_obs['obs_pos']];
622
			$obs = $enregistrements[$images_pour_obs['obs_pos']];
623
			$id_obs = $ordre_ids[$obs['ordre']]; // id réel de l'observation correspondant à l'ordre
623
			$id_obs = $ordre_ids[$obs['ordre']]; // id réel de l'observation correspondant à l'ordre
624
			$transmission = $obs['transmission'];
624
			$transmission = $obs['transmission'];
625
			$date_transmission = 'NOW()'; // par défaut pour les nouveaux imports
625
			$date_transmission = 'NOW()'; // par défaut pour les nouveaux imports
626
			if ($obs['date_transmission'] != null) { // peut être NULL selon la valeur par défaut de la colonne, la version du SGBD, etc.
626
			if ($obs['date_transmission'] != null) { // peut être NULL selon la valeur par défaut de la colonne, la version du SGBD, etc.
627
				$date_transmission = $obs['date_transmission'];
627
				$date_transmission = $obs['date_transmission'];
628
			}
628
			}
629
			foreach ($images_pour_obs['images'] as $image) {
629
			foreach ($images_pour_obs['images'] as $image) {
630
				$id_img = $image['id_image'];
630
				$id_img = $image['id_image'];
631
				$valuesSql[] = "($id_img, $id_obs, NOW(), $transmission, $date_transmission)";
631
				$valuesSql[] = "($id_img, $id_obs, NOW(), $transmission, $date_transmission)";
632
			}
632
			}
633
		}
633
		}
634
 
634
 
635
		if ($valuesSql) {
635
		if ($valuesSql) {
636
			$clauseValues = implode(', ', $valuesSql);
636
			$clauseValues = implode(', ', $valuesSql);
637
			// Utilisation de INSERT pour faire des UPDATE multiples en une seule requête
637
			// Utilisation de INSERT pour faire des UPDATE multiples en une seule requête
638
			$requete = 'INSERT INTO cel_images '.
638
			$requete = 'INSERT INTO cel_images '.
639
				'(id_image, ce_observation, date_liaison, transmission, date_transmission) '.
639
				'(id_image, ce_observation, date_liaison, transmission, date_transmission) '.
640
				"VALUES $clauseValues ".
640
				"VALUES $clauseValues ".
641
				'ON DUPLICATE KEY UPDATE '.
641
				'ON DUPLICATE KEY UPDATE '.
642
				'ce_observation = VALUES(ce_observation), '.
642
				'ce_observation = VALUES(ce_observation), '.
643
				'date_liaison = NOW(), '.
643
				'date_liaison = NOW(), '.
644
				'transmission = VALUES(transmission), '.
644
				'transmission = VALUES(transmission), '.
645
				'date_transmission = VALUES(date_transmission) '.
645
				'date_transmission = VALUES(date_transmission) '.
646
				' -- '.__FILE__.':'.__LINE__;
646
				' -- '.__FILE__.':'.__LINE__;
647
			Cel::db()->executer($requete);
647
			Cel::db()->executer($requete);
648
		}
648
		}
649
		return count($valuesSql);
649
		return count($valuesSql);
650
	}
650
	}
651
 
651
 
652
	/*
652
	/*
653
	 Aucune des valeurs présentes dans $enregistrement n'est quotée
653
	 Aucune des valeurs présentes dans $enregistrement n'est quotée
654
	 cad aucune des valeurs retournée par traiter{Espece|Localisation}()
654
	 cad aucune des valeurs retournée par traiter{Espece|Localisation}()
655
	 car ce tableau est passé à un PDO::preparedStatement() qui applique
655
	 car ce tableau est passé à un PDO::preparedStatement() qui applique
656
	  proprement les règle d'échappement.
656
	  proprement les règle d'échappement.
657
	*/
657
	*/
658
	static function chargerLigne($ligne, $dernier_ordre, $cel, $config) {
658
	static function chargerLigne($ligne, $dernier_ordre, $cel, $config) {
659
		// évite des notices d'index lors des trigger_error()
659
		// évite des notices d'index lors des trigger_error()
660
		$ref_ligne = !empty($ligne[C_NOM_SEL]) ? trim($ligne[C_NOM_SEL]) : '';
660
		$ref_ligne = !empty($ligne[C_NOM_SEL]) ? trim($ligne[C_NOM_SEL]) : '';
661
 
661
 
662
		// en premier car le résultat est utile pour
662
		// en premier car le résultat est utile pour
663
		// * traiter espèce (traiterEspece())
663
		// * traiter espèce (traiterEspece())
664
		// * traiter longitude et latitude (traiterLonLat())
664
		// * traiter longitude et latitude (traiterLonLat())
665
		$referentiel = self::identReferentiel(trim(strtolower(@$ligne[C_NOM_REFERENTIEL])), $ligne, $ref_ligne);
665
		$referentiel = self::identReferentiel(trim(strtolower(@$ligne[C_NOM_REFERENTIEL])), $ligne, $ref_ligne);
666
 
666
 
667
		// $espece est rempli de plusieurs informations
667
		// $espece est rempli de plusieurs informations
668
		$espece = array(
668
		$espece = array(
669
			C_NOM_SEL => NULL,
669
			C_NOM_SEL => NULL,
670
			C_NOM_SEL_NN => NULL,
670
			C_NOM_SEL_NN => NULL,
671
			C_NOM_RET => NULL,
671
			C_NOM_RET => NULL,
672
			C_NOM_RET_NN => NULL,
672
			C_NOM_RET_NN => NULL,
673
			C_NT => NULL,
673
			C_NT => NULL,
674
			C_FAMILLE => NULL);
674
			C_FAMILLE => NULL);
675
		self::traiterEspece($ligne, $espece, $referentiel, $cel->taxon_info_webservice);
675
		self::traiterEspece($ligne, $espece, $referentiel, $cel->taxon_info_webservice);
676
 
676
 
677
		if (!$espece[C_NOM_SEL]) {
677
		if (!$espece[C_NOM_SEL]) {
678
			$referentiel = Cel::$fallback_referentiel;
678
			$referentiel = Cel::$fallback_referentiel;
679
		}
679
		}
680
		if ($espece[C_NOM_SEL] && !$espece[C_NOM_SEL_NN]) {
680
		if ($espece[C_NOM_SEL] && !$espece[C_NOM_SEL_NN]) {
681
			$referentiel = Cel::$fallback_referentiel;
681
			$referentiel = Cel::$fallback_referentiel;
682
		}
682
		}
683
 
683
 
684
		// $localisation est rempli à partir de plusieurs champs: C_ZONE_GEO et C_CE_ZONE_GEO
684
		// $localisation est rempli à partir de plusieurs champs: C_ZONE_GEO et C_CE_ZONE_GEO
685
		$localisation = Array(C_ZONE_GEO => NULL, C_CE_ZONE_GEO => NULL);
685
		$localisation = Array(C_ZONE_GEO => NULL, C_CE_ZONE_GEO => NULL);
686
		self::traiterLocalisation($ligne, $localisation);
686
		self::traiterLocalisation($ligne, $localisation);
687
		//TODO: le jour où c'est efficace, traiter le pays à l'import
687
		//TODO: le jour où c'est efficace, traiter le pays à l'import
688
 
688
 
689
		// $transmission est utilisé pour date_transmission
689
		// $transmission est utilisé pour date_transmission
690
		// XXX: @ contre "Undefined index"
690
		// XXX: @ contre "Undefined index"
691
		@$transmission = in_array(strtolower(trim($ligne[C_TRANSMISSION])), array(1, 'oui')) ? 1 : 0;
691
		@$transmission = in_array(strtolower(trim($ligne[C_TRANSMISSION])), array(1, 'oui')) ? 1 : 0;
692
 
692
 
693
 
693
 
694
		// Dans ce tableau, seules devraient apparaître les données variable pour chaque ligne.
694
		// Dans ce tableau, seules devraient apparaître les données variable pour chaque ligne.
695
		// Dans ce tableau, l'ordre des clefs n'importe pas (cf: self::sortArrayByArray())
695
		// Dans ce tableau, l'ordre des clefs n'importe pas (cf: self::sortArrayByArray())
696
		$enregistrement = array(
696
		$enregistrement = array(
697
			"ordre" => $dernier_ordre,
697
			"ordre" => $dernier_ordre,
698
 
698
 
699
			"nom_sel" => $espece[C_NOM_SEL],
699
			"nom_sel" => $espece[C_NOM_SEL],
700
			"nom_sel_nn" => $espece[C_NOM_SEL_NN],
700
			"nom_sel_nn" => $espece[C_NOM_SEL_NN],
701
			"nom_ret" => $espece[C_NOM_RET],
701
			"nom_ret" => $espece[C_NOM_RET],
702
			"nom_ret_nn" => $espece[C_NOM_RET_NN],
702
			"nom_ret_nn" => $espece[C_NOM_RET_NN],
703
			"nt" => $espece[C_NT],
703
			"nt" => $espece[C_NT],
704
			"famille" => $espece[C_FAMILLE],
704
			"famille" => $espece[C_FAMILLE],
705
 
705
 
706
			"nom_referentiel" => $referentiel,
706
			"nom_referentiel" => $referentiel,
707
 
707
 
708
			"pays" => $ligne[C_PAYS],
708
			"pays" => $ligne[C_PAYS],
709
			"zone_geo" => $localisation[C_ZONE_GEO],
709
			"zone_geo" => $localisation[C_ZONE_GEO],
710
			"ce_zone_geo" => $localisation[C_CE_ZONE_GEO],
710
			"ce_zone_geo" => $localisation[C_CE_ZONE_GEO],
711
 
711
 
712
			// $ligne: uniquement pour les infos en cas de gestion d'erreurs (date incompréhensible)
712
			// $ligne: uniquement pour les infos en cas de gestion d'erreurs (date incompréhensible)
713
			"date_observation" => isset($ligne[C_DATE_OBSERVATION]) ? self::traiterDateObs($ligne[C_DATE_OBSERVATION], $ref_ligne) : null,
713
			"date_observation" => isset($ligne[C_DATE_OBSERVATION]) ? self::traiterDateObs($ligne[C_DATE_OBSERVATION], $ref_ligne) : null,
714
 
714
 
715
			"lieudit" => isset($ligne[C_LIEUDIT]) ? trim($ligne[C_LIEUDIT]) : null,
715
			"lieudit" => isset($ligne[C_LIEUDIT]) ? trim($ligne[C_LIEUDIT]) : null,
716
			"station" => isset($ligne[C_STATION]) ? trim($ligne[C_STATION]) : null,
716
			"station" => isset($ligne[C_STATION]) ? trim($ligne[C_STATION]) : null,
717
			"milieu" => isset($ligne[C_MILIEU]) ? trim($ligne[C_MILIEU]) : null,
717
			"milieu" => isset($ligne[C_MILIEU]) ? trim($ligne[C_MILIEU]) : null,
718
 
718
 
719
			"mots_cles_texte" => NULL, // TODO: foreign-key
719
			"mots_cles_texte" => NULL, // TODO: foreign-key
720
			// XXX: @ contre "Undefined index"
720
			// XXX: @ contre "Undefined index"
721
			"commentaire" => isset($ligne[C_COMMENTAIRE]) ? trim($ligne[C_COMMENTAIRE]) : null,
721
			"commentaire" => isset($ligne[C_COMMENTAIRE]) ? trim($ligne[C_COMMENTAIRE]) : null,
722
 
722
 
723
			"transmission" => $transmission,
723
			"transmission" => $transmission,
724
			"date_transmission" => $transmission ? date('Y-m-d H:i:s') : null, // pas de fonction SQL dans un PDO statement, <=> now()
724
			"date_transmission" => $transmission ? date('Y-m-d H:i:s') : null, // pas de fonction SQL dans un PDO statement, <=> now()
725
 
725
 
726
			// $ligne: uniquement pour les infos en cas de gestion d'erreurs (lon/lat incompréhensible)
726
			// $ligne: uniquement pour les infos en cas de gestion d'erreurs (lon/lat incompréhensible)
727
			"latitude" => isset($ligne[C_LATITUDE]) ? self::traiterLonLat(null, $ligne[C_LATITUDE], $referentiel, $ref_ligne) : null,
727
			"latitude" => isset($ligne[C_LATITUDE]) ? self::traiterLonLat(null, $ligne[C_LATITUDE], $referentiel, $ref_ligne) : null,
728
			"longitude" => isset($ligne[C_LONGITUDE]) ? self::traiterLonLat($ligne[C_LONGITUDE], null, $referentiel, $ref_ligne) : null,
728
			"longitude" => isset($ligne[C_LONGITUDE]) ? self::traiterLonLat($ligne[C_LONGITUDE], null, $referentiel, $ref_ligne) : null,
729
			"altitude" => isset($ligne[C_ALTITUDE]) ? intval($ligne[C_ALTITUDE]) : null, // TODO: guess alt from lon/lat
729
			"altitude" => isset($ligne[C_ALTITUDE]) ? intval($ligne[C_ALTITUDE]) : null, // TODO: guess alt from lon/lat
730
 
730
 
731
			// @ car potentiellement optionnelles ou toutes vides => pas d'index dans PHPExcel (tableau optimisé)
731
			// @ car potentiellement optionnelles ou toutes vides => pas d'index dans PHPExcel (tableau optimisé)
732
			"abondance" => @$ligne[C_ABONDANCE],
732
			"abondance" => @$ligne[C_ABONDANCE],
733
			"certitude" => @$ligne[C_CERTITUDE],
733
			"certitude" => @$ligne[C_CERTITUDE],
734
			"phenologie" => @$ligne[C_PHENOLOGIE],
734
			"phenologie" => @$ligne[C_PHENOLOGIE],
735
 
735
 
736
			"code_insee_calcule" => substr($localisation[C_CE_ZONE_GEO], -5) // varchar(5)
736
			"code_insee_calcule" => substr($localisation[C_CE_ZONE_GEO], -5) // varchar(5)
737
		);
737
		);
738
 
738
 
739
		// passage de $enregistrement par référence, ainsi ['_images'] n'est défini
739
		// passage de $enregistrement par référence, ainsi ['_images'] n'est défini
740
		// que si des résultats sont trouvés
740
		// que si des résultats sont trouvés
741
		// "@" car PHPExcel supprime les colonnes null sur toute la feuille (ou tout le chunk)
741
		// "@" car PHPExcel supprime les colonnes null sur toute la feuille (ou tout le chunk)
742
		if (@$ligne[C_IMAGES]) {
742
		if (@$ligne[C_IMAGES]) {
743
			self::traiterImage($ligne[C_IMAGES], $cel->id_utilisateur, $enregistrement);
743
			self::traiterImage($ligne[C_IMAGES], $cel->id_utilisateur, $enregistrement);
744
		}
744
		}
745
 
745
 
746
		if (@$ligne[C_MOTS_CLES_TEXTE]) {
746
		if (@$ligne[C_MOTS_CLES_TEXTE]) {
747
			self::traiterMotsCle($ligne[C_MOTS_CLES_TEXTE], $cel->id_utilisateur, $enregistrement, $config);
747
			self::traiterMotsCle($ligne[C_MOTS_CLES_TEXTE], $cel->id_utilisateur, $enregistrement, $config);
748
		}
748
		}
749
 
749
 
750
		$champs_etendus = self::traiterChampsEtendus($ligne, self::$indexes_colonnes_etendues);
750
		$champs_etendus = self::traiterChampsEtendus($ligne, self::$indexes_colonnes_etendues);
751
		if (!empty($champs_etendus)) {
751
		if (!empty($champs_etendus)) {
752
			$enregistrement['_champs_etendus'] = $champs_etendus;
752
			$enregistrement['_champs_etendus'] = $champs_etendus;
753
		}
753
		}
754
 
754
 
755
		return $enregistrement;
755
		return $enregistrement;
756
	}
756
	}
757
 
757
 
758
	static function traiterChampsEtendus(&$ligne, &$indexes_colonnes_etendues) {
758
	static function traiterChampsEtendus(&$ligne, &$indexes_colonnes_etendues) {
759
		$champs_etendus_indexes = array();
759
		$champs_etendus_indexes = array();
760
		foreach($indexes_colonnes_etendues as $index_num => $label) {
760
		foreach($indexes_colonnes_etendues as $index_num => $label) {
761
			if (isset($ligne[$index_num])) {
761
			if (isset($ligne[$index_num])) {
762
				$champs_etendus_indexes[str_replace(self::$prefixe_colonnes_etendues, '', $label)] = $ligne[$index_num];
762
				$champs_etendus_indexes[str_replace(self::$prefixe_colonnes_etendues, '', $label)] = $ligne[$index_num];
763
			}
763
			}
764
		}
764
		}
765
		return $champs_etendus_indexes;
765
		return $champs_etendus_indexes;
766
	}
766
	}
767
 
767
 
768
	static function traiterImage($str, $id_utilisateur, &$enregistrement) {
768
	static function traiterImage($str, $id_utilisateur, &$enregistrement) {
769
		$liste_images = array_filter(explode('/', $str));
769
		$liste_images = array_filter(explode('/', $str));
770
		array_walk($liste_images, array(__CLASS__, '__anonyme_4'));
770
		array_walk($liste_images, array(__CLASS__, '__anonyme_4'));
771
 
771
 
772
		$nomsOrignalConcat = implode(',', $liste_images);
772
		$nomsOrignalConcat = implode(',', $liste_images);
773
		$requete = 'SELECT id_image, nom_original '.
773
		$requete = 'SELECT id_image, nom_original '.
774
			'FROM cel_images '.
774
			'FROM cel_images '.
775
			"WHERE ce_utilisateur = $id_utilisateur AND nom_original IN ($nomsOrignalConcat) ".
775
			"WHERE ce_utilisateur = $id_utilisateur AND nom_original IN ($nomsOrignalConcat) ".
776
			' -- '.__FILE__.':'.__LINE__;
776
			' -- '.__FILE__.':'.__LINE__;
777
		$resultat = Cel::db()->requeter($requete);
777
		$resultat = Cel::db()->requeter($requete);
778
 
778
 
779
		if ($resultat) {
779
		if ($resultat) {
780
			$enregistrement['_images'] = $resultat;
780
			$enregistrement['_images'] = $resultat;
781
		}
781
		}
782
	}
782
	}
783
 
783
 
784
	static function traiterMotsCle($str, $id_utilisateur, &$enregistrement, $config) {
784
	static function traiterMotsCle($str, $id_utilisateur, &$enregistrement, $config) {
785
		$liste_mots_cle = $liste_mots_cle_recherche = array_map('trim', array_unique(array_filter(explode(',', $str))));
785
		$liste_mots_cle = $liste_mots_cle_recherche = array_map('trim', array_unique(array_filter(explode(',', $str))));
786
		array_walk($liste_mots_cle_recherche, array(__CLASS__, '__anonyme_4'));
786
		array_walk($liste_mots_cle_recherche, array(__CLASS__, '__anonyme_4'));
787
 
787
 
788
		#if (self::$gestion_mots_cles == null) {
788
		#if (self::$gestion_mots_cles == null) {
789
		if (true) {
789
		if (true) {
790
			$gestion_mots_cles = new GestionMotsClesChemin($config, 'obs');
790
			$gestion_mots_cles = new GestionMotsClesChemin($config, 'obs');
791
		}
791
		}
792
 
792
 
793
		// Recherche des IDs des mots-cles
793
		// Recherche des IDs des mots-cles
794
		$mots_cles_ids = $gestion_mots_cles->obtenirIdsMotClesPourMotsCles($liste_mots_cle, $id_utilisateur);
794
		$mots_cles_ids = $gestion_mots_cles->obtenirIdsMotClesPourMotsCles($liste_mots_cle, $id_utilisateur);
795
		// Y aura-t-il des nouvaux mots-cles
795
		// Y aura-t-il des nouvaux mots-cles
796
		foreach ($mots_cles_ids as $mot_cle) {
796
		foreach ($mots_cles_ids as $mot_cle) {
797
			$resultat[$mot_cle['id_mot_cle']] = $mot_cle['mot_cle'];
797
			$resultat[$mot_cle['id_mot_cle']] = $mot_cle['mot_cle'];
798
		}
798
		}
799
		if ($resultat == null) {
799
		if ($resultat == null) {
800
			// array_diff() ne gère pas les NULL en entrée
800
			// array_diff() ne gère pas les NULL en entrée
801
			$nouveaux_mots_cles = $liste_mots_cle;
801
			$nouveaux_mots_cles = $liste_mots_cle;
802
		} else {
802
		} else {
803
			$nouveaux_mots_cles = array_diff($liste_mots_cle, $resultat);
803
			$nouveaux_mots_cles = array_diff($liste_mots_cle, $resultat);
804
		}
804
		}
805
		// Ajout des mots-cles inexistants dans l'arbre
805
		// Ajout des mots-cles inexistants dans l'arbre
806
		if (count($nouveaux_mots_cles) > 0) {
806
		if (count($nouveaux_mots_cles) > 0) {
807
			foreach ($nouveaux_mots_cles as $nmc) {
807
			foreach ($nouveaux_mots_cles as $nmc) {
808
				$gestion_mots_cles->insererParChemin($nmc, '/', $id_utilisateur);
808
				$gestion_mots_cles->insererParChemin($nmc, '/', $id_utilisateur);
809
			}
809
			}
810
			// Mise à jour des ids de mots-cles après ajout
810
			// Mise à jour des ids de mots-cles après ajout
811
			$mots_cles_ids = $gestion_mots_cles->obtenirIdsMotClesPourMotsCles($liste_mots_cle, $id_utilisateur);
811
			$mots_cles_ids = $gestion_mots_cles->obtenirIdsMotClesPourMotsCles($liste_mots_cle, $id_utilisateur);
812
		}
812
		}
813
 
813
 
814
		$enregistrement['mots_cles_texte'] = implode(',', $liste_mots_cle);
814
		$enregistrement['mots_cles_texte'] = implode(',', $liste_mots_cle);
815
		$enregistrement['_mots_cle'] = array(
815
		$enregistrement['_mots_cle'] = array(
816
			'existing' => $resultat,
816
			'existing' => $resultat,
817
			'to_insert' => $nouveaux_mots_cles
817
			'to_insert' => $nouveaux_mots_cles
818
		);
818
		);
819
	}
819
	}
820
 
820
 
821
 
821
 
822
	/* FONCTIONS de TRANSFORMATION de VALEUR DE CELLULE */
822
	/* FONCTIONS de TRANSFORMATION de VALEUR DE CELLULE */
823
	// TODO: PHP 5.3, utiliser date_parse_from_format()
823
	// TODO: PHP 5.3, utiliser date_parse_from_format()
824
	// TODO: parser les heures (cf product-owner)
824
	// TODO: parser les heures (cf product-owner)
825
	// TODO: passer par le timestamp pour s'assurer de la validité
825
	// TODO: passer par le timestamp pour s'assurer de la validité
826
	static function traiterDateObs($date, $ref_ligne) {
826
	static function traiterDateObs($date, $ref_ligne) {
827
		// TODO: see https://github.com/PHPOffice/PHPExcel/issues/208
827
		// TODO: see https://github.com/PHPOffice/PHPExcel/issues/208
828
		// TODO: PHPExcel_Shared_Date::ExcelToPHP()
828
		// TODO: PHPExcel_Shared_Date::ExcelToPHP()
829
		if (is_double($date)) {
829
		if (is_double($date)) {
830
			if ($date > 0) {
830
			if ($date > 0) {
831
				return PHPExcel_Style_NumberFormat::toFormattedString($date, PHPExcel_Style_NumberFormat::FORMAT_DATE_YYYYMMDD2) . " 00:00:00";
831
				return PHPExcel_Style_NumberFormat::toFormattedString($date, PHPExcel_Style_NumberFormat::FORMAT_DATE_YYYYMMDD2) . " 00:00:00";
832
			}
832
			}
833
 
833
 
834
			$msg = "ligne «{$ref_ligne}»: Attention: date antérieure à 1970 et format de cellule «DATE» utilisés ensemble";
834
			$msg = "ligne «{$ref_ligne}»: Attention: date antérieure à 1970 et format de cellule «DATE» utilisés ensemble";
835
			trigger_error($msg, E_USER_NOTICE);
835
			trigger_error($msg, E_USER_NOTICE);
836
		} else {
836
		} else {
837
			// attend l'un des formats de
837
			// attend l'un des formats de
838
			// http://www.php.net/manual/fr/datetime.formats.date.php
838
			// http://www.php.net/manual/fr/datetime.formats.date.php
839
			// le plus simple: YYYY/MM/DD (utilisé à l'export), mais DD-MM-YYYY est aussi supporté
839
			// le plus simple: YYYY/MM/DD (utilisé à l'export), mais DD-MM-YYYY est aussi supporté
840
			$matches = NULL;
840
			$matches = NULL;
841
			// et on essaie d'être sympa et supporter aussi DD/MM/YYYY
841
			// et on essaie d'être sympa et supporter aussi DD/MM/YYYY
842
			if (preg_match(';^([0-3]?\d)/([01]\d)/([12]\d\d\d)$;', $date, $matches)) {
842
			if (preg_match(';^([0-3]?\d)/([01]\d)/([12]\d\d\d)$;', $date, $matches)) {
843
				$date = $matches[3] . '/' . $matches[2] . '/' . $matches[1];
843
				$date = $matches[3] . '/' . $matches[2] . '/' . $matches[1];
844
			}
844
			}
845
			$timestamp = strtotime($date);
845
			$timestamp = strtotime($date);
846
			if (! $timestamp || $timestamp > time() + 3600 * 24 * 1) { // une journée d'avance maxi autorisée (décallage horaire ?)
846
			if (! $timestamp || $timestamp > time() + 3600 * 24 * 1) { // une journée d'avance maxi autorisée (décallage horaire ?)
847
				if ($date) {
847
				if ($date) {
848
					$msg = "ligne «{$ref_ligne}»: Attention: date erronée ($date)";
848
					$msg = "ligne «{$ref_ligne}»: Attention: date erronée ($date)";
849
					trigger_error($msg, E_USER_NOTICE);
849
					trigger_error($msg, E_USER_NOTICE);
850
				}
850
				}
851
				return NULL;
851
				return NULL;
852
			}
852
			}
853
			return strftime('%Y-%m-%d 00:00:00', $timestamp);
853
			return strftime('%Y-%m-%d 00:00:00', $timestamp);
854
		}
854
		}
855
	}
855
	}
856
 
856
 
857
	static function identReferentiel($referentiel, $ligne, $ref_ligne) {
857
	static function identReferentiel($referentiel, $ligne, $ref_ligne) {
858
		// SELECT DISTINCT nom_referentiel, COUNT(id_observation) AS count FROM cel_obs GROUP BY nom_referentiel ORDER BY count DESC;
858
		// SELECT DISTINCT nom_referentiel, COUNT(id_observation) AS count FROM cel_obs GROUP BY nom_referentiel ORDER BY count DESC;
859
		if (strpos($referentiel, 'bdtfx') !== FALSE) {
859
		if (strpos($referentiel, 'bdtfx') !== FALSE) {
860
			return 'bdtfx'; //:v1.01';
860
			return 'bdtfx'; //:v1.01';
861
		}
861
		}
862
		if (strpos($referentiel, 'bdtxa') !== FALSE) {
862
		if (strpos($referentiel, 'bdtxa') !== FALSE) {
863
			return 'bdtxa'; //:v1.00';
863
			return 'bdtxa'; //:v1.00';
864
		}
864
		}
865
		if (strpos($referentiel, 'bdnff') !== FALSE) {
865
		if (strpos($referentiel, 'bdnff') !== FALSE) {
866
			return 'bdtfx';
866
			return 'bdtfx';
867
		}
867
		}
868
		if (strpos($referentiel, 'isfan') !== FALSE) {
868
		if (strpos($referentiel, 'isfan') !== FALSE) {
869
			return 'isfan'; //:v1.00';
869
			return 'isfan'; //:v1.00';
870
		}
870
		}
871
		if (strpos($referentiel, 'apd') !== FALSE) {
871
		if (strpos($referentiel, 'apd') !== FALSE) {
872
			return 'apd'; //:v1.00';
872
			return 'apd'; //:v1.00';
873
		}
873
		}
874
		if (strpos($referentiel, 'autre') !== FALSE) {
874
		if (strpos($referentiel, 'autre') !== FALSE) {
875
			return 'autre';
875
			return 'autre';
876
		}
876
		}
877
 
877
 
878
		if ($referentiel && isset($ligne[C_NOM_SEL]) && $ligne[C_NOM_SEL]) {
878
		if ($referentiel && isset($ligne[C_NOM_SEL]) && $ligne[C_NOM_SEL]) {
879
			$msg = "ligne «{$ref_ligne}»: Attention: référentiel «{$referentiel}» inconnu";
879
			$msg = "ligne «{$ref_ligne}»: Attention: référentiel «{$referentiel}» inconnu";
880
			trigger_error($msg, E_USER_NOTICE);
880
			trigger_error($msg, E_USER_NOTICE);
881
			return 'autre';
881
			return 'autre';
882
		}
882
		}
883
		return NULL;
883
		return NULL;
884
	}
884
	}
885
 
885
 
886
	static function traiterLonLat($lon = NULL, $lat = NULL, $referentiel = 'bdtfx', $ref_ligne) {
886
	static function traiterLonLat($lon = NULL, $lat = NULL, $referentiel = 'bdtfx', $ref_ligne) {
887
		// en CSV ces valeurs sont des string, avec séparateur en français (","; cf défauts dans ExportXLS)
887
		// en CSV ces valeurs sont des string, avec séparateur en français (","; cf défauts dans ExportXLS)
888
		if ($lon && is_string($lon)) {
888
		if ($lon && is_string($lon)) {
889
			$lon = str_replace(',', '.', $lon);
889
			$lon = str_replace(',', '.', $lon);
890
		}
890
		}
891
		if ($lat && is_string($lat)) {
891
		if ($lat && is_string($lat)) {
892
			$lat = str_replace(',', '.', $lat);
892
			$lat = str_replace(',', '.', $lat);
893
		}
893
		}
894
 
894
 
895
		// sprintf applique une précision à 5 décimale (comme le ferait MySQL)
895
		// sprintf applique une précision à 5 décimale (comme le ferait MySQL)
896
		// tout en uniformisant le format de séparateur des décimales (le ".")
896
		// tout en uniformisant le format de séparateur des décimales (le ".")
897
		if ($lon && is_numeric($lon) && $lon >= -180 && $lon <= 180) {
897
		if ($lon && is_numeric($lon) && $lon >= -180 && $lon <= 180) {
898
			return sprintf('%.5F', $lon);
898
			return sprintf('%.5F', $lon);
899
		}
899
		}
900
		if ($lat && is_numeric($lat) && $lat >= -90 && $lat <= 90) {
900
		if ($lat && is_numeric($lat) && $lat >= -90 && $lat <= 90) {
901
			return sprintf('%.5F', $lat);
901
			return sprintf('%.5F', $lat);
902
		}
902
		}
903
 
903
 
904
		if ($lon || $lat) {
904
		if ($lon || $lat) {
905
			trigger_error("ligne \"{$ref_ligne}\": " .
905
			trigger_error("ligne \"{$ref_ligne}\": " .
906
				  "Attention: longitude ou latitude erronée",
906
				  "Attention: longitude ou latitude erronée",
907
				  E_USER_NOTICE);
907
				  E_USER_NOTICE);
908
		}
908
		}
909
		return NULL;
909
		return NULL;
910
	}
910
	}
911
 
911
 
912
	/*
912
	/*
913
	  TODO: s'affranchir du webservice pour la détermination du nom scientifique en s'appuyant sur cel_references,
913
	  TODO: s'affranchir du webservice pour la détermination du nom scientifique en s'appuyant sur cel_references,
914
	  pour des questions de performances
914
	  pour des questions de performances
915
	*/
915
	*/
916
	static function traiterEspece($ligne, Array &$espece, &$referentiel, $taxon_info_webservice) {
916
	static function traiterEspece($ligne, Array &$espece, &$referentiel, $taxon_info_webservice) {
917
		if (empty($ligne[C_NOM_SEL])) {
917
		if (empty($ligne[C_NOM_SEL])) {
918
			return;
918
			return;
919
		}
919
		}
920
 
920
 
921
		// nom_sel reste toujours celui de l'utilisateur
921
		// nom_sel reste toujours celui de l'utilisateur
922
		$espece[C_NOM_SEL] = trim($ligne[C_NOM_SEL]);
922
		$espece[C_NOM_SEL] = trim($ligne[C_NOM_SEL]);
923
 
923
 
924
		// XXX/attention, nous ne devrions pas accepter un référentiel absent !
924
		// XXX/attention, nous ne devrions pas accepter un référentiel absent !
925
		if (!$referentiel) {
925
		if (!$referentiel) {
926
			$referentiel = 'bdtfx';
926
			$referentiel = 'bdtfx';
927
		}
927
		}
928
		$taxon_info_webservice->setReferentiel($referentiel);
928
		$taxon_info_webservice->setReferentiel($referentiel);
929
		$ascii = iconv('UTF-8', 'ASCII//TRANSLIT', $ligne[C_NOM_SEL]);
929
		$ascii = iconv('UTF-8', 'ASCII//TRANSLIT', $ligne[C_NOM_SEL]);
930
 
930
 
931
		$determ = $taxon_info_webservice->rechercherInfosSurTexteCodeOuNumTax(trim($ligne[C_NOM_SEL]));
931
		$determ = $taxon_info_webservice->rechercherInfosSurTexteCodeOuNumTax(trim($ligne[C_NOM_SEL]));
932
 
932
 
933
		// note: rechercherInfosSurTexteCodeOuNumTax peut ne retourner qu'une seule clef "nom_sel"
933
		// note: rechercherInfosSurTexteCodeOuNumTax peut ne retourner qu'une seule clef "nom_sel"
934
		if (! $determ) {
934
		if (! $determ) {
935
			// on supprime les noms retenus et renvoi tel quel
935
			// on supprime les noms retenus et renvoi tel quel
936
			// on réutilise les define pour les noms d'indexes, tant qu'à faire
936
			// on réutilise les define pour les noms d'indexes, tant qu'à faire
937
			// XXX; tout à NULL sauf C_NOM_SEL ci-dessus ?
937
			// XXX; tout à NULL sauf C_NOM_SEL ci-dessus ?
938
			$espece[C_NOM_SEL_NN] = @$ligne[C_NOM_SEL_NN];
938
			$espece[C_NOM_SEL_NN] = @$ligne[C_NOM_SEL_NN];
939
			$espece[C_NOM_RET] = @$ligne[C_NOM_RET];
939
			$espece[C_NOM_RET] = @$ligne[C_NOM_RET];
940
			$espece[C_NOM_RET_NN] = @$ligne[C_NOM_RET_NN];
940
			$espece[C_NOM_RET_NN] = @$ligne[C_NOM_RET_NN];
941
			$espece[C_NT] = @$ligne[C_NT];
941
			$espece[C_NT] = @$ligne[C_NT];
942
			$espece[C_FAMILLE] = @$ligne[C_FAMILLE];
942
			$espece[C_FAMILLE] = @$ligne[C_FAMILLE];
943
 
943
 
944
			return;
944
			return;
945
		}
945
		}
946
 
946
 
947
		// succès de la détection, mais résultat partiel
947
		// succès de la détection, mais résultat partiel
948
		if (!isset($determ->id)) {
948
		if (!isset($determ->id)) {
949
			$determ = $taxon_info_webservice->effectuerRequeteInfosComplementairesSurNumNom($determ->{"nom_retenu.id"});
949
			$determ = $taxon_info_webservice->effectuerRequeteInfosComplementairesSurNumNom($determ->{"nom_retenu.id"});
950
		}
950
		}
951
 
951
 
952
		// ne devrait jamais arriver !
952
		// ne devrait jamais arriver !
953
		if (!$determ) {
953
		if (!$determ) {
954
			die("erreur critique: " . __FILE__ . ':' . __LINE__);
954
			die("erreur critique: " . __FILE__ . ':' . __LINE__);
955
		}
955
		}
956
 
956
 
957
		// un schéma <ref>:(nt|nn):<num> (ie: bdtfx:nt:8503) a été passé
957
		// un schéma <ref>:(nt|nn):<num> (ie: bdtfx:nt:8503) a été passé
958
		// dans ce cas on met à jour le référentiel avec celui passé dans le champ espèce
958
		// dans ce cas on met à jour le référentiel avec celui passé dans le champ espèce
959
		if (isset($determ->ref)) {
959
		if (isset($determ->ref)) {
960
			$referentiel = $determ->ref;
960
			$referentiel = $determ->ref;
961
		}
961
		}
962
 
962
 
963
		// succès de la détection
963
		// succès de la détection
964
		// nom_sel est remplacé, mais seulement si un motif spécial à été utilisé (bdtfx:nn:4567)
964
		// nom_sel est remplacé, mais seulement si un motif spécial à été utilisé (bdtfx:nn:4567)
965
		if ($taxon_info_webservice->is_notation_spe) {
965
		if ($taxon_info_webservice->is_notation_spe) {
966
			$espece[C_NOM_SEL] = $determ->nom_sci;
966
			$espece[C_NOM_SEL] = $determ->nom_sci;
967
		}
967
		}
968
 
968
 
969
		// écrasement des numéros (nomenclatural, taxonomique) saisis...
969
		// écrasement des numéros (nomenclatural, taxonomique) saisis...
970
		$espece[C_NOM_SEL_NN] = $determ->id;
970
		$espece[C_NOM_SEL_NN] = $determ->id;
971
		$espece[C_NOM_RET] = RechercheInfosTaxonBeta::supprimerBiblio($determ->nom_retenu_complet);
971
		$espece[C_NOM_RET] = RechercheInfosTaxonBeta::supprimerBiblio($determ->nom_retenu_complet);
972
		$espece[C_NOM_RET_NN] = $determ->{"nom_retenu.id"};
972
		$espece[C_NOM_RET_NN] = $determ->{"nom_retenu.id"};
973
		$espece[C_NT] = $determ->num_taxonomique;
973
		$espece[C_NT] = $determ->num_taxonomique;
974
		$espece[C_FAMILLE] = $determ->famille;
974
		$espece[C_FAMILLE] = $determ->famille;
975
		return;
975
		return;
976
	}
976
	}
977
 
977
 
978
	static function detectFromNom($nom) {
978
	static function detectFromNom($nom) {
979
		$r = Cel::db()->requeter(sprintf("SELECT num_nom, num_tax_sup FROM bdtfx_v1_01 WHERE (nom_sci LIKE '%s') ".
979
		$r = Cel::db()->requeter(sprintf("SELECT num_nom, num_tax_sup FROM bdtfx_v1_01 WHERE (nom_sci LIKE '%s') ".
980
			"ORDER BY nom_sci ASC LIMIT 0, 1",
980
			"ORDER BY nom_sci ASC LIMIT 0, 1",
981
			Cel::db()->proteger($nom)));
981
			Cel::db()->proteger($nom)));
982
		if ($r) {
982
		if ($r) {
983
			return $r;
983
			return $r;
984
		}
984
		}
985
 
985
 
986
		Cel::db()->requeter(sprintf("SELECT num_nom, num_tax_sup FROM bdtfx_v1_01 WHERE (nom_sci LIKE '%s' OR nom LIKE '%s') ".
986
		Cel::db()->requeter(sprintf("SELECT num_nom, num_tax_sup FROM bdtfx_v1_01 WHERE (nom_sci LIKE '%s' OR nom LIKE '%s') ".
987
			"ORDER BY nom_sci ASC LIMIT 0, 1",
987
			"ORDER BY nom_sci ASC LIMIT 0, 1",
988
			Cel::db()->proteger($nom),
988
			Cel::db()->proteger($nom),
989
			Cel::db()->proteger(str_replace(' ', '% ', $nom))));
989
			Cel::db()->proteger(str_replace(' ', '% ', $nom))));
990
		return $r;
990
		return $r;
991
	}
991
	}
992
 
992
 
993
	static function traiterLocalisation($ligne, Array &$localisation) {
993
	static function traiterLocalisation($ligne, Array &$localisation) {
994
		if (empty($ligne[C_ZONE_GEO])) {
994
		if (empty($ligne[C_ZONE_GEO])) {
995
			$ligne[C_ZONE_GEO] = NULL;
995
			$ligne[C_ZONE_GEO] = NULL;
996
		}
996
		}
997
		if (empty($ligne[C_CE_ZONE_GEO])) {
997
		if (empty($ligne[C_CE_ZONE_GEO])) {
998
			$ligne[C_CE_ZONE_GEO] = NULL;
998
			$ligne[C_CE_ZONE_GEO] = NULL;
999
		}
999
		}
1000
 
1000
 
1001
		$identifiant_commune = trim($ligne[C_ZONE_GEO]);
1001
		$identifiant_commune = trim($ligne[C_ZONE_GEO]);
1002
		if (!$identifiant_commune) {
1002
		if (!$identifiant_commune) {
1003
			$departement = trim($ligne[C_CE_ZONE_GEO]);
1003
			$departement = trim($ligne[C_CE_ZONE_GEO]);
1004
 
1004
 
1005
			if (strpos($departement, 'INSEE-C:', 0) === 0) {
1005
			if (strpos($departement, 'INSEE-C:', 0) === 0) {
1006
				$localisation[C_CE_ZONE_GEO] = trim($ligne[C_CE_ZONE_GEO]);
1006
				$localisation[C_CE_ZONE_GEO] = trim($ligne[C_CE_ZONE_GEO]);
1007
				if (array_key_exists($localisation[C_CE_ZONE_GEO], self::$cache['geo'])) {
1007
				if (array_key_exists($localisation[C_CE_ZONE_GEO], self::$cache['geo'])) {
1008
					$localisation[C_ZONE_GEO] = self::$cache['geo'][$localisation[C_CE_ZONE_GEO]];
1008
					$localisation[C_ZONE_GEO] = self::$cache['geo'][$localisation[C_CE_ZONE_GEO]];
1009
				} else {
1009
				} else {
1010
					$nom = Cel::db()->requeter(sprintf("SELECT nom FROM cel_zones_geo WHERE code = %s LIMIT 1",
1010
					$nom = Cel::db()->requeter(sprintf("SELECT nom FROM cel_zones_geo WHERE code = %s LIMIT 1",
1011
						self::quoteNonNull(substr($localisation[C_CE_ZONE_GEO], strlen("INSEE-C:")))));
1011
						self::quoteNonNull(substr($localisation[C_CE_ZONE_GEO], strlen("INSEE-C:")))));
1012
					if ($nom) {
1012
					if ($nom) {
1013
						$localisation[C_ZONE_GEO] = $nom[0]['nom'];
1013
						$localisation[C_ZONE_GEO] = $nom[0]['nom'];
1014
					}
1014
					}
1015
					self::$cache['geo'][$localisation[C_CE_ZONE_GEO]] = @$nom[0]['nom'];
1015
					self::$cache['geo'][$localisation[C_CE_ZONE_GEO]] = @$nom[0]['nom'];
1016
				}
1016
				}
1017
				return;
1017
				return;
1018
			}
1018
			}
1019
 
1019
 
1020
			if (!is_numeric($departement)) {
1020
			if (!is_numeric($departement)) {
1021
				$localisation[C_CE_ZONE_GEO] = $ligne[C_CE_ZONE_GEO];
1021
				$localisation[C_CE_ZONE_GEO] = $ligne[C_CE_ZONE_GEO];
1022
				return;
1022
				return;
1023
			}
1023
			}
1024
 
1024
 
1025
			$cache_attempted = FALSE;
1025
			$cache_attempted = FALSE;
1026
			if(array_key_exists($departement, self::$cache['geo'])) {
1026
			if(array_key_exists($departement, self::$cache['geo'])) {
1027
				$cache_attempted = TRUE;
1027
				$cache_attempted = TRUE;
1028
				if (self::$cache['geo'][$departement][0] && self::$cache['geo'][$departement][1]) {
1028
				if (self::$cache['geo'][$departement][0] && self::$cache['geo'][$departement][1]) {
1029
					$localisation[C_ZONE_GEO] = self::$cache['geo'][$departement][0];
1029
					$localisation[C_ZONE_GEO] = self::$cache['geo'][$departement][0];
1030
					$localisation[C_CE_ZONE_GEO] = self::$cache['geo'][$departement][1];
1030
					$localisation[C_CE_ZONE_GEO] = self::$cache['geo'][$departement][1];
1031
					return;
1031
					return;
1032
				}
1032
				}
1033
			}
1033
			}
1034
 
1034
 
1035
			$requete = "SELECT DISTINCT nom, CONCAT('INSEE-C:', code) AS code ".
1035
			$requete = "SELECT DISTINCT nom, CONCAT('INSEE-C:', code) AS code ".
1036
				'FROM cel_zones_geo '.
1036
				'FROM cel_zones_geo '.
1037
				'WHERE code = %s '.
1037
				'WHERE code = %s '.
1038
				'LIMIT 1 '.
1038
				'LIMIT 1 '.
1039
				' -- '.__FILE__.':'.__LINE__;
1039
				' -- '.__FILE__.':'.__LINE__;
1040
			$resultat_commune = Cel::db()->requeter(sprintf($requete, self::quoteNonNull($departement)));
1040
			$resultat_commune = Cel::db()->requeter(sprintf($requete, self::quoteNonNull($departement)));
1041
			if (! $cache_attempted && $resultat_commune) {
1041
			if (! $cache_attempted && $resultat_commune) {
1042
				$localisation[C_ZONE_GEO] = $resultat_commune[0]['nom'];
1042
				$localisation[C_ZONE_GEO] = $resultat_commune[0]['nom'];
1043
				$localisation[C_CE_ZONE_GEO] = $resultat_commune[0]['code'];
1043
				$localisation[C_CE_ZONE_GEO] = $resultat_commune[0]['code'];
1044
				self::$cache['geo'][$departement] = array($resultat_commune[0]['nom'], $resultat_commune[0]['code']);
1044
				self::$cache['geo'][$departement] = array($resultat_commune[0]['nom'], $resultat_commune[0]['code']);
1045
				return;
1045
				return;
1046
			}
1046
			}
1047
			$localisation[C_CE_ZONE_GEO] = $ligne[C_CE_ZONE_GEO];
1047
			$localisation[C_CE_ZONE_GEO] = $ligne[C_CE_ZONE_GEO];
1048
			return;
1048
			return;
1049
		}
1049
		}
1050
 
1050
 
1051
		$select = "SELECT DISTINCT nom, code FROM cel_zones_geo";
1051
		$select = "SELECT DISTINCT nom, code FROM cel_zones_geo";
1052
 
1052
 
1053
		if (preg_match('/(.+) \((\d{1,5})\)/', $identifiant_commune, $elements)) {
1053
		if (preg_match('/(.+) \((\d{1,5})\)/', $identifiant_commune, $elements)) {
1054
			// commune + departement : montpellier (34)
1054
			// commune + departement : montpellier (34)
1055
			$nom_commune=$elements[1];
1055
			$nom_commune=$elements[1];
1056
			$code_commune=$elements[2];
1056
			$code_commune=$elements[2];
1057
			if (strlen($code_commune) <= 2) {
1057
			if (strlen($code_commune) <= 2) {
1058
				$requete = sprintf("%s WHERE nom = %s AND code LIKE %s",
1058
				$requete = sprintf("%s WHERE nom = %s AND code LIKE %s",
1059
					$select, self::quoteNonNull($nom_commune),
1059
					$select, self::quoteNonNull($nom_commune),
1060
					self::quoteNonNull($code_commune.'%'));
1060
					self::quoteNonNull($code_commune.'%'));
1061
			} else {
1061
			} else {
1062
				$requete = sprintf("%s WHERE nom = %s AND code = %d",
1062
				$requete = sprintf("%s WHERE nom = %s AND code = %d",
1063
					$select, self::quoteNonNull($nom_commune),
1063
					$select, self::quoteNonNull($nom_commune),
1064
					$code_commune);
1064
					$code_commune);
1065
			}
1065
			}
1066
		} elseif (preg_match('/^(\d+|(2[ab]\d+))$/i', $identifiant_commune, $elements)) {
1066
		} elseif (preg_match('/^(\d+|(2[ab]\d+))$/i', $identifiant_commune, $elements)) {
1067
			// Code insee seul
1067
			// Code insee seul
1068
			$code_insee_commune=$elements[1];
1068
			$code_insee_commune=$elements[1];
1069
			$requete = sprintf("%s WHERE code = %s", $select, self::quoteNonNull($code_insee_commune));
1069
			$requete = sprintf("%s WHERE code = %s", $select, self::quoteNonNull($code_insee_commune));
1070
		} else {
1070
		} else {
1071
			// Commune seule (le departement sera recupere dans la colonne departement si elle est presente)
1071
			// Commune seule (le departement sera recupere dans la colonne departement si elle est presente)
1072
			// on prend le risque ici de retourner une mauvaise Commune
1072
			// on prend le risque ici de retourner une mauvaise Commune
1073
			$nom_commune = str_replace(" ", "%", iconv('UTF-8', 'ASCII//TRANSLIT', $identifiant_commune));
1073
			$nom_commune = str_replace(" ", "%", iconv('UTF-8', 'ASCII//TRANSLIT', $identifiant_commune));
1074
			$requete = sprintf("%s WHERE nom LIKE %s", $select, self::quoteNonNull($nom_commune.'%'));
1074
			$requete = sprintf("%s WHERE nom LIKE %s", $select, self::quoteNonNull($nom_commune.'%'));
1075
		}
1075
		}
1076
 
1076
 
1077
		if (array_key_exists($identifiant_commune, self::$cache['geo'])) {
1077
		if (array_key_exists($identifiant_commune, self::$cache['geo'])) {
1078
			$resultat_commune = self::$cache['geo'][$identifiant_commune];
1078
			$resultat_commune = self::$cache['geo'][$identifiant_commune];
1079
		} else {
1079
		} else {
1080
			$resultat_commune = Cel::db()->requeter($requete);
1080
			$resultat_commune = Cel::db()->requeter($requete);
1081
			self::$cache['geo'][$identifiant_commune] = $resultat_commune;
1081
			self::$cache['geo'][$identifiant_commune] = $resultat_commune;
1082
		}
1082
		}
1083
 
1083
 
1084
		// cas de la commune introuvable dans le référentiel
1084
		// cas de la commune introuvable dans le référentiel
1085
		// réinitialisation aux valeurs du fichier XLS
1085
		// réinitialisation aux valeurs du fichier XLS
1086
		if (! $resultat_commune) {
1086
		if (! $resultat_commune) {
1087
			$localisation[C_ZONE_GEO] = trim($ligne[C_ZONE_GEO]);
1087
			$localisation[C_ZONE_GEO] = trim($ligne[C_ZONE_GEO]);
1088
			$localisation[C_CE_ZONE_GEO] = trim($ligne[C_CE_ZONE_GEO]);
1088
			$localisation[C_CE_ZONE_GEO] = trim($ligne[C_CE_ZONE_GEO]);
1089
		} else {
1089
		} else {
1090
			$localisation[C_ZONE_GEO] = $resultat_commune[0]['nom'];
1090
			$localisation[C_ZONE_GEO] = $resultat_commune[0]['nom'];
1091
			$localisation[C_CE_ZONE_GEO] = "INSEE-C:" . $resultat_commune[0]['code'];
1091
			$localisation[C_CE_ZONE_GEO] = "INSEE-C:" . $resultat_commune[0]['code'];
1092
			return;
1092
			return;
1093
		}
1093
		}
1094
 
1094
 
1095
		$departement =& $localisation[C_CE_ZONE_GEO];
1095
		$departement =& $localisation[C_CE_ZONE_GEO];
1096
 
1096
 
1097
		if (strpos($departement, "INSEE-C:", 0) === 0) {
1097
		if (strpos($departement, "INSEE-C:", 0) === 0) {
1098
			$localisation[C_ZONE_GEO] = $localisation[C_ZONE_GEO];
1098
			$localisation[C_ZONE_GEO] = $localisation[C_ZONE_GEO];
1099
			$localisation[C_CE_ZONE_GEO] = $localisation[C_CE_ZONE_GEO];
1099
			$localisation[C_CE_ZONE_GEO] = $localisation[C_CE_ZONE_GEO];
1100
		}
1100
		}
1101
 
1101
 
1102
		if (!is_numeric($departement)) {
1102
		if (!is_numeric($departement)) {
1103
			$localisation[C_ZONE_GEO] = $localisation[C_ZONE_GEO];
1103
			$localisation[C_ZONE_GEO] = $localisation[C_ZONE_GEO];
1104
			$localisation[C_CE_ZONE_GEO] = $localisation[C_CE_ZONE_GEO];
1104
			$localisation[C_CE_ZONE_GEO] = $localisation[C_CE_ZONE_GEO];
1105
		}
1105
		}
1106
 
1106
 
1107
		if (strlen($departement) == 4) {
1107
		if (strlen($departement) == 4) {
1108
			$departement = "INSEE-C:0$departement";
1108
			$departement = "INSEE-C:0$departement";
1109
		}
1109
		}
1110
		if (strlen($departement) == 5) {
1110
		if (strlen($departement) == 5) {
1111
			$departement = "INSEE-C:$departement";
1111
			$departement = "INSEE-C:$departement";
1112
		}
1112
		}
1113
		$departement = trim($departement);
1113
		$departement = trim($departement);
1114
 
1114
 
1115
		$localisation[C_ZONE_GEO] = $localisation[C_ZONE_GEO];
1115
		$localisation[C_ZONE_GEO] = $localisation[C_ZONE_GEO];
1116
		$localisation[C_CE_ZONE_GEO] = $localisation[C_CE_ZONE_GEO];
1116
		$localisation[C_CE_ZONE_GEO] = $localisation[C_CE_ZONE_GEO];
1117
	}
1117
	}
1118
 
1118
 
1119
	public static function stockerChampsEtendus($champs_etendus, $ordre_ids, $config) {
1119
	public static function stockerChampsEtendus($champs_etendus, $ordre_ids, $config) {
1120
		// singleton du pauvre mais l'export est suffisamment inefficace pour s'en priver
1120
		// singleton du pauvre mais l'export est suffisamment inefficace pour s'en priver
1121
		self::$gestion_champs_etendus = self::$gestion_champs_etendus == null ?
1121
		self::$gestion_champs_etendus = self::$gestion_champs_etendus == null ?
1122
			new GestionChampsEtendus($config, 'obs') :
1122
			new GestionChampsEtendus($config, 'obs') :
1123
			self::$gestion_champs_etendus;
1123
			self::$gestion_champs_etendus;
1124
 
1124
 
1125
		$champs_etendus_obs = array();
1125
		$champs_etendus_obs = array();
1126
		foreach ($champs_etendus as $champ_etendu_a_obs) {
1126
		foreach ($champs_etendus as $champ_etendu_a_obs) {
1127
			$id_obs = $ordre_ids[$champ_etendu_a_obs['ordre']]; // id réel de l'observation correspondant à l'ordre
1127
			$id_obs = $ordre_ids[$champ_etendu_a_obs['ordre']]; // id réel de l'observation correspondant à l'ordre
1128
			foreach ($champ_etendu_a_obs['champs_etendus'] as $label => $champ) {
1128
			foreach ($champ_etendu_a_obs['champs_etendus'] as $label => $champ) {
1129
				// XXX: insère t'on des valeurs vides ?
1129
				// XXX: insère t'on des valeurs vides ?
1130
				$valeur = $champ;
1130
				$valeur = $champ;
1131
				$cle = $label;
1131
				$cle = $label;
1132
 
1132
 
1133
				if (!empty($cle) && !empty($valeur)) {
1133
				if (!empty($cle) && !empty($valeur)) {
1134
					$champ_etendu_a_inserer = new ChampEtendu();
1134
					$champ_etendu_a_inserer = new ObsEtendue();
1135
					$champ_etendu_a_inserer->id = $id_obs;
1135
					$champ_etendu_a_inserer->id = $id_obs;
1136
					$champ_etendu_a_inserer->cle = $cle;
1136
					$champ_etendu_a_inserer->cle = $cle;
1137
					$champ_etendu_a_inserer->valeur = $valeur;
1137
					$champ_etendu_a_inserer->valeur = $valeur;
1138
 
1138
 
1139
					$champs_etendus_obs[] = $champ_etendu_a_inserer;
1139
					$champs_etendus_obs[] = $champ_etendu_a_inserer;
1140
				}
1140
				}
1141
			}
1141
			}
1142
		}
1142
		}
1143
 
1143
 
1144
		self::$gestion_champs_etendus->ajouterParLots($champs_etendus_obs);
1144
		self::$gestion_champs_etendus->ajouterParLots($champs_etendus_obs);
1145
		//TODO: que faire si l'insertion des champs étendus échoue ?
1145
		//TODO: que faire si l'insertion des champs étendus échoue ?
1146
		return count($champs_etendus_obs);
1146
		return count($champs_etendus_obs);
1147
	}
1147
	}
1148
 
1148
 
1149
	/* HELPERS */
1149
	/* HELPERS */
1150
 
1150
 
1151
	// http://stackoverflow.com/questions/348410/sort-an-array-based-on-another-array
1151
	// http://stackoverflow.com/questions/348410/sort-an-array-based-on-another-array
1152
	// XXX; utilisé aussi (temporairement ?) par FormateurGroupeColonne.
1152
	// XXX; utilisé aussi (temporairement ?) par FormateurGroupeColonne.
1153
	static function sortArrayByArray($array, $orderArray) {
1153
	static function sortArrayByArray($array, $orderArray) {
1154
		$ordered = array();
1154
		$ordered = array();
1155
		foreach($orderArray as $key) {
1155
		foreach($orderArray as $key) {
1156
			if (array_key_exists($key, $array)) {
1156
			if (array_key_exists($key, $array)) {
1157
				$ordered[$key] = $array[$key];
1157
				$ordered[$key] = $array[$key];
1158
				unset($array[$key]);
1158
				unset($array[$key]);
1159
			}
1159
			}
1160
		}
1160
		}
1161
		return $ordered + $array;
1161
		return $ordered + $array;
1162
	}
1162
	}
1163
 
1163
 
1164
	// retourne une BBox [N,S,E,O) pour un référentiel donné
1164
	// retourne une BBox [N,S,E,O) pour un référentiel donné
1165
	static function getReferentielBBox($referentiel) {
1165
	static function getReferentielBBox($referentiel) {
1166
		if ($referentiel == 'bdtfx') {
1166
		if ($referentiel == 'bdtfx') {
1167
			return Array(
1167
			return Array(
1168
				'NORD' => 51.2, // Dunkerque
1168
				'NORD' => 51.2, // Dunkerque
1169
				'SUD' => 41.3, // Bonifacio
1169
				'SUD' => 41.3, // Bonifacio
1170
				'EST' => 9.7, // Corse
1170
				'EST' => 9.7, // Corse
1171
				'OUEST' => -5.2); // Ouessan
1171
				'OUEST' => -5.2); // Ouessan
1172
		}
1172
		}
1173
		return FALSE;
1173
		return FALSE;
1174
	}
1174
	}
1175
 
1175
 
1176
	// ces valeurs ne sont pas inséré via les placeholders du PDO::preparedStatement
1176
	// ces valeurs ne sont pas inséré via les placeholders du PDO::preparedStatement
1177
	// et doivent donc être échappées correctement.
1177
	// et doivent donc être échappées correctement.
1178
	public function initialiser_colonnes_statiques() {
1178
	public function initialiser_colonnes_statiques() {
1179
		$this->colonnes_statiques = array_merge($this->colonnes_statiques,
1179
		$this->colonnes_statiques = array_merge($this->colonnes_statiques,
1180
			array(
1180
			array(
1181
				'ce_utilisateur' => self::quoteNonNull($this->id_utilisateur), // peut-être un hash ou un id
1181
				'ce_utilisateur' => self::quoteNonNull($this->id_utilisateur), // peut-être un hash ou un id
1182
				'prenom_utilisateur' => self::quoteNonNull($this->utilisateur['prenom']),
1182
				'prenom_utilisateur' => self::quoteNonNull($this->utilisateur['prenom']),
1183
				'nom_utilisateur' => self::quoteNonNull($this->utilisateur['nom']),
1183
				'nom_utilisateur' => self::quoteNonNull($this->utilisateur['nom']),
1184
				'courriel_utilisateur' => self::quoteNonNull($this->utilisateur['courriel']),
1184
				'courriel_utilisateur' => self::quoteNonNull($this->utilisateur['courriel']),
1185
			));
1185
			));
1186
	}
1186
	}
1187
 
1187
 
1188
	static function initialiser_pdo_ordered_statements($colonnes_statiques) {
1188
	static function initialiser_pdo_ordered_statements($colonnes_statiques) {
1189
		return Array(
1189
		return Array(
1190
			// insert_ligne_pattern_ordre
1190
			// insert_ligne_pattern_ordre
1191
			sprintf('INSERT INTO cel_obs (%s, %s) VALUES',
1191
			sprintf('INSERT INTO cel_obs (%s, %s) VALUES',
1192
				implode(', ', array_keys($colonnes_statiques)),
1192
				implode(', ', array_keys($colonnes_statiques)),
1193
				implode(', ', array_diff(self::$ordre_BDD, array_keys($colonnes_statiques)))),
1193
				implode(', ', array_diff(self::$ordre_BDD, array_keys($colonnes_statiques)))),
1194
 
1194
 
1195
			// insert_ligne_pattern_ordre
1195
			// insert_ligne_pattern_ordre
1196
			sprintf('(%s, %s ?)',
1196
			sprintf('(%s, %s ?)',
1197
				implode(', ', $colonnes_statiques),
1197
				implode(', ', $colonnes_statiques),
1198
				str_repeat('?, ', count(self::$ordre_BDD) - count($colonnes_statiques) - 1))
1198
				str_repeat('?, ', count(self::$ordre_BDD) - count($colonnes_statiques) - 1))
1199
		);
1199
		);
1200
	}
1200
	}
1201
 
1201
 
1202
	static function initialiser_pdo_statements($colonnes_statiques) {
1202
	static function initialiser_pdo_statements($colonnes_statiques) {
1203
		return Array(
1203
		return Array(
1204
			// insert_prefix
1204
			// insert_prefix
1205
			sprintf('INSERT INTO cel_obs (%s) VALUES ',
1205
			sprintf('INSERT INTO cel_obs (%s) VALUES ',
1206
				implode(', ', self::$ordre_BDD)),
1206
				implode(', ', self::$ordre_BDD)),
1207
 
1207
 
1208
 
1208
 
1209
			// insert_ligne_pattern, cf: self::$insert_ligne_pattern
1209
			// insert_ligne_pattern, cf: self::$insert_ligne_pattern
1210
			'(' .
1210
			'(' .
1211
			// 3) créé une chaîne de liste de champ à inséré en DB
1211
			// 3) créé une chaîne de liste de champ à inséré en DB
1212
			implode(', ', array_values(
1212
			implode(', ', array_values(
1213
			// 2) garde les valeurs fixes (de $colonnes_statiques),
1213
			// 2) garde les valeurs fixes (de $colonnes_statiques),
1214
			// mais remplace les NULL par des "?"
1214
			// mais remplace les NULL par des "?"
1215
			array_map('__anonyme_5',
1215
			array_map('__anonyme_5',
1216
				  // 1) créé un tableau genre (nom_sel_nn => NULL) depuis self::$ordre_BDD
1216
				  // 1) créé un tableau genre (nom_sel_nn => NULL) depuis self::$ordre_BDD
1217
				  // et écrase certaines valeurs avec $colonnes_statiques (initilisé avec les données utilisateur)
1217
				  // et écrase certaines valeurs avec $colonnes_statiques (initilisé avec les données utilisateur)
1218
				  array_merge(array_map('__anonyme_6', array_flip(self::$ordre_BDD)), $colonnes_statiques
1218
				  array_merge(array_map('__anonyme_6', array_flip(self::$ordre_BDD)), $colonnes_statiques
1219
				  )))) .
1219
				  )))) .
1220
			')'
1220
			')'
1221
		);
1221
		);
1222
	}
1222
	}
1223
 
1223
 
1224
	// équivalent à Bdd->proteger() (qui wrap PDO::quote),
1224
	// équivalent à Bdd->proteger() (qui wrap PDO::quote),
1225
	// sans transformer NULL en ""
1225
	// sans transformer NULL en ""
1226
	static function quoteNonNull($chaine) {
1226
	static function quoteNonNull($chaine) {
1227
		if (is_null($chaine)) {
1227
		if (is_null($chaine)) {
1228
			return 'NULL';
1228
			return 'NULL';
1229
		}
1229
		}
1230
		if (!is_string($chaine) && !is_integer($chaine)) {
1230
		if (!is_string($chaine) && !is_integer($chaine)) {
1231
			die('erreur: ' . __FILE__ . ':' . __LINE__);
1231
			die('erreur: ' . __FILE__ . ':' . __LINE__);
1232
		}
1232
		}
1233
		return Cel::db()->quote($chaine);
1233
		return Cel::db()->quote($chaine);
1234
	}
1234
	}
1235
 
1235
 
1236
	public function erreurs_stock($errno, $errstr) {
1236
	public function erreurs_stock($errno, $errstr) {
1237
		$this->bilan[] = $errstr;
1237
		$this->bilan[] = $errstr;
1238
	}
1238
	}
1239
}
1239
}
1240
?>
1240
?>