You are here

views_data_export_pdf_plugin_style_export.inc in Views Data Export PDF 7

Same filename and directory in other branches
  1. 7.2 plugins/views_data_export_pdf_plugin_style_export.inc

PDF style plugin.

File

plugins/views_data_export_pdf_plugin_style_export.inc
View source
<?php

/**
 * @file
 * PDF style plugin.
 */
class views_data_export_pdf_plugin_style_export extends views_data_export_plugin_style_export {

  /**
   * The renderer used when none has been chosen, or the chosen renderer is not
   * available.
   */
  const DEFAULT_RENDERER = 'wkhtmltopdf_in_proc';

  /**
   * Set options fields and default values.
   *
   * @return
   * An array of options information.
   */
  public function option_definition() {
    $options = parent::option_definition();
    $options['pdf_renderer'] = [
      'default' => 'wkhtmltopdf_in_proc',
      'translatable' => FALSE,
    ];
    $options['developer_mode'] = [
      'default' => FALSE,
      'translatable' => FALSE,
    ];
    $options['each_group_separate_table'] = [
      'default' => FALSE,
      'translatable' => FALSE,
    ];
    $options['landscape'] = [
      'default' => FALSE,
      'translatable' => FALSE,
    ];
    $options['page_width'] = [
      'default' => "",
      'translatable' => FALSE,
    ];
    $options['page_height'] = [
      'default' => "",
      'translatable' => FALSE,
    ];
    $options['user_style_sheet'] = [
      'default' => "",
      'translatable' => FALSE,
    ];
    return $options;
  }

  /**
   * Options form mini callback.
   *
   * @param $form
   * Form array to add additional fields to.
   * @param $form_state
   * State of the form.
   */
  public function options_form(&$form, &$form_state) {
    parent::options_form($form, $form_state);
    $form['pdf_renderer'] = $this
      ->build_renderer_selector();
    $form['developer_mode'] = [
      '#type' => 'checkbox',
      '#title' => t('Bypass PDF rendering and export intermediate HTML instead (for development and styling purposes)'),
      '#default_value' => $this->options['developer_mode'],
      '#description' => t('Enable this option to use the HTML markup that is normally fed into "WK HTML to PDF" as the export output, bypassing the conversion to PDF. This can be useful when crafting a user stylesheet.'),
    ];
    $form['each_group_separate_table'] = [
      '#type' => 'checkbox',
      '#title' => t('Each group as separate table'),
      '#default_value' => $this->options['each_group_separate_table'],
      '#description' => t('If used grouping, this option provide rendering each group as separate table.'),
    ];
    $form['landscape'] = [
      '#type' => 'checkbox',
      '#title' => t('Landscape'),
      '#default_value' => $this->options['landscape'],
      '#description' => t('PDF Landscape format.'),
    ];
    $form['page_width'] = [
      '#type' => 'textfield',
      '#title' => t('Page width'),
      '#default_value' => $this->options['page_width'],
      '#description' => t('Page width in mm, prior to rotation for portrait/landscape orientation. If blank, defaults to 210 mm (A4).'),
    ];
    $form['page_height'] = [
      '#type' => 'textfield',
      '#title' => t('Page height'),
      '#default_value' => $this->options['page_height'],
      '#description' => t('Page height in mm, prior to rotation for portrait/landscape orientation. If blank, defaults to 297 mm (A4).'),
    ];
    $form['user_style_sheet'] = [
      '#type' => 'textfield',
      '#title' => t('Custom stylesheet path'),
      '#default_value' => $this->options['user_style_sheet'],
      '#description' => t('Override the default stylesheet with a custom, site-specific stylesheet. Path is relative to site base path. For example: <code>sites/default/files/pdf_style.css</code>'),
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function validate() {
    $errors = parent::validate();
    $renderers = views_data_export_pdf_get_renderer_types();
    if (empty($renderers)) {
      $errors[] = t('No Views Data Export PDF renderer sub-modules are installed. Style @style requires at least one.', [
        '@style' => $this->definition['title'],
      ]);
    }
    return $errors;
  }

  /**
   * {@inheritDoc}
   */
  public function render_header() {
    $output = '';
    $head = $this
      ->get_html_head_markup();
    $vars = [
      'view' => $this->view,
      'options' => $this->options,
      'head' => $head,
    ];
    $theme_functions = $this
      ->get_all_theme_functions('header');
    $output .= theme($theme_functions, $vars);
    $output .= theme("views_data_export_pdf_theader", $vars);
    return $output;
  }

  /**
   * {@inheritDoc}
   */
  public function render_body(&$batch_sandbox = NULL) {
    if ($this
      ->uses_row_plugin() && empty($this->row_plugin)) {
      vpr('views_plugin_style_default: Missing row plugin');
      return;
    }

    // Group the rows according to the grouping field, if specified.
    $sets = $this
      ->render_grouping($this->view->result, $this->options['grouping']);

    // Render each group separately and concatenate.  Plugins may override this
    // method if they wish some other way of handling grouping.
    $output = '';
    if (!isset($batch_sandbox['group_titles'])) {
      $batch_sandbox['group_titles'] = [];
    }
    $group_titles = isset($batch_sandbox['group_titles']) ? $batch_sandbox['group_titles'] : [];
    foreach ($sets as $title => $records) {
      if ($this
        ->uses_row_plugin()) {
        $rows = [];
        foreach ($records as $row_index => $row) {
          $this->view->row_index = $row_index;
          $rows[] = $this->row_plugin
            ->render($row);
        }
      }
      else {
        $rows = $records;
      }
      $theme_functions = $this
        ->get_all_theme_functions('body');
      $vars = [
        'view' => $this->view,
        'options' => $this->options,
        'rows' => $rows,
        'title' => $title,
        'group_titles' => $group_titles,
      ];
      $output .= theme($theme_functions, $vars);
      if (!in_array($title, $batch_sandbox['group_titles'])) {
        $batch_sandbox['group_titles'][] = $title;
      }
    }
    unset($this->view->row_index);
    return $output;
  }

  /**
   * {@inheritDoc}
   */
  public function render_footer() {
    $title = '';
    $theme_functions = $this
      ->get_all_theme_functions('footer');
    $vars = [
      'view' => $this->view,
      'options' => $this->options,
      'title' => $title,
    ];
    $output = '';
    $output .= theme("views_data_export_pdf_tfooter", $vars);
    $output .= theme($theme_functions, $vars);
    return $output;
  }

  /**
   * Invokes the renderer to converts the given HTML file into PDF format.
   *
   * @param object $source_html_file
   *   The file entity wrapping the HTML file to convert.
   * @param object $target_pdf_file
   *   The file entity wrapping an (empty) target PDF output file.
   *
   * @return bool
   *   TRUE if conversion of the file was attempted and has completed; FALSE if
   *   the conversion is still in progress.
   *
   * @throws views_data_export_pdf_conversion_exception
   *   If the conversion fails.
   */
  public function render_html_file_to_pdf($source_html_file, $target_pdf_file) {
    $renderer_options = $this
      ->build_renderer_options();
    $renderer = $this
      ->build_pdf_renderer();
    return $renderer
      ->render_html_file_to_pdf($source_html_file, $target_pdf_file, $renderer_options);
  }

  /**
   * Invokes the renderer to converts the given HTML content into PDF format.
   *
   * Since the PDF output must be returned by this function, the conversion is
   * performed synchronously, and therefore blocks further request processing
   * until the conversion is complete.
   *
   * @param string $source_html
   *   The HTML content to convert.
   *
   * @return string
   *   The result of the conversion.
   *
   * @throws views_data_export_pdf_conversion_exception
   *   If the conversion fails.
   */
  public function render_html_to_pdf(string $source_html) {
    $renderer_options = $this
      ->build_renderer_options();
    $renderer = $this
      ->build_pdf_renderer();
    return $renderer
      ->render_html_to_pdf($source_html, $renderer_options);
  }

  /**
   * Determine whether or not each group of values belongs in a separate table.
   *
   * This is based on the site builder's configuration of the display style.
   *
   * @return bool
   */
  protected function is_grouping_into_separate_tables() {
    return !empty($this->options['each_group_separate_table']);
  }

  /**
   * Gets the type of renderer for generating PDFs.
   *
   * If the PDF renderer that was chosen in the plug-in configuration is not
   * available, this will automatically fall back to instantiating
   * self::DEFAULT_RENDERER. If that's not available, the first available PDF
   * renderer is selected.
   *
   * @return string|null
   *   Either the machine name of the renderer to use; or NULL if there are no
   *   installed renderers.
   */
  protected function get_pdf_renderer_type() {
    $renderers = views_data_export_pdf_get_renderer_types();
    $renderer_type = $this->options['pdf_renderer'] ?? self::DEFAULT_RENDERER;
    if (!isset($renderers[$renderer_type])) {

      // Fall back to default renderer if installed.
      if (isset($renderers[self::DEFAULT_RENDERER])) {
        $renderer_type = self::DEFAULT_RENDERER;
      }
      elseif (empty($renderers)) {
        $renderer_type = NULL;
      }
      else {
        $renderer_types = array_keys($renderers);
        $renderer_type = reset($renderer_types);
      }
    }
    return $renderer_type;
  }

  /**
   * Gets the type of engine that the renderer is using.
   *
   * @return string
   */
  protected function get_pdf_render_engine() {
    $renderer_type = $this
      ->get_pdf_renderer_type();

    /** @noinspection PhpUnnecessaryLocalVariableInspection */
    $render_engine = preg_replace('/^([^_]+)_.*$/', '\\1', $renderer_type);
    return $render_engine;
  }

  /**
   * Gets the relative path to the stylesheet to use for rendering HTML content.
   *
   * The stylesheet is used in both the larger HTML document as well as the
   * header and footer of every PDF page.
   */
  protected function get_stylesheet_path() {
    $default_style_sheet = _views_data_export_pdf_get_default_stylesheet_path();
    $raw_style_sheet_path = trim($this->options['user_style_sheet']) ?: $default_style_sheet;

    /** @noinspection PhpUnnecessaryLocalVariableInspection */
    $trimmed_style_sheet_path = ltrim($raw_style_sheet_path, '/');
    return $trimmed_style_sheet_path;
  }

  /**
   * Returns the markup that should be included in <head> tags of templates.
   *
   * This includes the PDF stylesheet.
   *
   * @return string
   */
  protected function get_html_head_markup() {
    $head = '';
    $style_sheet_path = $this
      ->get_stylesheet_path();
    if (file_exists($style_sheet_path)) {
      $styles = [
        '#type' => 'styles',
        '#items' => [
          $style_sheet_path => [
            'type' => 'file',
            'group' => CSS_DEFAULT,
            'weight' => 0,
            'every_page' => FALSE,
            'media' => 'all',
            'preprocess' => FALSE,
            'data' => $style_sheet_path,
            'browsers' => [
              'IE' => TRUE,
              '!IE' => TRUE,
            ],
          ],
        ],
      ];
      $head = drupal_render($styles);
    }
    else {
      watchdog('views_data_export_pdf', 'Stylesheet not found: %path', [
        '%path' => $style_sheet_path,
      ]);
    }
    return $head;
  }

  /**
   * Gets all theme functions defined for the specified export component.
   *
   * @param string $component
   *   The component name (header, footer, body, etc).
   *
   * @return string[]
   *   The names of all theme functions to invoke when rendering the component.
   */
  protected function get_all_theme_functions(string $component) {
    $base_name = $this->definition['additional themes base'];
    return $this
      ->theme_functions(sprintf('%s_%s', $base_name, $component));
  }

  /**
   * Gets a drop-down populated with options for all renderers on this site.
   *
   * @see hook_pdf_export_renderers()
   *
   * @return array
   *   An associative array containing the titles of all PDF renderers exposed
   *   by modules. Each description is keyed by its machine name.
   */
  protected function build_renderer_selector() {
    $all_renderer_info = views_data_export_pdf_get_renderer_types();
    $renderer_options = [];
    $descriptions = [];
    foreach ($all_renderer_info as $machine_name => $renderer_info) {
      $title = $renderer_info['title'];
      $description = $renderer_info['description'];
      $renderer_options[$machine_name] = $title;
      $descriptions[] = sprintf('<dt><strong>%s</strong></dt><dd>%s</dd>', $title, $description);
    }
    $description = '<p>' . t('Specifies how HTML is rendered to PDF. Options:') . '</p>' . '<dl>' . implode('', $descriptions) . '</dl>';

    /** @noinspection PhpUnnecessaryLocalVariableInspection */
    $selector = [
      '#type' => 'select',
      '#title' => t('PDF renderer'),
      '#description' => $description,
      '#options' => $renderer_options,
      '#default_value' => $this
        ->get_pdf_renderer_type(),
    ];
    return $selector;
  }

  /**
   * Instantiates the appropriate renderer for exporting a PDF.
   *
   * If the PDF renderer that was chosen in the plug-in configuration is not
   * available, this will automatically fall back to instantiating
   * self::DEFAULT_RENDERER. If that's not available, the first available PDF
   * renderer is selected.
   *
   * @return views_data_export_pdf_renderer
   *   The renderer to use for HTML to PDF conversion.
   *
   * @throws views_data_export_pdf_conversion_exception
   *   If there are no PDF renderer sub-modules enabled.
   */
  protected function build_pdf_renderer() {
    $renderers = views_data_export_pdf_get_renderer_types();
    $renderer_type = $this
      ->get_pdf_renderer_type();
    if (empty($renderer_type)) {
      throw new views_data_export_pdf_conversion_exception('No PDF renderers are installed.');
    }
    $renderer_info = $renderers[$renderer_type];
    $class_name = $renderer_info['class'];
    $include_file = $renderer_info['file'] ?? NULL;
    $include_path = $renderer_info['file path'];
    $constructor_args = $renderer_info['constructor arguments'] ?? [];
    if ($include_file !== NULL) {
      $file_path = sprintf('%s/%s', $include_path, $include_file);
      require_once $file_path;
    }
    return new $class_name(...$constructor_args);
  }

  /**
   * Builds options for the PDF renderer based on the view display config.
   *
   * @return string[]
   *   The options to pass-in to the renderer.
   */
  protected function build_renderer_options() {
    $is_landscape = $this->options['landscape'] ?? FALSE;
    $options = [
      'orientation' => $is_landscape ? 'landscape' : 'portrait',
      'page_width' => $this->options['page_width'] ?? '',
      'page_height' => $this->options['page_height'] ?? '',
      'header_html' => $this
        ->render_pdf_page_header(),
      'footer_html' => $this
        ->render_pdf_page_footer(),
    ];
    drupal_alter('views_data_export_renderer_options', $options);
    return $options;
  }

  /**
   * Gets HTML for the text at the top of each PDF page.
   *
   * @return string
   *   The HTML for headers of the PDF.
   *
   * @noinspection PhpDocMissingThrowsInspection
   */
  protected function render_pdf_page_header() {
    $vars = [
      'view' => $this->view,
      'options' => $this->options,
      'renderer' => $this
        ->get_pdf_renderer_type(),
      'render_engine' => $this
        ->get_pdf_render_engine(),
      'stylesheet_path' => $this
        ->get_stylesheet_path(),
    ];

    /** @noinspection PhpUnhandledExceptionInspection */
    return theme('views_data_export_pdf_pheader', $vars);
  }

  /**
   * Gets HTML for the text at the bottom of each PDF page.
   *
   * @return string
   *   The HTML for footers of the PDF.
   *
   * @noinspection PhpDocMissingThrowsInspection
   */
  protected function render_pdf_page_footer() {
    $vars = [
      'view' => $this->view,
      'options' => $this->options,
      'renderer' => $this
        ->get_pdf_renderer_type(),
      'render_engine' => $this
        ->get_pdf_render_engine(),
      'stylesheet_path' => $this
        ->get_stylesheet_path(),
    ];

    /** @noinspection PhpUnhandledExceptionInspection */
    return theme('views_data_export_pdf_pfooter', $vars);
  }

}

Classes

Namesort descending Description
views_data_export_pdf_plugin_style_export @file PDF style plugin.