Completed
Push — master ( 5cc303...b83508 )
by cam
04:45
created

exporter_csv.php ➔ exporter_csv_ligne_numerotee()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
nc 4
nop 5
dl 0
loc 11
rs 9.9
c 0
b 0
f 0
1
<?php
2
3
/***************************************************************************\
4
 *  SPIP, Système de publication pour l'internet                           *
5
 *                                                                         *
6
 *  Copyright © avec tendresse depuis 2001                                 *
7
 *  Arnaud Martin, Antoine Pitrou, Philippe Rivière, Emmanuel Saint-James  *
8
 *                                                                         *
9
 *  Ce programme est un logiciel libre distribué sous licence GNU/GPL.     *
10
 *  Pour plus de détails voir le fichier COPYING.txt ou l'aide en ligne.   *
11
\***************************************************************************/
12
13
/**
14
 * Gestion d'export de données au format CSV
15
 *
16
 * @package SPIP\Core\CSV\Export
17
 **/
18
19
if (!defined('_ECRIRE_INC_VERSION')) {
20
	return;
21
}
22
23
include_spip('inc/charsets');
24
include_spip('inc/filtres');
25
include_spip('inc/texte');
26
27
/**
28
 * Exporter un champ pour un export CSV : pas de retour a la ligne,
29
 * et echapper les guillements par des doubles guillemets
30
 *
31
 * @param string $champ
32
 * @return string
33
 */
34
function exporter_csv_champ($champ) {
35
	#$champ = str_replace("\r", "\n", $champ);
36
	#$champ = preg_replace(",[\n]+,ms", "\n", $champ);
37
	#$champ = str_replace("\n", ", ", $champ);
38
	$champ = preg_replace(',[\s]+,ms', ' ', $champ);
39
	$champ = str_replace('"', '""', $champ);
40
41
	return '"' . $champ . '"';
42
}
43
44
/**
45
 * Exporter une ligne complete au format CSV, avec delimiteur fourni
46
 *
47
 * @uses exporter_csv_champ()
48
 *
49
 * @param int $nb
50
 * @param array $ligne
51
 * @param string $delim
52
 * @param string|null $importer_charset
53
 *     Si défini exporte dans le charset indiqué
54
 * @param callable $callback
0 ignored issues
show
Documentation introduced by
Should the type for parameter $callback not be callable|null? Also, consider making the array more specific, something like array<String>, or String[].

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive. In addition it looks for parameters that have the generic type array and suggests a stricter type like array<String>.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
55
 * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be string|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
56
 */
57
function exporter_csv_ligne_numerotee($nb, $ligne, $delim = ',', $importer_charset = null, $callback=null) {
58
	if ($callback) {
59
		$ligne = call_user_func($callback, $nb, $ligne, $delim, $importer_charset);
60
	}
61
	$output = join($delim, array_map('exporter_csv_champ', $ligne)) . "\r\n";
62
	if ($importer_charset) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $importer_charset of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
63
		$output = str_replace('’', '\'', $output);
64
		$output = unicode2charset(html2unicode(charset2unicode($output)), $importer_charset);
65
	}
66
	return $output;
67
}
68
69
/**
70
 * @deprecated
71
 *
72
 * @param $ligne
73
 * @param string $delim
74
 * @param null $importer_charset
75
 * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be string|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
76
 */
77
function exporter_csv_ligne($ligne, $delim = ',', $importer_charset = null){
78
	return exporter_csv_ligne_numerotee(null, $ligne, $delim, $importer_charset);
79
}
80
81
/**
82
 * Exporte une ressource sous forme de fichier CSV
83
 *
84
 * La ressource peut etre un tableau ou une resource SQL issue d'une requete
85
 * L'extension est choisie en fonction du delimiteur :
86
 * - si on utilise ',' c'est un vrai csv avec extension csv
87
 * - si on utilise ';' ou tabulation c'est pour E*cel, et on exporte en iso-truc, avec une extension .xls
88
 *
89
 * @uses exporter_csv_ligne()
90
 *
91
 * @param string $titre
92
 *   titre utilise pour nommer le fichier
93
 * @param array|resource $resource
94
 * @param array $options
95
 *   string $delim : delimiteur
96
 *   array $entetes : tableau d'en-tetes pour nommer les colonnes (genere la premiere ligne)
97
 *   bool $envoyer : pour envoyer le fichier exporte (permet le telechargement)
98
 *   string $charset : charset de l'export si different de celui du site
99
 *   callable callback : fonction callback a appeler sur chaque ligne pour mettre en forme/completer les donnees
100
 * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be null|string?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
101
 */
102
function inc_exporter_csv_dist($titre, $resource, $options = []) {
103
104
	// support ancienne syntaxe
105
	// inc_exporter_csv_dist($titre, $resource, $delim = ', ', $entetes = null, $envoyer = true)
106
	if (is_string($options)) {
107
		$args = func_get_args();
108
		$options = [];
109
		foreach ([2 => 'delim', 3 => 'entetes', 4 => 'envoyer'] as $k => $option) {
110
			if (!empty($args[$k])) {
111
				$options[$option] = $args[$k];
112
			}
113
		}
114
	}
115
116
	$default_options = [
117
		'delim' => ', ',
118
		'entetes' => null,
119
		'envoyer' => true,
120
		'charset' => null,
121
		'callback' => null,
122
	];
123
	$options = array_merge($default_options, $options);
124
125
	$filename = preg_replace(',[^-_\w]+,', '_', translitteration(textebrut(typo($titre))));
126
127
	if ($options['delim'] == 'TAB') {
128
		$options['delim'] = "\t";
129
	}
130
	if (!in_array($options['delim'], array(',', ';', "\t"))) {
131
		$options['delim'] = ',';
132
	}
133
134
	$charset = $GLOBALS['meta']['charset'];
135
	$importer_charset = null;
0 ignored issues
show
Unused Code introduced by
$importer_charset is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
136
	if ($options['delim'] == ',') {
137
		$extension = 'csv';
138
	} else {
139
		$extension = 'xls';
140
		# Excel n'accepte pas l'utf-8 ni les entites html... on transcode tout ce qu'on peut
141
		$charset = 'iso-8859-1';
142
	}
143
	// mais si une option charset est explicite, elle a la priorite
144
	if (!empty($options['charset'])) {
145
		$charset = $options['charset'];
146
	}
147
148
	$importer_charset = (($charset === $GLOBALS['meta']['charset']) ? null : $charset);
149
150
	$filename = "$filename.$extension";
151
152
	$output = '';
153
	$nb = 0;
154
	if (!empty($options['entetes']) and is_array($options['entetes'])) {
155
		$output = exporter_csv_ligne_numerotee($nb, $options['entetes'], $options['delim'], $importer_charset, $options['callback']);
156
	}
157
	// les donnees commencent toujours a la ligne 1, qu'il y ait ou non des entetes
158
	$nb++;
159
160
	if ($options['envoyer']) {
161
		$disposition = ($options['envoyer'] === 'attachment' ? 'attachment' : 'inline');
162
		header("Content-Type: text/comma-separated-values; charset=$charset");
163
		header("Content-Disposition: $disposition; filename=$filename");
164
165
		// Vider tous les tampons
166
		$level = @ob_get_level();
167
		while ($level--) {
168
			@ob_end_flush();
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
169
		}
170
	}
171
172
	// si envoyer=='attachment' on passe par un fichier temporaire
173
	// sinon on ecrit directement sur stdout
174
	if ($options['envoyer'] and $options['envoyer'] !== 'attachment') {
175
		$fichier = "php://output";
176
	}
177
	else {
178
	$fichier = sous_repertoire(_DIR_CACHE, 'export') . $filename;
179
	}
180
181
	$fp = fopen($fichier, 'w');
182
	$length = fwrite($fp, $output);
183
184
	while ($row = is_array($resource) ? array_shift($resource) : sql_fetch($resource)) {
185
		$output = exporter_csv_ligne_numerotee($nb, $row, $options['delim'], $importer_charset, $options['callback']);
186
		$length += fwrite($fp, $output);
187
		$nb++;
188
	}
189
	fclose($fp);
190
191
	if ($options['envoyer']) {
192
		if ($options['envoyer'] === 'attachment') {
193
			header("Content-Length: $length");
194
			readfile($fichier);
195
		}
196
		// si on a envoye inline, c'est deja tout bon
197
		exit;
198
	}
199
200
	return $fichier;
201
}
202