public static function DataExport::processBatch in Views data export 8
Implements callback_batch_operation() - perform processing on each batch.
Writes rendered data export View rows to an output file that will be returned by callback_batch_finished() (i.e. finishBatch) when we're done.
Parameters
string $view_id: ID of the view.
string $display_id: ID of the view display.
array $args: Views arguments.
array $exposed_input: Exposed input.
int $total_rows: Total rows.
array $query_parameters: Query string parameters.
string $redirect_url: Redirect URL.
mixed $context: Batch context information.
Throws
\Drupal\Core\Entity\EntityStorageException
\PhpOffice\PhpSpreadsheet\Exception
\PhpOffice\PhpSpreadsheet\Reader\Exception
\PhpOffice\PhpSpreadsheet\Writer\Exception
File
- src/
Plugin/ views/ display/ DataExport.php, line 664
Class
- DataExport
- Provides a data export display plugin.
Namespace
Drupal\views_data_export\Plugin\views\displayCode
public static function processBatch($view_id, $display_id, array $args, array $exposed_input, $total_rows, array $query_parameters, $redirect_url, &$context) {
// Add query string back to the URL for processing.
if ($query_parameters) {
\Drupal::request()->query
->add($query_parameters);
}
// Load the View we're working with and set its display ID so we get the
// content we expect.
$view = Views::getView($view_id);
$view
->setDisplay($display_id);
$view
->setArguments($args);
$view
->setExposedInput($exposed_input);
if (isset($context['sandbox']['progress'])) {
$view
->setOffset($context['sandbox']['progress']);
}
$export_limit = $view->display_handler
->getOption('export_limit');
$view
->preExecute($args);
// Build the View so the query parameters and offset get applied. so our
// This is necessary for the total to be calculated accurately and the call
// to $view->render() to return the items we expect to process in the
// current batch (i.e. not the same set of N, where N is the number of
// items per page, over and over).
$view
->build();
// First time through - create an output file to write to, set our
// current item to zero and our total number of items we'll be processing.
if (empty($context['sandbox'])) {
// Set the redirect URL and the automatic download configuration in the
// results array so they can be accessed when the batch is finished.
$context['results'] = [
'automatic_download' => $view->display_handler->options['automatic_download'],
'redirect_url' => $redirect_url,
];
// Initialize progress counter, which will keep track of how many items
// we've processed.
$context['sandbox']['progress'] = 0;
// Initialize file we'll write our output results to.
// This file will be written to with each batch iteration until all
// batches have been processed.
// This is a private file because some use cases will want to restrict
// access to the file. The View display's permissions will govern access
// to the file.
$current_user = \Drupal::currentUser();
$user_ID = $current_user
->isAuthenticated() ? $current_user
->id() : NULL;
$timestamp = \Drupal::time()
->getRequestTime();
$filename = \Drupal::token()
->replace($view
->getDisplay()->options['filename'], [
'view' => $view,
]);
$extension = reset($view
->getDisplay()->options['style']['options']['formats']);
// Checks if extension is already included in the filename.
if (!preg_match("/^.*\\.({$extension})\$/i", $filename)) {
$filename = $filename . "." . $extension;
}
$user_dir = $user_ID ? "{$user_ID}-{$timestamp}" : $timestamp;
$view_dir = $view_id . '_' . $display_id;
// Determine if the export file should be stored in the public or private
// file system.
$store_in_public_file_directory = TRUE;
$streamWrapperManager = \Drupal::service('stream_wrapper_manager');
// Check if the private file system is ready to use.
if ($streamWrapperManager
->isValidScheme('private')) {
$store_in_public_file_directory = $view
->getDisplay()
->getOption('store_in_public_file_directory');
}
if ($store_in_public_file_directory === TRUE) {
$directory = "public://views_data_export/{$view_dir}/{$user_dir}/";
}
else {
$directory = "private://views_data_export/{$view_dir}/{$user_dir}/";
}
try {
$fileSystem = \Drupal::service('file_system');
$fileSystem
->prepareDirectory($directory, FileSystemInterface::CREATE_DIRECTORY);
$destination = $directory . $filename;
$file = file_save_data('', $destination, FileSystemInterface::EXISTS_REPLACE);
if (!$file) {
// Failed to create the file, abort the batch.
unset($context['sandbox']);
$context['success'] = FALSE;
throw new StorageException('Could not create a temporary file.');
}
$file
->setTemporary();
$file
->save();
// Create sandbox variable from filename that can be referenced
// throughout the batch processing.
$context['sandbox']['vde_file'] = $file
->getFileUri();
// Store URI of export file in results array because it can be accessed
// in our callback_batch_finished (finishBatch) callback. Better to do
// this than use a SESSION variable. Also, we're not returning any
// results so the $context['results'] array is unused.
$context['results']['vde_file'] = $context['sandbox']['vde_file'];
} catch (StorageException $e) {
$message = t('Could not write to temporary output file for result export (@file). Check permissions.', [
'@file' => $context['sandbox']['vde_file'],
]);
\Drupal::logger('views_data_export')
->error($message);
}
}
// Render the current batch of rows - these will then be appended to the
// output file we write to each batch iteration.
// Make sure that if limit is set the last batch will output the remaining
// amount of rows and not more.
$items_this_batch = $view->display_handler
->getOption('export_batch_size');
if ($export_limit && $context['sandbox']['progress'] + $items_this_batch > $export_limit) {
$items_this_batch = $export_limit - $context['sandbox']['progress'];
}
// Set the limit directly on the query.
$view->query
->setLimit((int) $items_this_batch);
$view
->execute($display_id);
// Check to see if the build failed.
if (!empty($view->build_info['fail'])) {
return;
}
if (!empty($view->build_info['denied'])) {
return;
}
// We have to render the whole view to get all hooks executes.
// Only rendering the display handler would result in many empty fields.
$rendered_rows = $view
->render();
$string = (string) $rendered_rows['#markup'];
// Workaround for CSV headers, remove the first line.
if ($context['sandbox']['progress'] != 0 && reset($view
->getStyle()->options['formats']) == 'csv') {
$string = preg_replace('/^[^\\n]+/', '', $string);
}
// Workaround for XML.
$output_format = reset($view
->getStyle()->options['formats']);
if ($output_format == 'xml') {
$maximum = $export_limit ? $export_limit : $total_rows;
// Remove xml declaration and response opening tag.
if ($context['sandbox']['progress'] != 0) {
$string = str_replace('<?xml version="1.0"?>', '', $string);
$string = str_replace('<response>', '', $string);
}
// Remove response closing tag.
if ($context['sandbox']['progress'] + $items_this_batch < $maximum) {
$string = str_replace('</response>', '', $string);
}
}
// Workaround for XLS/XLSX.
if ($context['sandbox']['progress'] != 0 && ($output_format == 'xls' || $output_format == 'xlsx')) {
$vdeFileRealPath = \Drupal::service('file_system')
->realpath($context['sandbox']['vde_file']);
$previousExcel = IOFactory::load($vdeFileRealPath);
file_put_contents($vdeFileRealPath, $string);
$currentExcel = IOFactory::load($vdeFileRealPath);
// Append all rows to previous created excel.
$rowIndex = $previousExcel
->getActiveSheet()
->getHighestRow();
foreach ($currentExcel
->getActiveSheet()
->getRowIterator() as $row) {
if ($row
->getRowIndex() == 1) {
// Skip header.
continue;
}
$rowIndex++;
$colIndex = 0;
foreach ($row
->getCellIterator() as $cell) {
$previousExcel
->getActiveSheet()
->setCellValueByColumnAndRow(++$colIndex, $rowIndex, $cell
->getValue());
}
}
$objWriter = new Xlsx($previousExcel);
$objWriter
->save($vdeFileRealPath);
}
elseif (file_put_contents($context['sandbox']['vde_file'], $string, FILE_APPEND) === FALSE) {
// Write to output file failed - log in logger and in ResponseText on
// batch execution page user will end up on if write to file fails.
$message = t('Could not write to temporary output file for result export (@file). Check permissions.', [
'@file' => $context['sandbox']['vde_file'],
]);
\Drupal::logger('views_data_export')
->error($message);
throw new ServiceUnavailableHttpException(NULL, $message);
}
// Update the progress of our batch export operation (i.e. number of
// items we've processed). Note can exceed the number of total rows we're
// processing, but that's considered in the if/else to determine when we're
// finished below.
$context['sandbox']['progress'] += $items_this_batch;
// If our progress is less than the total number of items we expect to
// process, we updated the "finished" variable to show the user how much
// progress we've made via the progress bar.
if ($context['sandbox']['progress'] < $total_rows) {
$context['finished'] = $context['sandbox']['progress'] / $total_rows;
}
else {
// We're finished processing, set progress bar to 100%.
$context['finished'] = 1;
}
}