class views_data_export_pdf_plugin_display_export in Views Data Export PDF 7
Same name and namespace in other branches
- 7.2 plugins/views_data_export_pdf_plugin_display_export.inc \views_data_export_pdf_plugin_display_export
The PDF export display plug-in for Views Data Export.
The plug-in supports both batched operation and full result-set operation.
Hierarchy
- class \views_data_export_pdf_plugin_display_export extends \views_data_export_plugin_display_export uses views_data_export_pdf_error_reporting_trait
Expanded class hierarchy of views_data_export_pdf_plugin_display_export
1 string reference to 'views_data_export_pdf_plugin_display_export'
- views_data_export_pdf_views_plugins in ./
views_data_export_pdf.views.inc - Implements hook_views_plugins().
File
- plugins/
views_data_export_pdf_plugin_display_export.inc, line 15 - Contains the Views Data Export Display plug-in for PDF format.
View source
class views_data_export_pdf_plugin_display_export extends views_data_export_plugin_display_export {
use views_data_export_pdf_error_reporting_trait;
/**
* Custom batch export status for when a PDF is being generated from HTML.
*/
const VIEWS_DATA_EXPORT_GENERATING_PDF = 'pdf_generation';
/**
* {@inheritDoc}
*/
public function get_style_type() {
return 'data_export_pdf';
}
/**
* {@inheritdoc}
*/
public function option_definition() {
$options = parent::option_definition();
$options['batch_result_storage']['default'] = 'placeholder';
$options['style_plugin']['default'] = 'views_data_export_pdf';
return $options;
}
/**
* {@inheritDoc}
*/
public function options_summary(&$categories, &$options) {
parent::options_summary($categories, $options);
if ($this
->is_batched()) {
$options['batch_result_storage'] = array(
'category' => 'page',
'title' => t('Batch results temporary storage'),
'value' => $this
->get_batch_result_storage_type(),
);
}
}
/**
* {@inheritDoc}
*/
public function options_form(&$form, &$form_state) {
parent::options_form($form, $form_state);
if ($form_state['section'] === 'batch_result_storage') {
$form['#title'] .= t('Batched view results storage');
$form['batch_result_storage'] = array(
'#type' => 'select',
'#title' => t('Temporary storage type'),
'#description' => '<p>' . t('Some Views plugins, especially in header and footer regions, require access to the entire views result set to render properly. This option controls how the raw results from each step of the batched export operation are stored and managed so that these plugins can function.') . '</p>' . '<dl>' . '<dt><strong>' . t('Full (temp file storage)') . '</strong></dt>' . '<dd>' . t("This is the most accurate method, but, depending upon result set size, it can run more slowly with a larger memory and disk footprint. It may fail on very large data sets. Use only if views plugins don't behave correctly without full result data.") . '</dd>' . '<dt><strong>' . t('Placeholder (default)') . '</strong></dt>' . '<dd>' . t('This method creates stand-ins for each view result, such that the <em>count</em> of results is accurate for headers and footer result summaries, <em>without any actual view result data</em>. This should work for most built-in Views header and footer plugins.') . '</dd>' . '<dt><strong>' . t('None') . '</strong></dt>' . '<dd>' . t('This method has no memory footprint, but it does not store any view results at all, so plugins that depend on view results may not function at all. Use this option if you are not using any plugins in the header or footer regions.') . '</dd>' . '</dl>',
'#options' => [
'full_file' => t('Full (temp file storage)'),
'placeholder' => t('Placeholder'),
'none' => t('None'),
],
'#default_value' => $this
->get_batch_result_storage_type(),
);
}
}
/**
* {@inheritDoc}
*/
public function options_submit(&$form, &$form_state) {
parent::options_submit($form, $form_state);
switch ($form_state['section']) {
case 'batch_result_storage':
$this
->set_option('batch_result_storage', $form_state['values']['batch_result_storage']);
break;
}
}
/**
* {@inheritDoc}
*/
public function preview() {
$is_preview = $this
->is_rendering_preview();
if ($this
->is_batched() && !$is_preview) {
return '';
}
if ($is_preview) {
// Change the items per page.
$this->view
->set_items_per_page(20);
// Force a pager to be used.
$this
->set_option('pager', [
'type' => 'some',
'options' => [],
]);
$output_markup = $this->view
->render();
$body_content = preg_replace('/.*<body[^>]*>(.*)<\\/body>.*/is', '\\1', $output_markup, 1);
$preview = [
'notice' => [
'#theme' => 'html_tag',
'#tag' => 'p',
'#value' => t('A maximum of 20 items will be shown here, but all results will be shown on export.'),
],
'view_output' => [
'#markup' => $body_content,
],
];
return render($preview);
}
else {
return $this->view
->render();
}
}
/**
* {@inheritDoc}
*/
public function render() {
$output = NULL;
if ($this
->is_batched()) {
$output = $this
->render_batched();
}
else {
$output = $this
->render_entire_result_set_directly();
if (empty($output)) {
$this
->display_render_error_and_exit();
}
else {
$this
->add_http_headers();
}
}
return $output;
}
/**
* {@inheritDoc}
*
* If the view is in "developer mode", we're returning the raw HTML and
* skipping PDF conversion, so this method will set the content type header
* appropriately for HTML in that scenario.
*/
public function add_http_headers() {
parent::add_http_headers();
// Fix-up content type header when exporting raw HTML, to avoid errors
// about corrupt PDF files.
if ($this
->is_developer_mode()) {
drupal_add_http_header('Content-Type', views_data_export_pdf_renderer::MIME_TYPE_HTML);
}
}
/**
* {@inheritDoc}
*
* @throws views_data_export_pdf_conversion_exception
*/
public function execute_normal() {
$state =& $this->batched_execution_state;
$sandbox =& $state->sandbox;
$batch_phase =& $state->batch_state;
if ($batch_phase === self::VIEWS_DATA_EXPORT_GENERATING_PDF) {
$this
->execute_pdf_generation($batch_phase, $sandbox);
}
else {
$this
->execute_html_generation($batch_phase, $sandbox);
}
}
/**
* Renders export output appropriate for part of a batched export.
*
* @return mixed
* The output for the current portion of the batch operation.
*/
protected function render_batched() {
$this->view
->build();
$batch_state = $this->batched_execution_state->batch_state;
switch ($batch_state) {
case VIEWS_DATA_EXPORT_BODY:
$output = $this
->get_style_plugin()
->render_body($this->batched_execution_state->sandbox);
break;
case VIEWS_DATA_EXPORT_HEADER:
$output = $this
->get_style_plugin()
->render_header();
break;
case VIEWS_DATA_EXPORT_FOOTER:
$output = $this
->get_style_plugin()
->render_footer();
break;
default:
throw new RuntimeException('Unexpected batch state: ' . $batch_state);
}
return $output;
}
/**
* Renders the entire output of the export.
*
* @return mixed
* The output for the current state of the batch operation.
*/
protected function render_entire_result_set_directly() {
$html_output = $this
->get_style_plugin()
->render();
if ($this
->is_rendering_preview() || $this
->is_developer_mode()) {
$result = $html_output;
}
else {
$result = $this
->convert_html_to_pdf($html_output);
}
return $result;
}
/**
* Executes the view for a segment of the batch and adds the results to HTML.
*
* If the view has no more results, the batch moves into the phase of
* generating the final PDF.
*
* @param string $batch_phase
* A reference to the phase that the batch is currently in. This function
* modifies this if the batch should progress into PDF generation.
* @param array $sandbox
* A reference to the batch sandbox storage area. This is modified to
* reflect the overall progress and status of the batch operation.
*/
protected function execute_html_generation(string &$batch_phase, array &$sandbox) {
$starting_phase = $batch_phase;
parent::execute_normal();
$ending_phase = $batch_phase;
if ($starting_phase === VIEWS_DATA_EXPORT_BODY) {
$temp_store = $this
->build_temp_result_store($sandbox);
// Keep track of results across batch segments, so we can render
// headers and footers with appropriate context later.
$temp_store
->stash_view_results();
}
// Insert our own pseudo-phase for PDF generation. This is not a phase
// normally supported by VDE.
if ($ending_phase === VIEWS_DATA_EXPORT_FINISHED) {
$batch_phase = self::VIEWS_DATA_EXPORT_GENERATING_PDF;
$sandbox['finished'] = 0.9;
$sandbox['message'] = t('Saving PDF file.');
}
else {
// Scale the overall export progress to allocate the last 10% to PDF
// generation, to set the user's expectations appropriately.
$sandbox['finished'] *= 0.9;
}
}
/**
* Converts the results of the export from HTML into PDF format.
*
* This step is skipped if the view is running in "developer mode", which
* outputs the raw HTML directly instead of converting the output to PDF.
*
* If there is a load-balancer HTTP timeout during this step of the batch, and
* the user refreshes the batch progress page, the logic here should allow the
* user to wait patiently on the result of the other request thread that's
* still running on the backend. The lock will cause the batch to continually
* poll on the status of the lock -- without resulting in an HTTP timeout --
* for up to five minutes.
*
* @param string $batch_phase
* A reference to the phase that the batch is currently in. This function
* modifies this if the batch should progress into finalization.
* @param array $sandbox
* A reference to the batch sandbox storage area. This is modified to
* reflect the overall progress and status of the batch operation.
*
* @throws views_data_export_pdf_conversion_exception
* If PDF generation fails; this is usually due to site misconfiguration.
*/
protected function execute_pdf_generation(string &$batch_phase, array &$sandbox) {
try {
if ($this
->is_developer_mode() || $this
->convert_batch_output_to_pdf($sandbox)) {
$batch_phase = VIEWS_DATA_EXPORT_FINISHED;
$sandbox['finished'] = 1;
}
} catch (views_data_export_pdf_conversion_exception $ex) {
$this
->set_render_failed_error();
throw $ex;
}
}
/**
* Determine whether or not the view is being rendered for live preview.
*
* (This can be removed once #4 in 3112010 has been merged into VDE and VDE
* PDF adds an appropriate minimum version constraint in the .info file on
* the VDE module).
*
* @return bool
*/
protected function is_rendering_preview() {
return !empty($this->view->live_preview);
}
/**
* Determine whether the view display should operate in developer mode.
*
* This is based on the site builder's configuration of the display style.
*
* Developer mode bypasses rendering the export output through WK HTML to PDF,
* so the raw HTML becomes the export output.
*
* @return bool
*/
protected function is_developer_mode() {
return !empty($this->options['style_options']['developer_mode']);
}
/**
* Gets the storage type being used for storing views results during batches.
*
* @return string
*/
protected function get_batch_result_storage_type() {
return $this
->get_option('batch_result_storage') ?? 'placeholder';
}
/**
* Gets the style plugin for this view.
*
* @return views_data_export_pdf_plugin_style_export
*/
protected function get_style_plugin() {
/* @var views_data_export_pdf_plugin_style_export $style_plugin */
$style_plugin = $this->view->style_plugin;
return $style_plugin;
}
/**
* Creates a temporary store for view results during a batched export.
*
* @param array $sandbox
* A reference to the sandbox storage area for the batch.
*
* @return views_data_export_pdf_view_result_temp_store
* The temp store that was created.
*/
protected function build_temp_result_store(array &$sandbox) {
$temp_store_factories = [
'full_file' => function () use (&$sandbox) {
return new \views_data_export_pdf_view_full_result_file_store($this->view, $sandbox, function () {
return $this
->tempfile_create();
});
},
'placeholder' => function () use (&$sandbox) {
return new views_data_export_pdf_view_placeholder_result_store($this->view, $sandbox);
},
'none' => function () use (&$sandbox) {
return new views_data_export_pdf_view_no_op_result_store();
},
];
$temp_store_type = $this
->get_batch_result_storage_type();
$temp_store_factory = $temp_store_factories[$temp_store_type] ?? NULL;
if ($temp_store_factory === NULL) {
throw new RuntimeException('Unexpected batch result storage type: ' . $temp_store_type);
}
return $temp_store_factory();
}
/**
* {@inheritDoc}
*
* We ensure that the output file has the proper MIME type set on it at
* creation time.
*
* NOTE: If you are using File Entity alongside this module, you should have a
* patch applied for #2570377 if you are not using a version of that module
* that already includes a fix for that issue. Otherwise, MIME type settings
* will not get saved properly.
*/
protected function outputfile_create() {
$fid = parent::outputfile_create();
$file = $this
->file_load($fid);
$file->filemime = views_data_export_pdf_renderer::MIME_TYPE_HTML;
file_save($file);
return $fid;
}
/**
* Creates a temporary file that is managed by Drupal.
*
* @return int
* The unique identifier for the temporary file record.
*/
protected function tempfile_create() {
return parent::outputfile_create();
}
/**
* Converts an HTML document to a stream of PDF data.
*
* The conversion is performed synchronously, and therefore blocks further
* request processing until the conversion is complete. If PDF conversion
* fails, an appropriate error message is written to site logs, and NULL is
* returned.
*
* @param string $html
* The contents of the HTML document to convert to PDF.
*
* @return string|null
* Either the PDF byte stream; or NULL if PDF conversion failed.
*/
protected function convert_html_to_pdf($html) {
$result = NULL;
try {
$result = $this
->get_style_plugin()
->render_html_to_pdf($html);
} catch (\Exception $ex) {
$this
->log_exception($ex);
}
return $result;
}
/**
* Converts the output of the export from HTML to PDF formats.
*
* @return bool
* TRUE if conversion of the file was attempted and has completed; FALSE if
* the conversion is still in progress in a background thread.
*
* @throws views_data_export_pdf_conversion_exception
* If the conversion fails.
*/
protected function convert_batch_output_to_pdf(array &$sandbox) {
$this->view
->build();
if (empty($sandbox['conversion_initialized'])) {
// Before conversion officially starts, prepare the view for final
// rendering.
$temp_store = $this
->build_temp_result_store($sandbox);
$temp_store
->restore_view_results();
$sandbox['pdf_output_fid'] = $this
->tempfile_create();
// Avoid reinitialization
$sandbox['conversion_initialized'] = TRUE;
}
try {
$source_html_file = $this
->outputfile_entity();
$target_pdf_file = file_load($sandbox['pdf_output_fid']);
$finished = $this
->get_style_plugin()
->render_html_file_to_pdf($source_html_file, $target_pdf_file);
if ($finished) {
$this
->switch_output_file($target_pdf_file);
}
return $finished;
} finally {
if (isset($temp_store)) {
// The temp store is only needed until conversion has started.
$temp_store
->cleanup();
}
}
}
/**
* Switches the output file of the batched export to the specified file.
*
* This can be used to change which type of file (PDF instead of HTML) is
* returned from the batch.
*
* @param object $new_output_file
* The file entity that represents the new output file of the batch.
*/
protected function switch_output_file($new_output_file) {
$this->_output_file = $new_output_file;
$this->batched_execution_state->fid = $new_output_file->fid;
}
/**
* Displays an error message to the end user that PDF conversion failed.
*
* This is used when conversion from HTML to PDF fails, to avoid trying to
* send either malformed PDF data or raw HTML data to the browser.
*/
protected function display_render_error_and_exit() {
drupal_add_http_header('Status', '500 Service unavailable (with message)');
drupal_set_title(t('Cannot generate PDF'));
$this
->set_render_failed_error();
$body_message = t('The website encountered an unexpected error. Please try again later.');
/** @noinspection PhpUnhandledExceptionInspection */
print theme('maintenance_page', array(
'content' => $body_message,
));
exit;
}
/**
* Displays an error on the next page load to inform users of a PDF failure.
*
* This is used to give end-users a reason why a PDF that was requested did
* not appear, so that they contact the site owner.
*/
protected function set_render_failed_error() {
drupal_set_message(t('Failed to generate the PDF. Please contact the site administrator for assistance.'), 'error', FALSE);
}
}
Members
Name![]() |
Modifiers | Type | Description | Overrides |
---|---|---|---|---|
views_data_export_pdf_error_reporting_trait:: |
protected static | function | Logs an error message for this module to site logs. | |
views_data_export_pdf_error_reporting_trait:: |
protected static | function | Logs an exception to site logs. | |
views_data_export_pdf_error_reporting_trait:: |
protected static | function | Logs a warning message for this module to site logs. | |
views_data_export_pdf_plugin_display_export:: |
public | function | If the view is in "developer mode", we're returning the raw HTML and skipping PDF conversion, so this method will set the content type header appropriately for HTML in that scenario. | |
views_data_export_pdf_plugin_display_export:: |
protected | function | Creates a temporary store for view results during a batched export. | |
views_data_export_pdf_plugin_display_export:: |
protected | function | Converts the output of the export from HTML to PDF formats. | |
views_data_export_pdf_plugin_display_export:: |
protected | function | Converts an HTML document to a stream of PDF data. | |
views_data_export_pdf_plugin_display_export:: |
protected | function | Displays an error message to the end user that PDF conversion failed. | |
views_data_export_pdf_plugin_display_export:: |
protected | function | Executes the view for a segment of the batch and adds the results to HTML. | |
views_data_export_pdf_plugin_display_export:: |
public | function | ||
views_data_export_pdf_plugin_display_export:: |
protected | function | Converts the results of the export from HTML into PDF format. | |
views_data_export_pdf_plugin_display_export:: |
protected | function | Gets the storage type being used for storing views results during batches. | |
views_data_export_pdf_plugin_display_export:: |
protected | function | Gets the style plugin for this view. | |
views_data_export_pdf_plugin_display_export:: |
public | function | ||
views_data_export_pdf_plugin_display_export:: |
protected | function | Determine whether the view display should operate in developer mode. | |
views_data_export_pdf_plugin_display_export:: |
protected | function | Determine whether or not the view is being rendered for live preview. | |
views_data_export_pdf_plugin_display_export:: |
public | function | ||
views_data_export_pdf_plugin_display_export:: |
public | function | ||
views_data_export_pdf_plugin_display_export:: |
public | function | ||
views_data_export_pdf_plugin_display_export:: |
public | function | ||
views_data_export_pdf_plugin_display_export:: |
protected | function | We ensure that the output file has the proper MIME type set on it at creation time. | |
views_data_export_pdf_plugin_display_export:: |
public | function | ||
views_data_export_pdf_plugin_display_export:: |
public | function | ||
views_data_export_pdf_plugin_display_export:: |
protected | function | Renders export output appropriate for part of a batched export. | |
views_data_export_pdf_plugin_display_export:: |
protected | function | Renders the entire output of the export. | |
views_data_export_pdf_plugin_display_export:: |
protected | function | Displays an error on the next page load to inform users of a PDF failure. | |
views_data_export_pdf_plugin_display_export:: |
protected | function | Switches the output file of the batched export to the specified file. | |
views_data_export_pdf_plugin_display_export:: |
protected | function | Creates a temporary file that is managed by Drupal. | |
views_data_export_pdf_plugin_display_export:: |
constant | Custom batch export status for when a PDF is being generated from HTML. |