Subversion Repositories eFlore/Applications.cel

Rev

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