* * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\Csv; use InvalidArgumentException; use php_user_filter; use function in_array; use function str_replace; use function strcspn; use function stream_bucket_append; use function stream_bucket_make_writeable; use function stream_filter_register; use function stream_get_filters; use function strlen; /** * A stream filter to improve enclosure character usage. * * @see https://tools.ietf.org/html/rfc4180#section-2 * @see https://bugs.php.net/bug.php?id=38301 */ class EncloseField extends php_user_filter { const FILTERNAME = 'convert.league.csv.enclosure'; /** * the filter name used to instantiate the class with. * * @var string */ public $filtername; /** * @var mixed value passed to passed to stream_filter_append or stream_filter_prepend functions. */ public $params; /** * Default sequence. * * @var string */ protected $sequence; /** * Characters that triggers enclosure in PHP. * * @var string */ protected static $force_enclosure = "\n\r\t "; /** * Static method to return the stream filter filtername. */ public static function getFiltername(): string { return self::FILTERNAME; } /** * Static method to register the class as a stream filter. */ public static function register(): void { if (!in_array(self::FILTERNAME, stream_get_filters(), true)) { stream_filter_register(self::FILTERNAME, self::class); } } /** * Static method to add the stream filter to a {@link Writer} object. * * @throws InvalidArgumentException if the sequence is malformed */ public static function addTo(Writer $csv, string $sequence): Writer { self::register(); if (!self::isValidSequence($sequence)) { throw new InvalidArgumentException('The sequence must contain at least one character to force enclosure'); } $formatter = static function (array $record) use ($sequence): array { foreach ($record as &$value) { $value = $sequence.$value; } unset($value); return $record; }; return $csv ->addFormatter($formatter) ->addStreamFilter(self::FILTERNAME, ['sequence' => $sequence]); } /** * Filter type and sequence parameters. * * The sequence to force enclosure MUST contains one of the following character ("\n\r\t ") */ protected static function isValidSequence(string $sequence): bool { return strlen($sequence) != strcspn($sequence, self::$force_enclosure); } /** * {@inheritdoc} */ public function onCreate(): bool { return isset($this->params['sequence']) && self::isValidSequence($this->params['sequence']); } /** * @param resource $in * @param resource $out * @param int $consumed * @param bool $closing */ public function filter($in, $out, &$consumed, $closing): int { while ($res = stream_bucket_make_writeable($in)) { $res->data = str_replace($this->params['sequence'], '', $res->data); $consumed += $res->datalen; stream_bucket_append($out, $res); } return PSFS_PASS_ON; } }