views_streaming_data-8.x-1.x-dev/src/Plugin/views/style/StreamingCsvSerializer.php
src/Plugin/views/style/StreamingCsvSerializer.php
<?php
namespace Drupal\views_streaming_data\Plugin\views\style;
use Drupal\Core\Cache\CacheableDependencyInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\csv_serialization\Encoder\CsvEncoder;
use Drupal\views\Plugin\views\style\StylePluginBase;
use Drupal\views\Plugin\views\display\DisplayPluginBase;
use Drupal\views\ViewExecutable;
/**
* The style plugin for serialized output formats.
*
* @ingroup views_style_plugins
*
* @ViewsStyle(
* id = "csv_streaming_data",
* title = @Translation("CSV streaming Serializer"),
* help = @Translation("Serializes views row data to CSV."),
* display_types = {"streaming_data"}
* )
*/
class StreamingCsvSerializer extends StylePluginBase implements CacheableDependencyInterface {
/**
* {@inheritdoc}
*/
protected $usesRowPlugin = TRUE;
/**
* {@inheritdoc}
*/
protected $usesGrouping = FALSE;
/**
* Indicates the character used to delimit fields. Defaults to ",".
*
* @var string
*/
protected $delimiter = ',';
/**
* Indicates the character used for field enclosure. Defaults to '"'.
*
* @var string
*/
protected $enclosure = '"';
/**
* Indicates the character used for escaping. Defaults to "\".
*
* @var string
*/
protected $escapeChar = "\\";
/**
* Whether to strip tags from values or not. Defaults to TRUE.
*
* @var bool
*/
protected $stripTags = TRUE;
/**
* Whether to trim values or not. Defaults to TRUE.
*
* @var bool
*/
protected $trimValues = TRUE;
/**
* The display object this plugin is for.
*
* @var \Drupal\views_streaming_data\Plugin\views\display\StreamingDisplayInterface
*/
public $displayHandler;
/**
* Overrides \Drupal\views\Plugin\views\PluginBase::init().
*
* The style options might come externally as the style can be sourced from at
* least two locations. If it's not included, look on the display.
*/
public function init(ViewExecutable $view, DisplayPluginBase $display, ?array &$options = NULL) {
parent::init($view, $display, $options);
$delimiter = $this->options['delimiter'];
$ext = 'csv';
$mime_type = 'text/csv';
if ($delimiter === '|') {
$ext = 'txt';
$mime_type = 'text/plain';
}
elseif ($delimiter === "\t") {
// @see https://www.iana.org/assignments/media-types/text/tab-separated-values
$ext = 'tsv';
$mime_type = 'text/tab-separated-values';
}
$this->displayHandler->setMimeType($mime_type);
$this->displayHandler->setFileExtension($ext);
}
/**
* {@inheritdoc}
*/
protected function defineOptions() {
$options = parent::defineOptions();
$options['delimiter'] = ['default' => $this->delimiter];
$options['strip_tags'] = ['default' => $this->stripTags];
$options['trim'] = ['default' => $this->trimValues];
return $options;
}
/**
* {@inheritdoc}
*/
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
parent::buildOptionsForm($form, $form_state);
$form['delimiter'] = [
'#type' => 'select',
'#title' => $this->t('Delimiter'),
'#description' => $this->t('Delimiter for the generated file.'),
'#options' => $this->getDelimiterOptions(),
'#default_value' => $this->options['delimiter'],
];
$form['strip_tags'] = [
'#type' => 'checkbox',
'#title' => $this->t('Strip tags'),
'#description' => $this->t('Strip HTML tags in each value.'),
'#default_value' => $this->options['strip_tags'],
];
$form['trim'] = [
'#type' => 'checkbox',
'#title' => $this->t('Trim whitespace'),
'#description' => $this->t('Trim leading and trailing whitespace for each value.'),
'#default_value' => $this->options['trim'],
];
}
/**
* Returns an array of format options.
*
* @return string[]
* An array of format options. Both key and value are the same.
*/
protected function getDelimiterOptions() {
return [
',' => $this->t('Comma ,'),
'|' => $this->t('Pipe |'),
"\t" => $this->t('Tab \t'),
];
}
/**
* For non-CSV, replace any delimiters in the value.
*
* @param resource $stream
* The open stream to write.
* @param array $row
* The current row of data.
* @param string $delimiter
* The delimiter.
*/
protected function outputWithDelimiter($stream, array $row, $delimiter) {
if ($delimiter === ',') {
fputcsv($stream, $row);
return;
}
// For other delimiters replace them in the row if present.
if ($delimiter === '|') {
$replacement = ';';
}
else {
$replacement = ' ';
}
foreach ($row as $k => $v) {
$row[$k] = str_replace($delimiter, $replacement, $v);
}
fwrite($stream, implode($delimiter, $row) . "\n");
}
/**
* Extracts the headers using the first row of values.
*
* Copied from \Drupal\csv_serialization\Encoder\CsvEncoder::extractHeaders().
*
* We must make the assumption that each row shares the same set of headers
* will all other rows. This is inherent in the structure of a CSV.
*
* @param array $first_row
* The first row of of data to be converted to a CSV.
*
* @return array
* An array of CSV headers.
*/
protected function extractHeaders(array $first_row) {
$headers = [];
$allowed_headers = array_keys($first_row);
$fields = (array) $this->displayHandler->getOption('fields');
foreach ($allowed_headers as $allowed_header) {
$headers[] = !empty($fields[$allowed_header]['label']) ? $fields[$allowed_header]['label'] : $allowed_header;
}
return $headers;
}
/**
* {@inheritdoc}
*/
public function render() {
$delimiter = $this->options['delimiter'];
$stream = $this->displayHandler->getOutputStream();
// Only the $strip_tags and $trim parameters matter for ::formatRow().
$encoder = new CsvEncoder(",", '"', "\\", (bool) $this->options['strip_tags'], (bool) $this->options['trim']);
// If the Data Entity row plugin is used, this will be an array of entities
// which will pass through Serializer to one of the registered Normalizers,
// which will transform it to arrays/scalars. If the Data field row plugin
// is used, $rows will not contain objects and will pass directly to the
// Encoder.
$headers = NULL;
// We expect to be able to traverse the view, but sanity check here.
if ($this->view instanceof \Traversable) {
$traversable = $this->view;
}
else {
$traversable = $this->view->result;
}
foreach ($traversable as $row_index => $row) {
$data = $this->view->rowPlugin->render($row);
if (!isset($headers)) {
$headers = $this->extractHeaders((array) $data);
$this->outputWithDelimiter($stream, $headers, $delimiter);
}
$row = $encoder->formatRow($data);
$this->outputWithDelimiter($stream, $row, $delimiter);
}
return '';
}
/**
* Gets the available format that can be requested.
*
* @return string
* An format.
*/
public function getFormat() {
return 'csv';
}
/**
* {@inheritdoc}
*/
public function getCacheMaxAge() {
return 0;
}
/**
* {@inheritdoc}
*/
public function getCacheContexts() {
return ['request_format'];
}
/**
* {@inheritdoc}
*/
public function getCacheTags() {
return [];
}
}
