Subversion Repositories eFlore/Applications.cel

Rev

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

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