PluginProbe
Better Payment – Instant Payments, Donations, Fundraising with Subscriptions & More / 2.2.2
Better Payment – Instant Payments, Donations, Fundraising with Subscriptions & More v2.2.2
2.4.0 2.3.4 2.3.3 2.3.2 2.3.1 2.3.0 2.2.2 2.2.1 2.2.0 2.1.2 2.1.1 trunk 0.0.1 0.0.2 0.0.3 0.0.4 0.0.5 0.0.6 0.0.7 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 All 67 releases
better-payment / vendor / league / csv / src / AbstractCsv.php

AbstractCsv.php in Better Payment – Instant Payments, Donations, Fundraising with Subscriptions & More 2.2.2, at vendor/league/csv/src/AbstractCsv.php

493 lines 12.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * League.Csv (https://csv.thephpleague.com)
5 *
6 * (c) Ignace Nyamagana Butera <[email protected]>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12 declare(strict_types=1);
13
14 namespace League\Csv;
15
16 use Generator;
17 use SplFileObject;
18 use function filter_var;
19 use function get_class;
20 use function mb_strlen;
21 use function rawurlencode;
22 use function sprintf;
23 use function str_replace;
24 use function str_split;
25 use function strcspn;
26 use function strlen;
27 use const FILTER_FLAG_STRIP_HIGH;
28 use const FILTER_FLAG_STRIP_LOW;
29 use const FILTER_UNSAFE_RAW;
30
31 /**
32 * An abstract class to enable CSV document loading.
33 */
34 abstract class AbstractCsv implements ByteSequence
35 {
36 protected const STREAM_FILTER_MODE = STREAM_FILTER_READ;
37
38 /** @var SplFileObject|Stream The CSV document. */
39 protected $document;
40 /** @var array<string, bool> collection of stream filters. */
41 protected array $stream_filters = [];
42 protected ?string $input_bom = null;
43 protected string $output_bom = '';
44 protected string $delimiter = ',';
45 protected string $enclosure = '"';
46 protected string $escape = '\\';
47 protected bool $is_input_bom_included = false;
48
49 /**
50 * @final This method should not be overwritten in child classes
51 *
52 * @param SplFileObject|Stream $document The CSV Object instance
53 */
54 protected function __construct($document)
55 {
56 $this->document = $document;
57 [$this->delimiter, $this->enclosure, $this->escape] = $this->document->getCsvControl();
58 $this->resetProperties();
59 }
60
61 /**
62 * Reset dynamic object properties to improve performance.
63 */
64 abstract protected function resetProperties(): void;
65
66 public function __destruct()
67 {
68 unset($this->document);
69 }
70
71 public function __clone()
72 {
73 throw UnavailableStream::dueToForbiddenCloning(static::class);
74 }
75
76 /**
77 * Return a new instance from a SplFileObject.
78 *
79 * @return static
80 */
81 public static function createFromFileObject(SplFileObject $file)
82 {
83 return new static($file);
84 }
85
86 /**
87 * Return a new instance from a PHP resource stream.
88 *
89 * @param resource $stream
90 *
91 * @return static
92 */
93 public static function createFromStream($stream)
94 {
95 return new static(new Stream($stream));
96 }
97
98 /**
99 * Return a new instance from a string.
100 *
101 * @return static
102 */
103 public static function createFromString(string $content = '')
104 {
105 return new static(Stream::createFromString($content));
106 }
107
108 /**
109 * Return a new instance from a file path.
110 *
111 * @param resource|null $context the resource context
112 *
113 * @return static
114 */
115 public static function createFromPath(string $path, string $open_mode = 'r+', $context = null)
116 {
117 return new static(Stream::createFromPath($path, $open_mode, $context));
118 }
119
120 /**
121 * Returns the current field delimiter.
122 */
123 public function getDelimiter(): string
124 {
125 return $this->delimiter;
126 }
127
128 /**
129 * Returns the current field enclosure.
130 */
131 public function getEnclosure(): string
132 {
133 return $this->enclosure;
134 }
135
136 /**
137 * Returns the pathname of the underlying document.
138 */
139 public function getPathname(): string
140 {
141 return $this->document->getPathname();
142 }
143
144 /**
145 * Returns the current field escape character.
146 */
147 public function getEscape(): string
148 {
149 return $this->escape;
150 }
151
152 /**
153 * Returns the BOM sequence in use on Output methods.
154 */
155 public function getOutputBOM(): string
156 {
157 return $this->output_bom;
158 }
159
160 /**
161 * Returns the BOM sequence of the given CSV.
162 */
163 public function getInputBOM(): string
164 {
165 if (null !== $this->input_bom) {
166 return $this->input_bom;
167 }
168
169 $this->document->setFlags(SplFileObject::READ_CSV);
170 $this->document->rewind();
171 $this->input_bom = Info::fetchBOMSequence((string) $this->document->fread(4)) ?? '';
172
173 return $this->input_bom;
174 }
175
176 /**
177 * DEPRECATION WARNING! This method will be removed in the next major point release.
178 *
179 * @deprecated since version 9.7.0
180 * @see AbstractCsv::supportsStreamFilterOnRead
181 * @see AbstractCsv::supportsStreamFilterOnWrite
182 *
183 * Returns the stream filter mode.
184 */
185 public function getStreamFilterMode(): int
186 {
187 return static::STREAM_FILTER_MODE;
188 }
189
190 /**
191 * DEPRECATION WARNING! This method will be removed in the next major point release.
192 *
193 * @deprecated since version 9.7.0
194 * @see AbstractCsv::supportsStreamFilterOnRead
195 * @see AbstractCsv::supportsStreamFilterOnWrite
196 *
197 * Tells whether the stream filter capabilities can be used.
198 */
199 public function supportsStreamFilter(): bool
200 {
201 return $this->document instanceof Stream;
202 }
203
204 /**
205 * Tells whether the stream filter read capabilities can be used.
206 */
207 public function supportsStreamFilterOnRead(): bool
208 {
209 return $this->document instanceof Stream
210 && (static::STREAM_FILTER_MODE & STREAM_FILTER_READ) === STREAM_FILTER_READ;
211 }
212
213 /**
214 * Tells whether the stream filter write capabilities can be used.
215 */
216 public function supportsStreamFilterOnWrite(): bool
217 {
218 return $this->document instanceof Stream
219 && (static::STREAM_FILTER_MODE & STREAM_FILTER_WRITE) === STREAM_FILTER_WRITE;
220 }
221
222 /**
223 * Tell whether the specify stream filter is attach to the current stream.
224 */
225 public function hasStreamFilter(string $filtername): bool
226 {
227 return $this->stream_filters[$filtername] ?? false;
228 }
229
230 /**
231 * Tells whether the BOM can be stripped if presents.
232 */
233 public function isInputBOMIncluded(): bool
234 {
235 return $this->is_input_bom_included;
236 }
237
238 /**
239 * Returns the CSV document as a Generator of string chunk.
240 *
241 * @param int $length number of bytes read
242 *
243 * @throws Exception if the number of bytes is lesser than 1
244 */
245 public function chunk(int $length): Generator
246 {
247 if ($length < 1) {
248 throw InvalidArgument::dueToInvalidChunkSize($length, __METHOD__);
249 }
250
251 $input_bom = $this->getInputBOM();
252 $this->document->rewind();
253 $this->document->setFlags(0);
254 $this->document->fseek(strlen($input_bom));
255 /** @var array<int, string> $chunks */
256 $chunks = str_split($this->output_bom.$this->document->fread($length), $length);
257 foreach ($chunks as $chunk) {
258 yield $chunk;
259 }
260
261 while ($this->document->valid()) {
262 yield $this->document->fread($length);
263 }
264 }
265
266 /**
267 * DEPRECATION WARNING! This method will be removed in the next major point release.
268 *
269 * @deprecated since version 9.1.0
270 * @see AbstractCsv::toString
271 *
272 * Retrieves the CSV content
273 */
274 public function __toString(): string
275 {
276 return $this->toString();
277 }
278
279 /**
280 * Retrieves the CSV content.
281 *
282 * DEPRECATION WARNING! This method will be removed in the next major point release
283 *
284 * @deprecated since version 9.7.0
285 * @see AbstractCsv::toString
286 */
287 public function getContent(): string
288 {
289 return $this->toString();
290 }
291
292 /**
293 * Retrieves the CSV content.
294 *
295 * @throws Exception If the string representation can not be returned
296 */
297 public function toString(): string
298 {
299 $raw = '';
300 foreach ($this->chunk(8192) as $chunk) {
301 $raw .= $chunk;
302 }
303
304 return $raw;
305 }
306
307 /**
308 * Outputs all data on the CSV file.
309 *
310 * @return int Returns the number of characters read from the handle
311 * and passed through to the output.
312 */
313 public function output(string $filename = null): int
314 {
315 if (null !== $filename) {
316 $this->sendHeaders($filename);
317 }
318
319 $this->document->rewind();
320 if (!$this->is_input_bom_included) {
321 $this->document->fseek(strlen($this->getInputBOM()));
322 }
323
324 echo $this->output_bom;
325
326 return strlen($this->output_bom) + (int) $this->document->fpassthru();
327 }
328
329 /**
330 * Send the CSV headers.
331 *
332 * Adapted from Symfony\Component\HttpFoundation\ResponseHeaderBag::makeDisposition
333 *
334 * @throws Exception if the submitted header is invalid according to RFC 6266
335 *
336 * @see https://tools.ietf.org/html/rfc6266#section-4.3
337 */
338 protected function sendHeaders(string $filename): void
339 {
340 if (strlen($filename) != strcspn($filename, '\\/')) {
341 throw InvalidArgument::dueToInvalidHeaderFilename($filename);
342 }
343
344 $flag = FILTER_FLAG_STRIP_LOW;
345 if (strlen($filename) !== mb_strlen($filename)) {
346 $flag |= FILTER_FLAG_STRIP_HIGH;
347 }
348
349 /** @var string $filtered_name */
350 $filtered_name = filter_var($filename, FILTER_UNSAFE_RAW, $flag);
351 $filename_fallback = str_replace('%', '', $filtered_name);
352
353 $disposition = sprintf('attachment; filename="%s"', str_replace('"', '\\"', $filename_fallback));
354 if ($filename !== $filename_fallback) {
355 $disposition .= sprintf("; filename*=utf-8''%s", rawurlencode($filename));
356 }
357
358 header('Content-Type: text/csv');
359 header('Content-Transfer-Encoding: binary');
360 header('Content-Description: File Transfer');
361 header('Content-Disposition: '.$disposition);
362 }
363
364 /**
365 * Sets the field delimiter.
366 *
367 * @throws InvalidArgument If the Csv control character is not one character only.
368 *
369 * @return static
370 */
371 public function setDelimiter(string $delimiter): self
372 {
373 if ($delimiter === $this->delimiter) {
374 return $this;
375 }
376
377 if (1 !== strlen($delimiter)) {
378 throw InvalidArgument::dueToInvalidDelimiterCharacter($delimiter, __METHOD__);
379 }
380
381 $this->delimiter = $delimiter;
382 $this->resetProperties();
383
384 return $this;
385 }
386
387 /**
388 * Sets the field enclosure.
389 *
390 * @throws InvalidArgument If the Csv control character is not one character only.
391 *
392 * @return static
393 */
394 public function setEnclosure(string $enclosure): self
395 {
396 if ($enclosure === $this->enclosure) {
397 return $this;
398 }
399
400 if (1 !== strlen($enclosure)) {
401 throw InvalidArgument::dueToInvalidEnclosureCharacter($enclosure, __METHOD__);
402 }
403
404 $this->enclosure = $enclosure;
405 $this->resetProperties();
406
407 return $this;
408 }
409
410 /**
411 * Sets the field escape character.
412 *
413 * @throws InvalidArgument If the Csv control character is not one character only.
414 *
415 * @return static
416 */
417 public function setEscape(string $escape): self
418 {
419 if ($escape === $this->escape) {
420 return $this;
421 }
422
423 if ('' !== $escape && 1 !== strlen($escape)) {
424 throw InvalidArgument::dueToInvalidEscapeCharacter($escape, __METHOD__);
425 }
426
427 $this->escape = $escape;
428 $this->resetProperties();
429
430 return $this;
431 }
432
433 /**
434 * Enables BOM Stripping.
435 *
436 * @return static
437 */
438 public function skipInputBOM(): self
439 {
440 $this->is_input_bom_included = false;
441
442 return $this;
443 }
444
445 /**
446 * Disables skipping Input BOM.
447 *
448 * @return static
449 */
450 public function includeInputBOM(): self
451 {
452 $this->is_input_bom_included = true;
453
454 return $this;
455 }
456
457 /**
458 * Sets the BOM sequence to prepend the CSV on output.
459 *
460 * @return static
461 */
462 public function setOutputBOM(string $str): self
463 {
464 $this->output_bom = $str;
465
466 return $this;
467 }
468
469 /**
470 * append a stream filter.
471 *
472 * @param null|array $params
473 *
474 * @throws InvalidArgument If the stream filter API can not be appended
475 * @throws UnavailableFeature If the stream filter API can not be used
476 *
477 * @return static
478 */
479 public function addStreamFilter(string $filtername, $params = null): self
480 {
481 if (!$this->document instanceof Stream) {
482 throw UnavailableFeature::dueToUnsupportedStreamFilterApi(get_class($this->document));
483 }
484
485 $this->document->appendFilter($filtername, static::STREAM_FILTER_MODE, $params);
486 $this->stream_filters[$filtername] = true;
487 $this->resetProperties();
488 $this->input_bom = null;
489
490 return $this;
491 }
492 }
493