Subversion Repositories eFlore/Applications.cel

Rev

Rev 2933 | Go to most recent revision | Only display areas with differences | Ignore whitespace | Details | Blame | Last modification | View Log | RSS feed

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