Subversion Repositories eFlore/Applications.cel

Rev

Go to most recent revision | Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
1374 aurelien 1
<?php
2
/**
3
* Service fournissant des informations concernant le CEL au format RSS1, RSS2 ou ATOM.
4
* Encodage en entrée : utf8
5
* Encodage en sortie : utf8
6
* Format du service :
7
* /CelWidgetExport/format
8
* /CelWidgetExport/csv
9
*
10
* Les paramêtres :
11
*  - "start" indique le numéro du premier item à afficher
12
*  - "limit" nombre d'items à afficher
13
*
14
* @author Aurélien 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
* @version $Id$
18
* @copyright 2012
19
*/
1610 raphael 20
 
21
set_include_path(get_include_path() . PATH_SEPARATOR . dirname(dirname(realpath(__FILE__))) . '/lib');
22
// la sortie est binaire (xls), mais OLE n'est pas compatible E_ALL en PHP-5.4
1700 raphael 23
error_reporting(error_reporting() & ~E_STRICT);
1610 raphael 24
require_once("lib/OLE.php");
25
require_once("lib/Spreadsheet/Excel/Writer.php");
26
 
1374 aurelien 27
class CelWidgetExport extends Cel {
28
 
1408 aurelien 29
	private $nom_fichier_export = 'cel_export';
1671 aurelien 30
	// certains paramètres apparaissent plusieurs fois car ils ont des alias
31
	// dans certains widgets
1376 aurelien 32
	private $parametres_autorises = array(
1654 aurelien 33
		'id_utilisateur' => 'ce_utilisateur',
1376 aurelien 34
		'utilisateur' => 'courriel_utilisateur',
35
		'commune' => 'zone_geo',
1671 aurelien 36
		'zone_geo' => 'zone_geo',
1376 aurelien 37
		'dept' => 'departement',
1654 aurelien 38
		'departement' => 'departement',
39
		'lieudit' => 'lieudit',
40
		'station' => 'station',
1376 aurelien 41
		'projet' => 'mots_cles',
1379 aurelien 42
		'num_taxon' => 'nt',
1378 aurelien 43
		'date_debut' => 'date_debut',
1387 aurelien 44
		'date_fin' => 'date_fin',
1625 aurelien 45
		'taxon' => 'taxon',
1654 aurelien 46
		'annee' => 'annee',
47
		'mois' => 'mois',
48
		'jour' => 'jour',
49
		'recherche' => 'recherche',
50
		'id_mots_cles' => 'id_mots_cles',
1662 aurelien 51
		'mots_cles' => 'mots_cles',
1625 aurelien 52
		'debut' => 'debut',
53
		'limite' => 'limite',
1654 aurelien 54
		'format' => 'format',
55
		'colonnes' => 'colonnes',
1679 raphael 56
		'transmission' => 'transmission',
57
		'obsids' => 'obsids',
1376 aurelien 58
	);
1402 aurelien 59
 
1625 aurelien 60
	private $limite_decoupage_defaut = 9000;
61
 
1402 aurelien 62
	private $format = 'csv';
1579 aurelien 63
 
1659 aurelien 64
	public $id_utilisateur = null;
1715 raphael 65
 
66
	// un cache, initialisé par certaines fonctions de préchargement, à la manière
67
	// de ce qui est fait par FormateurGroupeColonne
68
	static $cache = Array();
1659 aurelien 69
 
1579 aurelien 70
	public function getRessource() {
71
		return $this->getElement(array());
72
	}
1374 aurelien 73
 
74
	/**
75
	 * Méthode appelée avec une requête de type GET.
76
	 */
1709 raphael 77
	public function getElement($params = array()) {
1711 raphael 78
		switch(@strtolower($params[0])) {
1709 raphael 79
		case 'calcul':
80
			$this->getCalcul();
81
			break;
1625 aurelien 82
 
1709 raphael 83
		case 'export':
84
			$this->getExport();
85
			break;
86
		default:
1625 aurelien 87
			$this->getExport();
88
		}
89
	}
90
 
91
	private function getCalcul() {
1611 raphael 92
		$criteres = $this->traiterParametresAutorises($_GET);
1654 aurelien 93
 
1374 aurelien 94
		$criteres['transmission'] = 1;
1654 aurelien 95
		if($this->doitEtPeutExporterObsPrivees($criteres)) {
96
			unset($criteres['transmission']);
1659 aurelien 97
			$this->id_utilisateur = $criteres['id_utilisateur'];
1654 aurelien 98
		}
1374 aurelien 99
		$chercheur_observations = new RechercheObservation($this->config);
100
 
1379 aurelien 101
		$numero_page = isset($criteres['debut']) ? $criteres['debut'] : 0;
102
		$limite = isset($criteres['limite']) ? $criteres['limite'] : 0;
1714 raphael 103
		$colonnes = @FormateurGroupeColonne::colGroupsValidation($criteres['colonnes']);
1374 aurelien 104
 
105
		unset($criteres['limite']);
106
		unset($criteres['debut']);
1625 aurelien 107
		unset($criteres['format']);
1654 aurelien 108
		unset($criteres['colonnes']);
1625 aurelien 109
 
110
		$nb_observations = $chercheur_observations->compterObservations(null, $criteres);
2250 aurelien 111
		$limite_decoupage = $this->calculerNbLignesMaxParFichier(explode(',', $colonnes));
1625 aurelien 112
 
113
		$url_telechargements = array();
114
		$intervalle = 0;
115
 
116
		$params_url = $criteres;
117
		unset($params_url['transmission']);
118
		do {
119
			$base_url = $this->config['settings']['baseURLAbsolu'].'CelWidgetExport/export';
120
			$params_url['debut'] = $intervalle;
121
			$params_url['limite'] = $limite_decoupage;
122
			$url_telechargement_fichier = $base_url;
1654 aurelien 123
			$url_telechargements[] = $base_url.'?'.http_build_query($params_url).'&format='.$this->format.'&colonnes='.$colonnes;
1625 aurelien 124
			$intervalle += $limite_decoupage;
125
			$nb_observations -= $limite_decoupage;
2190 mathias 126
		} while($nb_observations > 0);
1625 aurelien 127
 
128
		$this->envoyerJson($url_telechargements);
129
	}
1374 aurelien 130
 
2250 aurelien 131
	private function calculerNbLignesMaxParFichier($colonnes) {
132
		$limite = $this->limite_decoupage_defaut;
133
		// TODO: gérer une réduction du nombre de ligne max différement suivant chaque colonne
134
		// demandée, car elles n'impactent pas toutes le temps de d'export de la même manière.
135
		$facteur_reduction = count($colonnes) <= 2 ? 1 :  pow(0.85, count($colonnes) - 2);
2252 aurelien 136
 
1402 aurelien 137
		switch($this->format) {
138
			case 'csv':
1625 aurelien 139
				$limite = 20000;
140
				break;
141
			case 'xls':
142
				$limite = 8000;
143
				break;
1660 raphael 144
			case 'pdf':
145
				$limite = 300;
146
				break;
1625 aurelien 147
		}
2252 aurelien 148
 
149
		$limite = floor($limite * $facteur_reduction);
150
		// précaution pour les données baseflor qui semblent très difficile à exporter par plus de 10000
151
		$limite = (in_array('baseflor', $colonnes) && $limite > 10000) ? 10000 : $limite;
2250 aurelien 152
 
2251 aurelien 153
		return floor($limite * $facteur_reduction);
1625 aurelien 154
	}
155
 
156
	private function getExport() {
157
		$criteres = $this->traiterParametresAutorises($_GET);
1711 raphael 158
		// ne pas faire de super-requête en cas d'absence de paramètres
159
		// par exemple "format", au minimum, devrait être défini
1715 raphael 160
		if(!$criteres) die('erreur: pas de paramètre reçu');
161
		if(!in_array($this->format, array('pdf','csv','xls'))) die('erreur: format invalide');
1711 raphael 162
 
1625 aurelien 163
		$criteres['transmission'] = 1;
1654 aurelien 164
		if($this->doitEtPeutExporterObsPrivees($criteres)) {
165
			unset($criteres['transmission']);
1755 raphael 166
			$this->id_utilisateur = $criteres['ce_utilisateur'];
1654 aurelien 167
		}
1625 aurelien 168
		$chercheur_observations = new RechercheObservation($this->config);
169
 
1679 raphael 170
		$debut = isset($criteres['debut']) ? intval($criteres['debut']) : 0;
171
		$limite = isset($criteres['limite']) ? intval($criteres['limite']) : 0;
1714 raphael 172
		$groupes = @FormateurGroupeColonne::colGroupsValidation($criteres['colonnes']);
1835 raphael 173
		$groupes .= ',auteur';
174
 
1714 raphael 175
		if(!$groupes) die('erreur: Ne peut identifier les groupes de champs demandés.');
1679 raphael 176
 
1714 raphael 177
 
1679 raphael 178
		if($criteres['obsids']) $criteres['sql_brut'] = sprintf('id_observation IN (%s)',
179
																implode(',', $criteres['obsids']));
1625 aurelien 180
 
181
		unset($criteres['limite']);
182
		unset($criteres['debut']);
183
		unset($criteres['format']);
1654 aurelien 184
		unset($criteres['colonnes']);
1679 raphael 185
		unset($criteres['obsids']);
186
 
187
		$observations = $chercheur_observations->rechercherObservations(null, $criteres, $debut, $limite, TRUE)->get();
1659 aurelien 188
		$ids = array();
189
		foreach($observations as &$obs) {
190
			$ids[] = $obs['id_observation'];
191
		}
192
 
1703 raphael 193
		if($this->format == 'pdf') {
194
			$pdf = $this->convertirEnPdf($observations);
195
			$pdf->pdf->Output('etiquettes.pdf', 'I');
196
			exit;
1662 aurelien 197
		}
198
 
1703 raphael 199
		// cas XLS et CSV: on peut avoir besoin des champs étendus, des noms communs et des champs baseflor:
1715 raphael 200
 
201
		// Obtention des colonnes correspondantes aux groupes de champs
1714 raphael 202
		$colonnes = FormateurGroupeColonne::nomEnsembleVersListeColonnes($groupes);
1703 raphael 203
 
1715 raphael 204
		/*
205
		  Champs étendus et noms communs, si demandés.
206
		  * Pour "nom commun", "preload" retourne NULL, car c'est le cache statique de FormateurGroupeColonne
207
		  qu'il initialise et utilise en interne sans qu'un passage par paramètre dans le contexte de CelWidgetExport
208
		  ne soit nécessaire.
209
		  * Pour les champs étendus, c'est CelWidgetExport::$cache qui est utilisé, aussi bien pour les en-têtes que
210
		  pour les données préchargées, cf self::traiterLigneEtendue()
211
		*/
212
		self::$cache = FormateurGroupeColonne::preload($colonnes, $this, $ids);
1703 raphael 213
 
1659 aurelien 214
    	// TODO: tous les champs étendus et les paramètres supplémentaires devraient être passés en un seul
215
    	// tableau (et chaque formateur csv, xls etc... pourrait également être dans une classe à part)
1625 aurelien 216
		switch($this->format) {
1660 raphael 217
		case 'csv':
1715 raphael 218
			$csv = $this->convertirEnCsv($observations, $colonnes);
1660 raphael 219
			$this->envoyerCsv($csv);
220
			break;
221
		case 'xls':
1715 raphael 222
			$xls = $this->convertirEnXls($observations, $colonnes);
1660 raphael 223
			$this->envoyerXls($xls);
224
			break;
225
		default:
1402 aurelien 226
		}
1374 aurelien 227
	}
228
 
1611 raphael 229
	protected function traiterParametresAutorises(Array $parametres) {
1376 aurelien 230
		$parametres_traites = array();
1402 aurelien 231
		$this->format = (isset($parametres['format']) && $parametres['format'] != '') ? $parametres['format'] : $this->format;
1376 aurelien 232
		foreach($parametres as $cle => $valeur) {
1711 raphael 233
			if(is_string($valeur) && !trim($valeur)) continue;
234
			if(isset($this->parametres_autorises[$cle])) {
1376 aurelien 235
				$parametres_traites[$this->parametres_autorises[$cle]] = $valeur;
236
			}
237
		}
1679 raphael 238
		$parametres_traites['obsids'] = @self::traiterObsIds($parametres['obsids']);
1376 aurelien 239
		return $parametres_traites;
240
	}
241
 
1374 aurelien 242
	private function envoyerCsv($csv) {
243
		header('Content-Type: text/csv; charset=UTF-8');
1408 aurelien 244
		header('Content-Disposition: attachment;filename='.$this->nom_fichier_export.'.csv');
1374 aurelien 245
		echo $csv;
1376 aurelien 246
		exit;
1374 aurelien 247
	}
248
 
1402 aurelien 249
	private function envoyerXls($workbook) {
250
		$workbook->close();
251
		exit;
252
	}
253
 
1715 raphael 254
	private function convertirEnCsv(&$data, $colonnes) {
1374 aurelien 255
		$chemin_temp = "php://temp";
256
		$outstream = fopen($chemin_temp, 'r+');
1690 raphael 257
 
1711 raphael 258
		$intitule_champs = array_merge(FormateurGroupeColonne::getIntitulesColonnes($colonnes));
1703 raphael 259
		// en premier car utilisé génériquement dans getLigneObservation()
1711 raphael 260
		if(isset($colonnes['baseflor'])) {
1703 raphael 261
			$intitule_champs = array_merge($intitule_champs, FormateurGroupeColonne::$baseflor_col);
262
		}
263
		// en second car manuellement appellé plus bas, TODO: utiliser l'API du FormateurGroupeColonne
1715 raphael 264
		if(isset($colonnes['etendu'])) {
265
			$intitule_champs = array_merge($intitule_champs, array_values(self::$cache['etendu']['header']));
266
		}
1690 raphael 267
 
268
		// header
269
		fputcsv($outstream, $intitule_champs, ',', '"');
270
		// lignes
1617 aurelien 271
		foreach($data as &$ligne) {
1692 raphael 272
			$ligne = self::filtrerDonneesSensibles($ligne);
1711 raphael 273
			$ligne = FormateurGroupeColonne::getLigneObservation($ligne, $colonnes, $this);
1374 aurelien 274
			fputcsv($outstream, $ligne, ',', '"');
275
		}
276
		rewind($outstream);
277
		$csv = stream_get_contents($outstream);
278
		fclose($outstream);
279
		return $csv;
280
	}
281
 
1715 raphael 282
	private function convertirEnXls(&$data, $colonnes) {
1402 aurelien 283
		$this->extendSpreadsheetProductor = new SpreadsheetProductor();
284
		$this->extendSpreadsheetProductor->initSpreadsheet();
285
 
286
		$workbook = new Spreadsheet_Excel_Writer();
1804 raphael 287
		// avant la définition du titre de la worksheet !
288
		$workbook->setVersion(8);
289
 
1625 aurelien 290
		$worksheet = $workbook->addWorksheet('Liste');
291
		$workbook->setTempDir($this->config['cel']['chemin_stockage_temp']);
1612 raphael 292
		$worksheet->setInputEncoding('utf-8');
1408 aurelien 293
		$workbook->send($this->nom_fichier_export.'.xls');
1402 aurelien 294
 
295
		$nb_lignes = 1;
1690 raphael 296
 
1711 raphael 297
		$intitule_champs = array_merge(FormateurGroupeColonne::getIntitulesColonnes($colonnes));
1703 raphael 298
		// en premier car utilisé génériquement dans getLigneObservation()
1711 raphael 299
		if(isset($colonnes['baseflor'])) {
1703 raphael 300
			$intitule_champs = array_merge($intitule_champs, FormateurGroupeColonne::$baseflor_col);
301
		}
302
		// en second car manuellement appellé plus bas, TODO: utiliser l'API du FormateurGroupeColonne
1715 raphael 303
		if(isset($colonnes['etendu'])) {
304
			$intitule_champs = array_merge($intitule_champs, array_values(self::$cache['etendu']['header']));
305
		}
1703 raphael 306
 
1690 raphael 307
		// header
308
		$indice = 0;
309
		foreach ($intitule_champs as &$intitule) {
1692 raphael 310
			$worksheet->write(0,$indice++,$intitule);
1690 raphael 311
		}
312
 
1617 aurelien 313
		foreach($data as &$ligne) {
1692 raphael 314
			$ligne = self::filtrerDonneesSensibles($ligne);
1711 raphael 315
			$ligne = FormateurGroupeColonne::getLigneObservation($ligne, $colonnes, $this);
1402 aurelien 316
			$indice = 0;
1617 aurelien 317
			foreach($ligne as &$champ) {
1692 raphael 318
				$worksheet->write($nb_lignes,$indice++,$champ);
1402 aurelien 319
			}
320
			$nb_lignes++;
321
		}
322
		return $workbook;
323
	}
324
 
1662 aurelien 325
	private function convertirEnPdf(&$observations) {
1715 raphael 326
		if(count($observations) > 300) die('erreur: trop de données');
1662 aurelien 327
		//require_once('GenerateurPDF.php');
328
		$pdf = new GenerateurPDF();
329
		$pdf->export($observations);
330
		return $pdf;
331
	}
332
 
1692 raphael 333
	static function filtrerDonneesSensibles($ligne) {
1429 aurelien 334
		if(stripos($ligne['mots_cles_texte'], 'sensible') !== false) {
335
			$ligne['latitude'] = '';
336
			$ligne['longitude'] = '';
337
		}
338
		return $ligne;
339
	}
340
 
1654 aurelien 341
	private function doitEtPeutExporterObsPrivees($criteres) {
342
		return isset($criteres['ce_utilisateur']) &&
343
					$this->peutExporterObsPrivees($criteres['ce_utilisateur']);
344
	}
345
 
346
	private function peutExporterObsPrivees($id_utilisateur) {
347
		$gestion_utilisateur = new User($this->config);
348
		$utilisateur = $gestion_utilisateur->obtenirIdentiteConnectee();
1703 raphael 349
		return ! empty($utilisateur['id_utilisateur']) && $id_utilisateur == $utilisateur['id_utilisateur'];
1654 aurelien 350
	}
1679 raphael 351
 
352
	static function traiterObsIds($range_param) {
353
		if (!isset($range_param)) return NULL;
354
		// trim() car: `POST http://url<<<"range=*"`
355
		if (trim($range_param) == '*') return NULL;
356
		return self::rangeToList(trim($range_param));
357
	}
358
 
359
	/*
360
	 * @param $fieldSets: un range, eg: 1-5,8,32,58-101
361
	 * @return un tableau trié, eg: 1,2,3,4,5,8,32,58,...,101
362
	 * http://stackoverflow.com/questions/7698664/converting-a-range-or-partial-array-in-the-form-3-6-or-3-6-12-into-an-arra
363
	 */
364
	static function rangeToList($in = '') {
365
		$inSets = explode(',', trim($in, ','));
366
		$outSets = array();
367
 
368
		foreach($inSets as $inSet) {
369
			list($start,$end) = explode('-', $inSet . '-' . $inSet);
370
			// ignore les ranges trop importants
371
			if($start > 10000000 || $end > 10000000 || abs($start-$end) > 10000) continue;
372
			$outSets = array_merge($outSets,range($start,$end));
373
		}
374
		$outSets = array_unique($outSets);
375
		$outSets = array_filter($outSets, 'is_numeric');
376
		sort($outSets);
377
		return $outSets;
378
	}
1374 aurelien 379
}
380
?>