Subversion Repositories eFlore/Applications.cel

Rev

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

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