Subversion Repositories eFlore/Applications.cel

Rev

Rev 2253 | Rev 2280 | Go to most recent revision | Details | Compare with Previous | 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;
2252 aurelien 133
 
1402 aurelien 134
		switch($this->format) {
135
			case 'csv':
1625 aurelien 136
				$limite = 20000;
137
				break;
138
			case 'xls':
139
				$limite = 8000;
140
				break;
1660 raphael 141
			case 'pdf':
142
				$limite = 300;
143
				break;
1625 aurelien 144
		}
2250 aurelien 145
 
2253 aurelien 146
		return $limite;
1625 aurelien 147
	}
148
 
149
	private function getExport() {
150
		$criteres = $this->traiterParametresAutorises($_GET);
1711 raphael 151
		// ne pas faire de super-requête en cas d'absence de paramètres
152
		// par exemple "format", au minimum, devrait être défini
1715 raphael 153
		if(!$criteres) die('erreur: pas de paramètre reçu');
154
		if(!in_array($this->format, array('pdf','csv','xls'))) die('erreur: format invalide');
1711 raphael 155
 
1625 aurelien 156
		$criteres['transmission'] = 1;
1654 aurelien 157
		if($this->doitEtPeutExporterObsPrivees($criteres)) {
158
			unset($criteres['transmission']);
1755 raphael 159
			$this->id_utilisateur = $criteres['ce_utilisateur'];
1654 aurelien 160
		}
1625 aurelien 161
		$chercheur_observations = new RechercheObservation($this->config);
162
 
1679 raphael 163
		$debut = isset($criteres['debut']) ? intval($criteres['debut']) : 0;
164
		$limite = isset($criteres['limite']) ? intval($criteres['limite']) : 0;
1714 raphael 165
		$groupes = @FormateurGroupeColonne::colGroupsValidation($criteres['colonnes']);
1835 raphael 166
		$groupes .= ',auteur';
167
 
1714 raphael 168
		if(!$groupes) die('erreur: Ne peut identifier les groupes de champs demandés.');
1679 raphael 169
 
1714 raphael 170
 
1679 raphael 171
		if($criteres['obsids']) $criteres['sql_brut'] = sprintf('id_observation IN (%s)',
172
																implode(',', $criteres['obsids']));
1625 aurelien 173
 
174
		unset($criteres['limite']);
175
		unset($criteres['debut']);
176
		unset($criteres['format']);
1654 aurelien 177
		unset($criteres['colonnes']);
1679 raphael 178
		unset($criteres['obsids']);
179
 
180
		$observations = $chercheur_observations->rechercherObservations(null, $criteres, $debut, $limite, TRUE)->get();
1659 aurelien 181
		$ids = array();
182
		foreach($observations as &$obs) {
183
			$ids[] = $obs['id_observation'];
184
		}
185
 
1703 raphael 186
		if($this->format == 'pdf') {
187
			$pdf = $this->convertirEnPdf($observations);
188
			$pdf->pdf->Output('etiquettes.pdf', 'I');
189
			exit;
1662 aurelien 190
		}
191
 
1703 raphael 192
		// cas XLS et CSV: on peut avoir besoin des champs étendus, des noms communs et des champs baseflor:
1715 raphael 193
 
194
		// Obtention des colonnes correspondantes aux groupes de champs
1714 raphael 195
		$colonnes = FormateurGroupeColonne::nomEnsembleVersListeColonnes($groupes);
1703 raphael 196
 
1715 raphael 197
		/*
198
		  Champs étendus et noms communs, si demandés.
199
		  * Pour "nom commun", "preload" retourne NULL, car c'est le cache statique de FormateurGroupeColonne
200
		  qu'il initialise et utilise en interne sans qu'un passage par paramètre dans le contexte de CelWidgetExport
201
		  ne soit nécessaire.
202
		  * Pour les champs étendus, c'est CelWidgetExport::$cache qui est utilisé, aussi bien pour les en-têtes que
203
		  pour les données préchargées, cf self::traiterLigneEtendue()
204
		*/
205
		self::$cache = FormateurGroupeColonne::preload($colonnes, $this, $ids);
1703 raphael 206
 
1659 aurelien 207
    	// TODO: tous les champs étendus et les paramètres supplémentaires devraient être passés en un seul
208
    	// tableau (et chaque formateur csv, xls etc... pourrait également être dans une classe à part)
1625 aurelien 209
		switch($this->format) {
1660 raphael 210
		case 'csv':
1715 raphael 211
			$csv = $this->convertirEnCsv($observations, $colonnes);
1660 raphael 212
			$this->envoyerCsv($csv);
213
			break;
214
		case 'xls':
1715 raphael 215
			$xls = $this->convertirEnXls($observations, $colonnes);
1660 raphael 216
			$this->envoyerXls($xls);
217
			break;
218
		default:
1402 aurelien 219
		}
1374 aurelien 220
	}
221
 
1611 raphael 222
	protected function traiterParametresAutorises(Array $parametres) {
1376 aurelien 223
		$parametres_traites = array();
1402 aurelien 224
		$this->format = (isset($parametres['format']) && $parametres['format'] != '') ? $parametres['format'] : $this->format;
1376 aurelien 225
		foreach($parametres as $cle => $valeur) {
1711 raphael 226
			if(is_string($valeur) && !trim($valeur)) continue;
227
			if(isset($this->parametres_autorises[$cle])) {
1376 aurelien 228
				$parametres_traites[$this->parametres_autorises[$cle]] = $valeur;
229
			}
230
		}
1679 raphael 231
		$parametres_traites['obsids'] = @self::traiterObsIds($parametres['obsids']);
1376 aurelien 232
		return $parametres_traites;
233
	}
234
 
1374 aurelien 235
	private function envoyerCsv($csv) {
236
		header('Content-Type: text/csv; charset=UTF-8');
1408 aurelien 237
		header('Content-Disposition: attachment;filename='.$this->nom_fichier_export.'.csv');
1374 aurelien 238
		echo $csv;
1376 aurelien 239
		exit;
1374 aurelien 240
	}
241
 
1402 aurelien 242
	private function envoyerXls($workbook) {
243
		$workbook->close();
244
		exit;
245
	}
246
 
1715 raphael 247
	private function convertirEnCsv(&$data, $colonnes) {
1374 aurelien 248
		$chemin_temp = "php://temp";
249
		$outstream = fopen($chemin_temp, 'r+');
1690 raphael 250
 
1711 raphael 251
		$intitule_champs = array_merge(FormateurGroupeColonne::getIntitulesColonnes($colonnes));
1703 raphael 252
		// en premier car utilisé génériquement dans getLigneObservation()
1711 raphael 253
		if(isset($colonnes['baseflor'])) {
1703 raphael 254
			$intitule_champs = array_merge($intitule_champs, FormateurGroupeColonne::$baseflor_col);
255
		}
256
		// en second car manuellement appellé plus bas, TODO: utiliser l'API du FormateurGroupeColonne
1715 raphael 257
		if(isset($colonnes['etendu'])) {
258
			$intitule_champs = array_merge($intitule_champs, array_values(self::$cache['etendu']['header']));
259
		}
1690 raphael 260
 
261
		// header
262
		fputcsv($outstream, $intitule_champs, ',', '"');
263
		// lignes
1617 aurelien 264
		foreach($data as &$ligne) {
1692 raphael 265
			$ligne = self::filtrerDonneesSensibles($ligne);
1711 raphael 266
			$ligne = FormateurGroupeColonne::getLigneObservation($ligne, $colonnes, $this);
1374 aurelien 267
			fputcsv($outstream, $ligne, ',', '"');
268
		}
269
		rewind($outstream);
270
		$csv = stream_get_contents($outstream);
271
		fclose($outstream);
272
		return $csv;
273
	}
274
 
1715 raphael 275
	private function convertirEnXls(&$data, $colonnes) {
1402 aurelien 276
		$this->extendSpreadsheetProductor = new SpreadsheetProductor();
277
		$this->extendSpreadsheetProductor->initSpreadsheet();
278
 
279
		$workbook = new Spreadsheet_Excel_Writer();
1804 raphael 280
		// avant la définition du titre de la worksheet !
281
		$workbook->setVersion(8);
282
 
1625 aurelien 283
		$worksheet = $workbook->addWorksheet('Liste');
284
		$workbook->setTempDir($this->config['cel']['chemin_stockage_temp']);
1612 raphael 285
		$worksheet->setInputEncoding('utf-8');
1408 aurelien 286
		$workbook->send($this->nom_fichier_export.'.xls');
1402 aurelien 287
 
288
		$nb_lignes = 1;
1690 raphael 289
 
1711 raphael 290
		$intitule_champs = array_merge(FormateurGroupeColonne::getIntitulesColonnes($colonnes));
1703 raphael 291
		// en premier car utilisé génériquement dans getLigneObservation()
1711 raphael 292
		if(isset($colonnes['baseflor'])) {
1703 raphael 293
			$intitule_champs = array_merge($intitule_champs, FormateurGroupeColonne::$baseflor_col);
294
		}
295
		// en second car manuellement appellé plus bas, TODO: utiliser l'API du FormateurGroupeColonne
1715 raphael 296
		if(isset($colonnes['etendu'])) {
297
			$intitule_champs = array_merge($intitule_champs, array_values(self::$cache['etendu']['header']));
298
		}
1703 raphael 299
 
1690 raphael 300
		// header
301
		$indice = 0;
302
		foreach ($intitule_champs as &$intitule) {
1692 raphael 303
			$worksheet->write(0,$indice++,$intitule);
1690 raphael 304
		}
305
 
1617 aurelien 306
		foreach($data as &$ligne) {
1692 raphael 307
			$ligne = self::filtrerDonneesSensibles($ligne);
1711 raphael 308
			$ligne = FormateurGroupeColonne::getLigneObservation($ligne, $colonnes, $this);
1402 aurelien 309
			$indice = 0;
1617 aurelien 310
			foreach($ligne as &$champ) {
1692 raphael 311
				$worksheet->write($nb_lignes,$indice++,$champ);
1402 aurelien 312
			}
313
			$nb_lignes++;
314
		}
315
		return $workbook;
316
	}
317
 
1662 aurelien 318
	private function convertirEnPdf(&$observations) {
1715 raphael 319
		if(count($observations) > 300) die('erreur: trop de données');
1662 aurelien 320
		//require_once('GenerateurPDF.php');
321
		$pdf = new GenerateurPDF();
322
		$pdf->export($observations);
323
		return $pdf;
324
	}
325
 
1692 raphael 326
	static function filtrerDonneesSensibles($ligne) {
1429 aurelien 327
		if(stripos($ligne['mots_cles_texte'], 'sensible') !== false) {
328
			$ligne['latitude'] = '';
329
			$ligne['longitude'] = '';
330
		}
331
		return $ligne;
332
	}
333
 
1654 aurelien 334
	private function doitEtPeutExporterObsPrivees($criteres) {
335
		return isset($criteres['ce_utilisateur']) &&
336
					$this->peutExporterObsPrivees($criteres['ce_utilisateur']);
337
	}
338
 
339
	private function peutExporterObsPrivees($id_utilisateur) {
340
		$gestion_utilisateur = new User($this->config);
341
		$utilisateur = $gestion_utilisateur->obtenirIdentiteConnectee();
1703 raphael 342
		return ! empty($utilisateur['id_utilisateur']) && $id_utilisateur == $utilisateur['id_utilisateur'];
1654 aurelien 343
	}
1679 raphael 344
 
345
	static function traiterObsIds($range_param) {
346
		if (!isset($range_param)) return NULL;
347
		// trim() car: `POST http://url<<<"range=*"`
348
		if (trim($range_param) == '*') return NULL;
349
		return self::rangeToList(trim($range_param));
350
	}
351
 
352
	/*
353
	 * @param $fieldSets: un range, eg: 1-5,8,32,58-101
354
	 * @return un tableau trié, eg: 1,2,3,4,5,8,32,58,...,101
355
	 * http://stackoverflow.com/questions/7698664/converting-a-range-or-partial-array-in-the-form-3-6-or-3-6-12-into-an-arra
356
	 */
357
	static function rangeToList($in = '') {
358
		$inSets = explode(',', trim($in, ','));
359
		$outSets = array();
360
 
361
		foreach($inSets as $inSet) {
362
			list($start,$end) = explode('-', $inSet . '-' . $inSet);
363
			// ignore les ranges trop importants
364
			if($start > 10000000 || $end > 10000000 || abs($start-$end) > 10000) continue;
365
			$outSets = array_merge($outSets,range($start,$end));
366
		}
367
		$outSets = array_unique($outSets);
368
		$outSets = array_filter($outSets, 'is_numeric');
369
		sort($outSets);
370
		return $outSets;
371
	}
1374 aurelien 372
}
373
?>