Subversion Repositories eFlore/Applications.cel

Rev

Rev 2538 | Rev 2729 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
1654 aurelien 1
<?php
2461 jpm 2
// declare(encoding='UTF-8');
1656 raphael 3
/**
2461 jpm 4
 * Classe métier de mise en forme des groupes de colonnes pour les exports.
5
 *
6
 * @internal   Mininum PHP version : 5.2
7
 * @category   CEL
8
 * @package    Services
9
 * @subpackage Bibliothèques
10
 * @version    0.1
11
 * @author     Mathias CHOUET <mathias@tela-botanica.org>
12
 * @author     Raphaël Droz <raphael@tela-botania.org>
13
 * @author     Jean-Pascal MILCENT <jpm@tela-botanica.org>
14
 * @author     Aurelien PERONNET <aurelien@tela-botanica.org>
15
 * @license    GPL v3 <http://www.gnu.org/licenses/gpl.txt>
16
 * @license    CECILL v2 <http://www.cecill.info/licences/Licence_CeCILL_V2-en.txt>
17
 * @copyright  1999-2014 Tela Botanica <accueil@tela-botanica.org>
18
 */
19
define('SEPARATEUR_IMAGES', ' / ');
20
define('PREFIX_CHAMPS_ETENDUS', 'ext:');
1835 raphael 21
// utilisé par formaterUrlUser() [ nécessaire pour le widget d'export)
22
define('USER_BASEURL', 'http://www.tela-botanica.org/profil:%d');
1656 raphael 23
 
2461 jpm 24
class FormateurGroupeColonne {
1656 raphael 25
 
1702 raphael 26
	// cache pour les données des fonctions
1656 raphael 27
	static $cache = Array();
28
 
1702 raphael 29
	// test sur la table cel_references, mis à TRUE si la table existe
30
	static $is_table = false;
31
 
1714 raphael 32
	// les groupes de champs utilisables
33
	static $fieldGroups = array(
34
		'standard',
35
		'avance',
36
		'etendu',
1835 raphael 37
		'baseflor',
38
		'auteur'
1714 raphael 39
	);
40
 
1702 raphael 41
	// les données baseflor à récupérer: colonnes présentes dans cel_references
42
	// et intitulés associés
43
	static $baseflor_col = array(
2461 jpm 44
		've_lumiere' => 'Lumière',
45
		've_temperature' => 'Température',
46
		've_continentalite' => 'Continentalité',
47
		've_humidite_atmos' => 'Humidité Atmosphérique',
48
		've_humidite_edaph' => 'Humidité',
49
		've_reaction_sol' => 'Réaction (pH)',
50
		've_nutriments_sol' => 'Nutriments',
51
		've_salinite' => 'Salinité',
52
		've_texture_sol' => 'Texture' ,
53
		've_mat_org_sol' => 'Matière Organique',
54
		'catminat_code' => 'Code Catminat',
55
		'syntaxon' => 'Syntaxon',
1702 raphael 56
	);
57
 
1741 raphael 58
	// TODO: dirty, ordre des champs étendus... souhaité pour florilèges:
59
	static $ordre_champ_etendus_Florileges = array(
2461 jpm 60
		'personneStructure',
61
		'personneService',
62
		'personneFonction',
63
		'adresse',
64
		'latitudeDebutRue',
65
		'longitudeDebutRue',
66
		'latitudeFinRue',
67
		'longitudeFinRue',
68
		'typoUrbaine',
69
		'revetementSol',
70
		'presenceZoneVegetalise',
71
		'hauteurBatimentAvoisinant',
72
		'intensiteGestion',
73
		'periodiciteTraitementPhyto',
74
		'dateArretTraitementPhyto',
75
		'itineraireGestion',
76
		'dateDerniereIntervention',
77
		'hauteurPlante',
78
		'resistanceTraitementPhyto',
79
		'vitesseCroissance',
80
		'perceptionTechnicien',
81
		'perceptionRiverainMauvaise',
1741 raphael 82
	);
83
 
1714 raphael 84
	static function colGroupsValidation($groupe_de_champs = 'standard,avance') {
2461 jpm 85
		if (! $groupe_de_champs) return FALSE;
86
		if (is_string($groupe_de_champs)) {
1714 raphael 87
			$groupe_de_champs = array_flip(explode(',', $groupe_de_champs));
2461 jpm 88
		} elseif(is_array($groupe_de_champs)) {
1714 raphael 89
			$groupe_de_champs = array_flip($groupe_de_champs);
2461 jpm 90
		} else {
91
			return null;
1714 raphael 92
		}
2461 jpm 93
		$groupe_de_champs = array_intersect_key(array_flip(self::$fieldGroups), $groupe_de_champs);
94
		if (!$groupe_de_champs) {
95
			return false;
1714 raphael 96
		}
97
		// toujours ajouter standard
2461 jpm 98
		$groupe_de_champs['standard'] = true;
1714 raphael 99
		return implode(',', array_keys($groupe_de_champs));
100
	}
101
 
1654 aurelien 102
	/*
103
	* @param $fieldSets: un liste de noms de colonnes ou de sets de colonnes
104
	*		séparés par des virgules
105
	* 		eg: "espece" ou "champs-etendus", ...
106
	*
107
	* @return: un tableau associatif déjà ordonné
108
	* 		clé: abbrev [machine-name] de la colonne (eg: "espece" ou "mot-clef")
109
	* 		valeur: des données relative à cette colonne, cf GenColInfo
110
	*
1757 raphael 111
	* Si la colonne n'utilise pas de fonction de récupération particulière
112
	* (ie: si le champ exportés [ou importé] correspond exactement au champ dans la base de donnée)
113
	* Alors 'abbrev' doit avoir la même valeur que le nom de la colonne dans la table mysql `cel_obs`.
1654 aurelien 114
	*/
115
	static function nomEnsembleVersListeColonnes($groupe_de_champs = 'standard') {
2461 jpm 116
		if (! $groupe_de_champs) {
117
			$groupe_de_champs = 'standard';
118
		}
119
		if (is_string($groupe_de_champs)) {
1711 raphael 120
			$groupe_de_champs = array_flip(explode(',', $groupe_de_champs));
2461 jpm 121
		} elseif(is_array($groupe_de_champs)) {
1711 raphael 122
			$groupe_de_champs = array_flip($groupe_de_champs);
2461 jpm 123
		} else {
124
			return null;
1711 raphael 125
		}
2461 jpm 126
		$groupe_de_champs = array_intersect_key(array_flip(self::$fieldGroups), $groupe_de_champs);
127
		if (!$groupe_de_champs) {
128
			return null;
1711 raphael 129
		}
130
 
2461 jpm 131
		$colonnes = array();
132
		if (isset($groupe_de_champs['standard'])) {
1654 aurelien 133
			$colonnes += Array(
1757 raphael 134
				'nom_sel'			=> self::GenColInfo(Array('abbrev' => 'nom_sel',
135
															  'nom' => 'Espèce')),
136
				'nom_sel_nn'		=> self::GenColInfo(Array('abbrev' => 'nom_sel_nn',
137
															  'nom' => 'Numéro nomenclatural',
138
															  'importable' => FALSE)),
139
				'nom_ret'			=> self::GenColInfo(Array('abbrev' => 'nom_ret',
140
															  'nom' => 'Nom retenu',
141
															  'importable' => FALSE)),
142
				'nom_ret_nn'		=> self::GenColInfo(Array('abbrev' => 'nom_ret_nn',
143
															  'nom' => 'Numéro nomenclatural nom retenu',
144
															  'importable' => FALSE)),
145
				'nt'				=> self::GenColInfo(Array('abbrev' => 'nt',
146
															  'nom' => 'Numéro taxonomique',
147
															  'importable' => FALSE)),
148
				'famille'			=> self::GenColInfo(Array('abbrev' => 'famille',
149
															  'nom' => 'Famille',
150
															  'importable' => FALSE)),
151
				'nom_referentiel'	=> self::GenColInfo(Array('abbrev' => 'nom_referentiel',
152
															  'nom' => 'Referentiel taxonomique')),
2538 aurelien 153
				'pays'				=> self::GenColInfo(Array('abbrev' => 'pays',
154
															  'nom' => 'Pays')),
1757 raphael 155
				'zone_geo'			=> self::GenColInfo(Array('abbrev' => 'zone_geo',
156
															  'nom' => 'Commune')),
157
				'ce_zone_geo'		=> self::GenColInfo(Array('abbrev' => 'ce_zone_geo',
158
															  'nom' => 'Identifiant Commune',
159
															  'fonction' => 'convertirCodeZoneGeoVersDepartement')),
160
				'date_observation'	=> self::GenColInfo(Array('abbrev' => 'date_observation',
161
															  'nom' => 'Date',
162
															  'fonction' => 'formaterDate')),
163
				'lieudit'			=> self::GenColInfo(Array('abbrev' => 'lieudit',
164
															  'nom' => 'Lieu-dit')),
165
				'station'			=> self::GenColInfo(Array('abbrev' => 'station',
166
															  'nom' => 'Station')),
167
				'milieu'			=> self::GenColInfo(Array('abbrev' => 'milieu',
168
															  'nom' => 'Milieu')),
169
				'commentaire'		=> self::GenColInfo(Array('abbrev' => 'commentaire',
170
															  'nom' => 'Notes')),
171
				'latitude'			=> self::GenColInfo(Array('abbrev' => 'latitude',
172
															  'nom' => 'Latitude',
173
															  'extra' => 1,
174
															  'fonction' => 'trim0')),
175
				'longitude'			=> self::GenColInfo(Array('abbrev' => 'longitude',
176
															  'nom' => 'Longitude',
177
															  'extra' => 1,
178
															  'fonction' => 'trim0')),
179
				'altitude'			=> self::GenColInfo(Array('abbrev' => 'altitude',
180
															  'nom' => 'Altitude',
1816 raphael 181
															  'extra' => 1)), // pas de trim0 car INT(5) en DB
1757 raphael 182
				'geodatum'			=> self::GenColInfo(Array('abbrev' => 'geodatum',
183
															  'nom' => 'Référentiel Géographique',
184
															  'extra' => 1,
185
															  'importable' => FALSE)),
1654 aurelien 186
			);
187
		}
2143 jpm 188
 
1654 aurelien 189
		if(isset($groupe_de_champs['avance'])) {
1656 raphael 190
			$colonnes += array(
1757 raphael 191
				// TODO: importable = FALSE car pas de merge de données importées
192
				'ordre'				=> self::GenColInfo(Array('abbrev' => 'ordre',
193
															  'nom' => 'Ordre',
194
															  'extra' => 1,
195
															  'importable' => FALSE)),
196
				'id_observation'	=> self::GenColInfo(Array('abbrev' => 'id_observation',
197
															  'nom' => 'Identifiant',
198
															  'extra' => 1,
199
															  'importable' => FALSE)),
1656 raphael 200
 
1757 raphael 201
				'mots_cles_texte'	=> self::GenColInfo(Array('abbrev' => 'mots_cles_texte',
202
															  'nom' => 'Mots Clés',
203
															  'extra' => 1)),
204
				'date_creation'		=> self::GenColInfo(Array('abbrev' => 'date_creation',
205
															  'nom' => 'Date Création',
206
															  'extra' => 1,
207
															  'importable' => FALSE)),
208
				'date_modification'	=> self::GenColInfo(Array('abbrev' => 'date_modification',
209
															  'nom' => 'Date Modification',
210
															  'extra' => 1,
211
															  'importable' => FALSE)),
1656 raphael 212
 
1757 raphael 213
				// rappel transmission = 1, signifie simplement "public"
214
				// des données importées peuvent être d'emblée "publiques"
215
				// "importable" = TRUE
216
				'transmission'		=> self::GenColInfo(Array('abbrev' => 'transmission',
217
															  'nom' => 'Transmis',
218
															  'extra' => 1,
219
															  'fonction' => 'boolOuiNon')),
220
				'date_transmission'	=> self::GenColInfo(Array('abbrev' => 'date_transmission',
221
															  'nom' => 'Date Transmission',
222
															  'extra' => 1,
223
															  'importable' => FALSE)),
224
				'abondance'			=> self::GenColInfo(Array('abbrev' => 'abondance',
225
															  'nom' => 'Abondance',
226
															  'extra' => 1)),
227
				'certitude'			=> self::GenColInfo(Array('abbrev' => 'certitude',
228
															  'nom' => 'Certitude',
229
															  'extra' => 1)),
230
				'phenologie'		=> self::GenColInfo(Array('abbrev' => 'phenologie',
231
															  'nom' => 'Phénologie',
232
															  'extra' => 1)),
2143 jpm 233
 
1757 raphael 234
				// XXX: getImages() dépend du contexte de Cel, et doit être appelée comme cas particulier
235
				// cf ExportXLS::traiterLigneObservation()
236
				'images'			=> self::GenColInfo(Array('abbrev' => 'images',
237
															  'nom' => 'Image(s)',
238
															  'extra' => 1,
239
															  'fonction_data' => NULL /* cas particulier 'getImages' */,
240
															  'importable' => TRUE,
241
															  //'preload' => array(__CLASS__, 'getImages_preload')//TODO
242
				)),
1656 raphael 243
 
1757 raphael 244
				/* 'nom_commun'			=> self::GenColInfo(Array('abbrev' => 'nom_commun',
245
				   'nom' => 'Nom Commun',
246
				   'extra' => 1,
247
				   'fonction_data' => 'getNomCommun',
248
				   'importable' => FALSE),
1685 raphael 249
 
1757 raphael 250
				   'nom-commun'			=> self::GenColInfo(Array('abbrev' => 'nom-commun',
251
				   'nom' => 'Nom Commun',
252
				   'extra' => 1,
253
				   'fonction_data' => 'getNomCommun_v2'),
254
 
255
				   'nom-commun'			=> self::GenColInfo(Array('abbrev' => 'nom-commun',
256
				   'nom' => 'Nom Commun',
257
				   'extra' => 1,
258
				   'fonction_data' => 'getNomCommun_v3'),
259
				   'importable' => FALSE), */
2461 jpm 260
				'nom-commun' => self::GenColInfo(array(
261
					'abbrev' => 'nom-commun',
262
					'nom' => 'Nom Commun',
263
					'extra' => 1,
264
					'fonction_data' => null /* cas particu 'getNomCommun_v4' */,
265
					'preload' => array(__CLASS__, 'getNomCommun_preload')))
1702 raphael 266
			);
267
		}
1685 raphael 268
 
1702 raphael 269
		if(isset($groupe_de_champs['baseflor'])) {
270
			$colonnes += array(
271
				// champ dynamique
2461 jpm 272
				'baseflor' => self::GenColInfo(array(
273
					'abbrev' => 'baseflor',
274
					'nom' => '',
275
					'extra' => 1,
276
					'importable' => false,
277
					'preload' => array(__CLASS__, 'baseflor_preload'),
278
					'dyna' => array(__CLASS__, 'baseflor_ligne'))),
1654 aurelien 279
			);
1702 raphael 280
		}
1656 raphael 281
 
2461 jpm 282
		if (isset($groupe_de_champs['etendu'])) {
1714 raphael 283
			$colonnes += array(
284
				// champ dynamique
2461 jpm 285
				'etendu' => self::GenColInfo(array(
286
					'abbrev' => 'etendu',
287
					'nom' => '',
288
					'extra' => 1,
289
					'importable' => false,
290
					'preload' => array(__CLASS__, 'champsEtendus_preload'),
291
					'dyna' => array(__CLASS__, 'champsEtendus_ligne'))),
1714 raphael 292
			);
293
		}
1835 raphael 294
 
2461 jpm 295
		if (isset($groupe_de_champs['auteur'])) {
1835 raphael 296
			$colonnes += array(
2461 jpm 297
				'observateur' => self::GenColInfo(array(
298
					'abbrev' => 'observateur',
299
					'nom' => 'Observateur',
300
					'extra' => 1,
301
					'fonction_data' => 'formaterUrlUser',
302
					'importable' => false)),
1835 raphael 303
			);
304
		}
1654 aurelien 305
		return $colonnes;
306
	}
1694 raphael 307
 
308
	static function preload($colonnes, $cel, $ids) {
309
		$result = array();
2461 jpm 310
		foreach ($colonnes as $abbrev => $colonne) {
311
			if (!$colonne['preload']) {
312
				continue;
313
			}
1702 raphael 314
			$result[$abbrev] = call_user_func($colonne['preload'], $cel, $ids);
1694 raphael 315
		}
316
		return $result;
317
	}
2143 jpm 318
 
1656 raphael 319
	public static function getIntitulesColonnes($colonnes) {
1702 raphael 320
		// array_filter pour supprimer les colonnes "dynamique" n'ayant pas défini $nom (cf GenColInfo())
321
		return array_filter(array_map(array('FormateurGroupeColonne', 'retournerNomItem'), $colonnes));
1654 aurelien 322
	}
2143 jpm 323
 
1671 aurelien 324
	public static function retournerNomItem(&$item) {
325
		return $item['nom'];
326
	}
1694 raphael 327
 
1656 raphael 328
	public static function getLigneObservation(&$obs, &$colonnes, $cel = false) {
1654 aurelien 329
		$ligne_formatee = array();
330
		foreach($colonnes as $abbrev => $colonne) {
331
			$valeur = null;
2461 jpm 332
			if ($colonne['extra'] == 2 || ! is_null($colonne['dyna'])) {
333
				continue;
334
			}
2143 jpm 335
 
1835 raphael 336
			// valeur directe depuis cel_obs ?
2461 jpm 337
			if (isset($obs[$abbrev])) {
338
				$valeur = $obs[$abbrev];
339
			}
2143 jpm 340
 
1835 raphael 341
			// pré-processeur des champs
2461 jpm 342
			if (function_exists($colonne['fonction'])) {
1654 aurelien 343
				$valeur = $colonne['fonction']($valeur);
2461 jpm 344
			} else if(method_exists(__CLASS__, $colonne['fonction'])) {
1654 aurelien 345
				$valeur = call_user_func(array(__CLASS__, $colonne['fonction']), $valeur);
2461 jpm 346
			} else if($colonne['fonction']) {
1654 aurelien 347
				die("méthode {$colonne['fonction']} introuvable");
2461 jpm 348
			} else if(function_exists($colonne['fonction_data'])) {// fonction pour obtenir des champs (étendus)
1654 aurelien 349
				$valeur = $colonne['fonction_data']($obs);
2461 jpm 350
			} else if(method_exists(__CLASS__, $colonne['fonction_data'])) {
1654 aurelien 351
				$valeur = call_user_func(array(__CLASS__, $colonne['fonction_data']), $obs);
352
			}
2143 jpm 353
 
1654 aurelien 354
			// // cette section devrait être vide:
355
			// // cas particuliers ingérable avec l'architecture actuelle:
2461 jpm 356
			if (false && $abbrev == 'date_observation' && $valeur == '0000-00-00') {
1654 aurelien 357
				/* blah */
358
			}
1765 raphael 359
			// ici à cause du passage de $cel ($this->utilisateur)
2461 jpm 360
			if ($abbrev == 'images') {
1765 raphael 361
				$valeur = FormateurGroupeColonne::getImages($obs, $cel->id_utilisateur);
1656 raphael 362
			}
2461 jpm 363
			if ($abbrev == 'nom-commun') {
1765 raphael 364
				$valeur = FormateurGroupeColonne::getNomCommun_v4($obs);
1685 raphael 365
			}
2143 jpm 366
 
2461 jpm 367
			if ($valeur == null) {
368
				$valeur = '';
1654 aurelien 369
			}
2143 jpm 370
 
2461 jpm 371
			// fin de section "cas particuliers"
1654 aurelien 372
			$ligne_formatee[] = $valeur;
373
		}
1694 raphael 374
 
1835 raphael 375
		// uniquement les champs dynamiques
1702 raphael 376
		foreach($colonnes as $abbrev => $colonne) {
377
			$valeur = null;
2461 jpm 378
			if (is_null($colonne['dyna'])) {
379
				continue;
380
			}
381
			call_user_func_array($colonne['dyna'], array($obs, &$ligne_formatee));
1702 raphael 382
		}
1654 aurelien 383
		return $ligne_formatee;
384
	}
2143 jpm 385
 
1654 aurelien 386
	/*
387
	* Wrapper générant un tableau associatif:
1694 raphael 388
	* Ne pas changer les valeurs par défaut du prototype sans réflexion sur l'implication pour nomEnsembleVersListeColonnes()
2143 jpm 389
 
1654 aurelien 390
	* @param $abbrev (obligatoire): nom court de colonne, largement utilisé lors de l'import.
391
	*		  En effet chaque ligne importée est accessible à l'aide du `define` de $abbrev en majuscule, préfixé de "C_"
392
	*		  Exemple: $ligne[C_LONGITUDE] pour "longitude".
393
	*		  cf: ImportXLS::detectionEntete()
2143 jpm 394
 
1654 aurelien 395
	* @param $nom (obligatoire): nom complet de colonne (utilisé pour la ligne d'en-tête)
1702 raphael 396
	*		  Les définition de champs dynamique (correspondant à de multiples colonnes) doivent laisser cette valeur
397
	*		  vide afin de ne pas créer une colonne supplémentaire erronée.
2143 jpm 398
 
1654 aurelien 399
	* @param $is_extra:
400
	* Si 0, la colonne est une colonne standard
401
	* Si 1, la colonne est extra [le plus souvent générée automatiquement]
402
	*		 (auquel cas une bordure bleue entoure son nom dans la ligne d'entête)
403
	* Si 2, la colonne n'est pas traité à l'export, mais une définition peut lui être donnée
404
	*		 qui pourra être utilisée à l'import, exemple: "image"
2143 jpm 405
 
1654 aurelien 406
	* @param $fonction (optionnel): un nom d'un fonction de préprocessing
407
	* 		  $fonction doit prendre comme seul argument la valeur d'origine et retourner la valeur transformée
2143 jpm 408
 
1654 aurelien 409
	* @param $fonction_data (optionnel): une *méthode* d'obtention de donnée
410
	* 		  $fonction_data doit prendre comme premier argument le tableau des champs de l'enregistrement existant
411
	*		  $fonction_data doit retourner une valeur
2143 jpm 412
 
1654 aurelien 413
	* @param $importable (optionnel): défini si la colonne est traitée (ou absolument ignorée par PHPExcel) lors de
414
	*		  l'import.
1694 raphael 415
 
416
	* @param $preload (optionnel): défini une fonction de préchargement massif de donnée potentiellement utilisable par $fonction_data.
417
	*		  Utile, notamment, dans le cadre de l'export
1702 raphael 418
 
419
	* @param $fonction_dynamique (optionnel): défini une fonction ajoutant un nombre arbitraire de colonnes à une ligne donnée
420
	*		  Utile, notamment, dans le cadre de l'export des champs étendus ou des données baseflor
421
	*		  La fonction doit TOUJOURS alterer la ligne en lui ajoutant une nombre CONSTANT d'éléments (NULL ou non)
1765 raphael 422
	*		  La fonction doit prendre comme arguments ($obs, &$ligne_formatee)
1654 aurelien 423
	*/
1757 raphael 424
	static function GenColInfo($args) {
2461 jpm 425
		$default = array(
426
			'abbrev' => null,
427
			'nom' => null,
428
			'extra' => 0,
429
			'fonction' => null,
430
			'fonction_data' => null,
431
			'importable' => true,
432
			'preload' => null,
433
			'dyna' => null);
1757 raphael 434
		$ret = array_intersect_key($args, $default);
435
		return array_merge($default, $ret);
1654 aurelien 436
	}
2143 jpm 437
 
1656 raphael 438
	static function formaterDate($date_heure_mysql) {
1671 aurelien 439
		//return "";
2461 jpm 440
		if (!$date_heure_mysql || $date_heure_mysql == "0000-00-00 00:00:00") {
441
			return null;
442
		}
1671 aurelien 443
		// malheureusement pas disponible en php < 5.3
444
		//$date_format = DateTime::createFromFormat("Y-m-d H:i:s", $date_heure_mysql);
445
		$val = explode(' ', $date_heure_mysql);
446
		$date = explode('-', $val[0]);
447
		$heure = explode(':', $val[1]);
448
		$timestamp = mktime((int) $heure[0], (int) $heure[1], (int) $heure[2], (int) $date[1], (int) $date[2], (int) $date[0]);
2461 jpm 449
		if (!$timestamp) {
450
			return null;
451
		}
1656 raphael 452
		// TODO: les widgets ne font malheureusement pas usage de l'heure dans le CEL
453
		// TODO: si modification, ne pas oublier de modifier le format d'import correspondant
1698 raphael 454
		//	dans ImportXLS, traiterDateObs() (actuellement: "Y/m/d" car utilisation de strtotime() qui ne lit pas tout)
455
		// $date_formatee = strftime('%d/%m/%Y', $timestamp);
456
		$date_formatee = strftime('%Y/%m/%d', $timestamp);
2461 jpm 457
		if (!$date_formatee) {
458
			return '00/00/0000';
459
		}
1654 aurelien 460
		return $date_formatee;
461
	}
1757 raphael 462
 
1835 raphael 463
	static function formaterUrlUser($obs) {
464
		$is_id = is_numeric($obs['ce_utilisateur']);
465
		return sprintf("%s %s <%s>%s",
2461 jpm 466
			$obs['prenom_utilisateur'],
467
			$obs['nom_utilisateur'],
468
			preg_replace(';@.*;', '@...', $obs['courriel_utilisateur']),
469
			$is_id ? sprintf(' (' . USER_BASEURL . ')', $obs['ce_utilisateur']) : '');
1835 raphael 470
	}
471
 
1759 raphael 472
	static function getImages_preload($cel, $obsids) {
2461 jpm 473
		if (!$obsids) return;
1766 raphael 474
		$rec = Cel::db()->requeter(
1759 raphael 475
			sprintf("SELECT o.id_observation, GROUP_CONCAT(nom_original ORDER BY nom_original ASC SEPARATOR '%s') AS i " .
2446 jpm 476
					"FROM cel_images i LEFT JOIN cel_obs o ON (i.ce_observation = o.id_observation) " .
1759 raphael 477
					"WHERE o.ce_utilisateur = %d AND o.id_observation IN (%s) " .
478
					"GROUP BY id_observation",
479
					SEPARATEUR_IMAGES,
480
					$cel->id_utilisateur,
481
					implode(',', $obsids)));
2461 jpm 482
		foreach ($rec as $v) {
1759 raphael 483
			self::$cache['getImages'][$v['id_observation']] = $v['i'];
484
		}
2461 jpm 485
		return null;
1759 raphael 486
	}
487
 
1765 raphael 488
	static function getImages($obs, $id_utilisateur) {
1656 raphael 489
		if(! $id_utilisateur) return NULL;
1759 raphael 490
		if(isset(self::$cache['getImages'][$obs['id_observation']]))
491
			return self::$cache['getImages'][$obs['id_observation']];
492
 
1765 raphael 493
		$rec = Cel::db()->requeter(
2446 jpm 494
			sprintf("SELECT GROUP_CONCAT(nom_original ORDER BY nom_original ASC SEPARATOR '%s') AS i ".
495
					"FROM cel_images i ".
496
					"	LEFT JOIN cel_obs o ON (i.ce_observation = o.id_observation) ".
497
					"WHERE o.ce_utilisateur = %d ".
498
					"	AND o.id_observation = %d ".
499
					'LIMIT 1',
1656 raphael 500
					SEPARATEUR_IMAGES,
1757 raphael 501
					$id_utilisateur,
502
					$obs['id_observation']));
503
		return $rec ? $rec[0]['i'] : NULL;
1654 aurelien 504
	}
2143 jpm 505
 
506
	public static function convertirCodeZoneGeoVersDepartement($code_zone_geo) {
1654 aurelien 507
		$code_departement = '';
508
		if(self::estUnCodeInseeDepartement($code_zone_geo)) {
509
			$code_departement = substr(ltrim($code_zone_geo,'INSEE-C:'),0,2);
510
		}
2143 jpm 511
 
1654 aurelien 512
		return $code_departement;
513
	}
1757 raphael 514
 
515
	public static function trim0($lonlat) {
516
		return trim(trim($lonlat, "0"), ".");
517
	}
518
 
519
	public static function boolOuiNon($transmission) {
520
		return $transmission ? 'oui' : '';
521
	}
2143 jpm 522
 
1654 aurelien 523
	public static function estUnCodeInseeDepartement($code_a_tester) {
524
		return preg_match('/^INSEE-C:[0-9]{5}/',$code_a_tester);
525
	}
2143 jpm 526
 
1654 aurelien 527
	// TODO: référentiel ne devrait pas être généré au moment d'un Config::get,
528
	// comme dans Config::get('nomsVernaRechercheLimiteeTpl')
529
	// Par exemple, la variable pour "nva" ?
530
	function getNomCommun($obs) {
531
		$langue = 'fra';
532
		list($referentiel) = explode(':', strtolower($obs['nom_referentiel']));
533
		if($referentiel == 'bdtfx') $referentiel = 'nvjfl';
534
		else return '';
2143 jpm 535
 
1654 aurelien 536
		$cache_id = $referentiel . '-' . $obs['nt'] . '-' . $langue;
1657 raphael 537
		if(isset(self::$cache['getNomCommun'][$cache_id])) {
1656 raphael 538
			//debug: error_log("require url_service_nom_attribution: OK ! (pour \"{$obs['nom_ret']}\")");
1657 raphael 539
			return self::$cache['getNomCommun'][$cache_id];
1654 aurelien 540
		}
541
		// pas de cache:
542
		//debug: error_log("require url_service_nom_attribution pour \"{$obs['nom_ret']}\"");
2143 jpm 543
 
1654 aurelien 544
		// pour bdtfx:
545
		// /service:eflore:0.1/nvjfl/noms-vernaculaires/attributions?masque.nt=X&masque.lg=fra&retour.champs=num_statut
546
		// /projet/services/modules/0.1/nvjfl/NomsVernaculaires.php
547
		$url = str_replace(Array('{referentiel}', '{valeur}', '{langue}'),
1656 raphael 548
						   Array($referentiel, $obs['nt'], $langue),
1657 raphael 549
						   self::$config['eflore']['url_service_nom_attribution']) . // TODO !
1656 raphael 550
			"&retour.champs=num_statut";
1654 aurelien 551
		$noms = @json_decode(file_get_contents($url));
552
		if(! $noms) return '';
1673 raphael 553
		$noms = array_filter((array)($noms->resultat), array($this, retournerNumStatutUn)); // XXX: php 5.3
1654 aurelien 554
		$nom = array_pop($noms)->nom_vernaculaire;
2143 jpm 555
 
1654 aurelien 556
		// cache
1657 raphael 557
		self::$cache['getNomCommun'][$cache_id] = $nom;
1654 aurelien 558
		return $nom;
559
	}
2143 jpm 560
 
1671 aurelien 561
	private function retournerNumStatutUn(&$item) {
562
		return ($item->num_statut == 1);
563
	}
1673 raphael 564
 
565
	private function retournerNumStatutUnArr(&$item) {
566
		return ($item['num_statut'] == 1);
567
	}
2143 jpm 568
 
1656 raphael 569
	// si getNomCommun_v2 ou getNomCommun_v3 sont utilisés
570
	/* require_once('/home/raphael/eflore/framework/framework/Framework.php');
571
	Framework::setCheminAppli("/home/raphael/eflore/projets/services/index.php");
572
	Framework::setInfoAppli(Config::get('info'));
573
	require_once('/home/raphael/eflore/projets/services/modules/0.1/Projets.php');*/
2143 jpm 574
 
1654 aurelien 575
	/* Tente de bootstraper le framework au plus court et d'initialiser une instance de
1656 raphael 576
	   NomsVernaculaires pour obtenir le nom commun */
1654 aurelien 577
	function getNomCommun_v2($obs) {
578
		static $service;
1656 raphael 579
		$service = new Projets();
2143 jpm 580
 
1654 aurelien 581
		$langue = 'fra';
582
		list($referentiel) = explode(':', strtolower($obs['nom_referentiel']));
583
		if($referentiel == 'bdtfx') $referentiel = 'nvjfl';
584
		else return '';
2143 jpm 585
 
1654 aurelien 586
		$cache_id = $referentiel . '-' . $obs['nt'] . '-' . $langue;
1657 raphael 587
		if(isset(self::$cache['getNomCommun'][$cache_id])) {
1656 raphael 588
			error_log("require NomsVernaculaires.php: OK ! (pour \"{$obs['nom_ret']}\")");
1657 raphael 589
			return self::$cache['getNomCommun'][$cache_id];
1654 aurelien 590
		}
591
		// pas de cache:
592
		error_log("require NomsVernaculaires.php pour \"{$obs['nom_ret']}\"");
2143 jpm 593
 
1656 raphael 594
		$donnees = Array('masque.nt' => $obs['nt'],
595
						 'masque.lg' => $langue,
596
						 'retour.champs' => 'num_statut');
1654 aurelien 597
		$noms = $service->consulter(Array('nvjfl', 'noms-vernaculaires'), $donnees);
2143 jpm 598
 
1654 aurelien 599
		if(! $noms) return '';
1673 raphael 600
		$noms = array_filter((array)($noms->resultat), array($this, retournerNumStatutUn)); // XXX: php 5.3
1654 aurelien 601
		$nom = array_pop($noms)->nom_vernaculaire;
2143 jpm 602
 
1656 raphael 603
		// cache
1657 raphael 604
		self::$cache['getNomCommun'][$cache_id] = $nom;
1654 aurelien 605
		return $nom;
606
	}
2143 jpm 607
 
608
 
1654 aurelien 609
	/* Effectue un bootstraping plus sage que ci-dessus, mais le gain d'efficacité
1656 raphael 610
	   n'est pas aussi retentissant qu'espéré */
1654 aurelien 611
	static $service;
612
	function getNomCommun_v3($obs) {
613
		if(! $this->service) $this->service = new Projets();
2143 jpm 614
 
1654 aurelien 615
		$langue = 'fra';
616
		list($referentiel) = explode(':', strtolower($obs['nom_referentiel']));
617
		if($referentiel == 'bdtfx') $referentiel = 'nvjfl';
618
		else return '';
2143 jpm 619
 
1654 aurelien 620
		$cache_id = $referentiel . '-' . $obs['nt'] . '-' . $langue;
1657 raphael 621
		if(isset(self::$cache['getNomCommun'][$cache_id])) {
1656 raphael 622
			error_log("require NomsVernaculaires.php: OK ! (pour \"{$obs['nom_ret']}\")");
1657 raphael 623
			return self::$cache['getNomCommun'][$cache_id];
1654 aurelien 624
		}
625
		// pas de cache:
1656 raphael 626
		error_log("require NomsVernaculaires.php pour \"{$obs['nom_ret']}\"");
2143 jpm 627
 
1654 aurelien 628
		$donnees = Array('masque.nt' => $obs['nt'],
1656 raphael 629
						 'masque.lg' => $langue,
630
						 'retour.champs' => 'conseil_emploi');
631
		$this->service->initialiserRessourcesEtParametres(Array('nvjfl', 'noms-vernaculaires', 'attributions'), $donnees);
1654 aurelien 632
		try {
1656 raphael 633
			$noms = $this->service->traiterRessources();
2143 jpm 634
		} catch(Exception $e) {
1656 raphael 635
			return '';
1654 aurelien 636
		}
1656 raphael 637
		if(! $noms) return '';
1673 raphael 638
		$noms = array_filter($noms['resultat'], array($this, retournerNumStatutUnArr)); // XXX: php 5.3
1656 raphael 639
		$premier_nom = array_pop($noms);
1654 aurelien 640
		$nom = $premier_nom['nom_vernaculaire'];
2143 jpm 641
 
1654 aurelien 642
		// cache
1657 raphael 643
		self::$cache['getNomCommun'][$cache_id] = $nom;
1654 aurelien 644
		return $nom;
645
	}
1685 raphael 646
 
1694 raphael 647
	/* Cette fonction initialise le cache des noms communs en 1 fois, sur la liste des observations à exporter.
1702 raphael 648
	   Ainsi, les appels successifs à getNomCommun_v4() ne sont pas couteux (pas de requête SQL) */
1694 raphael 649
	static function getNomCommun_preload($cel, $obsids) {
650
		if(!$obsids) return;
1765 raphael 651
		if(!self::referenceTableExiste()) return NULL;
1694 raphael 652
 
653
		// CREATE INDEX i_nom_referentiel ON cel_obs (nom_referentiel(5));
654
		$req = sprintf("SELECT r.referentiel, r.num_taxon, r.nom_commun FROM cel_references r" .
655
					   " INNER JOIN cel_obs c ON (r.referentiel = substring_index(c.nom_referentiel, ':', 1) and r.num_taxon = c.nt)" .
656
					   " WHERE c.id_observation IN (%s)",
657
					   implode(',', $obsids));
1765 raphael 658
		$res = Cel::db()->requeter($req);
1694 raphael 659
		foreach($res as $v) {
660
			self::$cache['getNomCommun'][$v['referentiel'] . '-' . $v['num_taxon'] . '-' . 'fra'] = $v['nom_commun'];
661
		}
662
		return NULL;
663
	}
1702 raphael 664
 
1765 raphael 665
	static function referenceTableExiste() {
2143 jpm 666
		if (!self::$is_table) {
1702 raphael 667
			// une seule fois
2143 jpm 668
			if (! Cel::db()->requeterLigne("SHOW TABLES LIKE 'cel_references'")) return FALSE;
1702 raphael 669
			self::$is_table = TRUE;
670
		}
671
		return TRUE;
672
	}
2143 jpm 673
 
1765 raphael 674
	static function getNomCommun_v4($obs) {
2446 jpm 675
		// Attention la fonction suppose que l'on ait fait appel à getNomCommun_preload avant
2281 aurelien 676
		// d'être appelée
1685 raphael 677
		if(! $obs['nt']) return NULL;
1765 raphael 678
		if(! self::referenceTableExiste()) return NULL;
1689 raphael 679
 
1685 raphael 680
		$langue = 'fra';
681
		list($referentiel) = explode(':', strtolower($obs['nom_referentiel']));
682
		$cache_id = $referentiel . '-' . $obs['nt'] . '-' . $langue;
683
 
2281 aurelien 684
		$nom = null;
1757 raphael 685
		// cache:
2281 aurelien 686
		if(isset(self::$cache['getNomCommun'])) {
1757 raphael 687
			if(isset(self::$cache['getNomCommun'][$cache_id])) return self::$cache['getNomCommun'][$cache_id];
688
			// XXX: problème de valeurs NULL ?
2281 aurelien 689
			if(array_key_exists($cache_id, self::$cache['getNomCommun'])) {
690
				$nom = self::$cache['getNomCommun'][$cache_id];
691
			}
1757 raphael 692
		}
1685 raphael 693
 
694
		return $nom;
695
	}
1702 raphael 696
 
697
 
698
	/* Cette fonction initialise le cache des données baseflor en 1 fois, sur la liste des observations à exporter.
699
	   Ainsi, les appels successifs à baseflor_ligne() ne sont pas couteux (pas de requête SQL) */
700
	static function baseflor_preload($cel, $obsids) {
701
		if(!$obsids) return;
1765 raphael 702
		if(!self::referenceTableExiste()) return NULL;
1702 raphael 703
 
2254 aurelien 704
		// Attention (en attendant de faire une meilleure table et une meilleure requete) le distinct est très important
705
		$req = sprintf("SELECT DISTINCT referentiel, num_nom_retenu, %s FROM cel_references r" .
2297 mathias 706
						" INNER JOIN cel_obs c ON (r.num_nom_retenu = c.nom_ret_nn)" .
707
						" AND r.referentiel = IF(LOCATE(':', c.nom_referentiel) = 0, c.nom_referentiel, SUBSTR(c.nom_referentiel FROM 1 FOR LOCATE(':', c.nom_referentiel) - 1))" .
708
						" WHERE c.id_observation IN (%s)",
709
						//" AND catminat_code IS NOT NULL", // TODO: suppression des NULL ici signifie que le cache sera partiel...
710
						implode(',', array_keys(self::$baseflor_col)),
711
						implode(',', $obsids));
1765 raphael 712
		$res = Cel::db()->requeter($req);
1706 raphael 713
		if(!$res) return NULL;
1702 raphael 714
 
715
		foreach($res as $v) {
716
			$data = $v;
717
			unset($data['referentiel']); // non nécessaire
718
			unset($data['num_nom_retenu']); // non nécessaire
2446 jpm 719
 
2244 aurelien 720
			// Des fois les synonymes ont des valeurs pour baseflor et pas le nom retenu et vice versa
2446 jpm 721
			// on les fusionne pour avoir le maximum d'infos, en attendant de repenser la table référence
2244 aurelien 722
			if(isset(self::$cache['getBaseflor'][$v['referentiel'] . '-' . $v['num_nom_retenu']])) {
2446 jpm 723
				$orig  = array_filter(self::$cache['getBaseflor'][$v['referentiel'] . '-' . $v['num_nom_retenu']], 'strlen');
724
				$data  = array_filter($data , 'strlen');
2244 aurelien 725
				$data = array_merge($orig, $data);
726
			}
2446 jpm 727
 
1702 raphael 728
			self::$cache['getBaseflor'][$v['referentiel'] . '-' . $v['num_nom_retenu']] = $data;
729
		}
730
 
731
		return NULL;
732
	}
1685 raphael 733
 
2446 jpm 734
	/**
2312 mathias 735
	 * Attention la fonction suppose que l'on ait fait appel à baseflor_preload avant
736
	 * d'être appelée
737
	 * @CASSECOUILLES elle pourrait le détecter et le faire elle-même
738
	 */
1765 raphael 739
	static function baseflor_ligne($obs, &$ligne) {
2312 mathias 740
		$clefsBF = array_keys(self::$baseflor_col);
741
		// par défaut des colonnes vides pour ne pas décaler le bousin
742
		$donneesBF = array_fill_keys($clefsBF, "");
1702 raphael 743
 
2312 mathias 744
		// s'il y a des données baseflor
745
		if ($obs['nom_ret_nn'] && self::referenceTableExiste() && count(self::$cache['getBaseflor']) > 0) {
746
			// l'astuce à un franc vingt
747
			list($referentiel) = explode(':', strtolower($obs['nom_referentiel']));
748
			$cache_id = $referentiel . '-' . $obs['nom_ret_nn'];
1702 raphael 749
 
2312 mathias 750
			// si les données baseflor existent dans le cache pour ce nom_ret_nn
751
			if (array_key_exists($cache_id, self::$cache['getBaseflor'])) {
752
				$donneesBFATrous = self::$cache['getBaseflor'][$cache_id];
753
				foreach($clefsBF as $colbf) { // remplit les trous tout en préservant l'ordre
754
					if(array_key_exists($colbf, $donneesBFATrous)) {
755
						$donneesBF[$colbf] = $donneesBFATrous[$colbf];
756
					} else {
757
						$donneesBF[$colbf] = "";
758
					}
759
				}
760
			}
1702 raphael 761
		}
2312 mathias 762
 
763
		// Quand les données sont prêtes, on les fusionne
764
		$ligne = array_merge($ligne, $donneesBF);
1702 raphael 765
	}
2446 jpm 766
 
1715 raphael 767
	static function champsEtendus_preload($cel, $obsids) {
2446 jpm 768
		$gestion_champs_etendus = new GestionChampsEtendus($cel->config, 'obs');
1718 raphael 769
		$colonnes_champs_supp_par_obs = $gestion_champs_etendus->consulterClesParLots($obsids);
2446 jpm 770
 
771
		// Supprime les champs étendus considérés comme privés dans le cas de l'export public en chargeant
2403 aurelien 772
		// le catalogue et en excluant ceux qui sont explicitement privés
773
		if(!$cel->export_prive) {
774
			$indices_a_supprimer = array();
775
			$catalogue_champs_etendus = $gestion_champs_etendus->consulterCatalogueChampsEtendusPredefinis();
776
			foreach($catalogue_champs_etendus as $champ_catalogue) {
777
				if($champ_catalogue['options']['prive'] == 1) {
2446 jpm 778
					// Les champs étendus peuvent avoir des variantes lorsqu'ils apparaissent de multiples fois.
2407 aurelien 779
					// Vont donc matcher monChamp mais aussi monChamp:1, monChamp:2 ou bien monChamp1, monChamp: etc...
780
					// pour plus de sécurité (ce filtra n'est affectué qu'une fois au début de l'export donc on ne s'en prive pas)
781
					$entrees = preg_grep("/".$champ_catalogue['cle']."(?::?\d*)?$/", $colonnes_champs_supp_par_obs);
2403 aurelien 782
					$indices_a_supprimer = array_merge($indices_a_supprimer, array_keys($entrees));
783
				}
784
			}
785
			// les champs étendus sont renvoyés dans l'export suivant les colonnes présentes dans ce tableau
786
			// les éliminer de la liste des colonnes suffit à les faire ignorer par l'export
2446 jpm 787
			foreach($indices_a_supprimer as $indice_supp) {
788
				unset($colonnes_champs_supp_par_obs[$indice_supp]);
2403 aurelien 789
			}
790
		}
2446 jpm 791
 
1741 raphael 792
		// ces deux lignes réordonnent l'ordre des colonnes des champs étendus en fonction de l'ordre (très spécifique)
793
		// de self::$ordre_champ_etendus_Florileges, les champs non-mentionnés sont ajoutés à la fin.
794
		$colonnes_champs_supp_par_obs = self::sortArrayByArray(array_flip($colonnes_champs_supp_par_obs),
795
															   self::$ordre_champ_etendus_Florileges);
796
		$colonnes_champs_supp_par_obs = array_keys($colonnes_champs_supp_par_obs);
797
 
1718 raphael 798
		// si le SELECT des clefs ne retourne rien, une autre requêtes est inutile
799
		// TODO: optimize, 1 seule requête
800
		if(!$colonnes_champs_supp_par_obs) return Array('header' => array(), 'data' => array());
801
 
1715 raphael 802
		$champs_supp_par_obs = $gestion_champs_etendus->consulterParLots($obsids);
1741 raphael 803
 
1791 raphael 804
		self::$cache['champsEtendus']['header'] = self::champsEtendus_prefixHeader($colonnes_champs_supp_par_obs);
805
 
1715 raphael 806
		foreach($champs_supp_par_obs as &$v) {
807
			$v = self::champsEtendus_aplatir($v);
808
		}
809
		self::$cache['champsEtendus']['data'] = $champs_supp_par_obs;
810
		// ce return est temporaire,
811
		// le temps que toutes les fonctions bougent ici et utilise plutôt le cache statique
1716 raphael 812
		// encore utilisé pour les entêtes (self::$cache['champsEtendus']['header'])
1715 raphael 813
		return self::$cache['champsEtendus'];
814
	}
815
 
816
	// XXX: PHP-5.3, fonction anonyme + array_map
1791 raphael 817
	static function champsEtendus_prefixHeader($array) {
818
		return array_map(create_function('$v', 'return "' . PREFIX_CHAMPS_ETENDUS . '".$v;'), $array);
819
	}
820
 
821
	// XXX: PHP-5.3, fonction anonyme + array_map
1715 raphael 822
	static function champsEtendus_aplatir($ligne_champs_etendus) {
823
		$champs_etendus_fmt = array();
824
		if(!$ligne_champs_etendus) return $champs_etendus_fmt;
825
		foreach($ligne_champs_etendus as $champ) {
1791 raphael 826
			$champs_etendus_fmt[PREFIX_CHAMPS_ETENDUS . $champ->cle] = $champ->valeur;
1715 raphael 827
		}
828
		return $champs_etendus_fmt;
829
	}
1716 raphael 830
 
1765 raphael 831
	static function champsEtendus_ligne($obs, &$ligne) {
1716 raphael 832
		// si header n'est pas défini, aucune observation ne possède de champ étendu
833
		// et nous n'ajoutons ni colonnes, ni valeurs.
834
		if(! isset(self::$cache['champsEtendus']['header'])) return;
1736 raphael 835
		$ligne_etendue_aplatie = @self::$cache['champsEtendus']['data'][$obs['id_observation']];
1716 raphael 836
 
837
		$ligne_supp = array_fill(0, count(self::$cache['champsEtendus']['header']), '');
838
		$ligne_etendue_fmt = array();
839
 
840
		// si, cependant cette seule observation n'a pas de champs étendus,
841
		// nous devons rajouter des blancs (notamment dans le cas ou d'autres
842
		// champs viennent à être ajoutés en aval à l'avenir
843
		// cf: $fonction_dynamique dans FormateurGroupeColonne::GenColInfo()
844
		if(! $ligne_etendue_aplatie) {
845
			$ligne = array_merge($ligne, $ligne_supp);
846
			return;
847
		}
848
 
849
		foreach(self::$cache['champsEtendus']['header'] as $colonne) {
850
			if(!isset($ligne_etendue_aplatie[$colonne])) {
2143 jpm 851
				$ligne_etendue_fmt[$colonne] = '';
1716 raphael 852
			} else {
853
				$ligne_etendue_fmt[$colonne] = $ligne_etendue_aplatie[$colonne];
854
			}
855
		}
856
 
857
		// XXX/ array_merge() ?
858
		$ligne += $ligne_etendue_fmt;
859
	}
1741 raphael 860
 
861
	/* HELPERS */
862
 
863
	// http://stackoverflow.com/questions/348410/sort-an-array-based-on-another-array
864
	// XXX; redéfinition, utilisé aussi par ExportXLS
865
	static function sortArrayByArray($array, $orderArray) {
866
		$ordered = array();
867
		foreach($orderArray as $key) {
868
			if(array_key_exists($key, $array)) {
869
				$ordered[$key] = $array[$key];
870
				unset($array[$key]);
871
			}
872
		}
873
		return $ordered + $array;
874
	}
2461 jpm 875
}