You are here

commerce_invoice_pdf.module in Commerce Invoice 7.2

The Commerce Invoice PDF module.

File

modules/pdf/commerce_invoice_pdf.module
View source
<?php

/**
 * @file
 * The Commerce Invoice PDF module.
 */
use Drupal\commerce_invoice\Entity\Invoice;

/**
 * Implements hook_menu().
 */
function commerce_invoice_pdf_menu() {
  $items = [];
  $items['invoices/%entity_object/pdf'] = [
    'title' => 'PDF',
    'load arguments' => [
      'commerce_invoice',
    ],
    'page callback' => 'commerce_invoice_pdf_deliver',
    'page arguments' => [
      1,
    ],
    'access callback' => 'entity_access',
    'access arguments' => [
      'view',
      'commerce_invoice',
      1,
    ],
    'type' => MENU_CALLBACK,
  ];
  $items['invoices/%entity_object/pdf/html'] = [
    'title' => 'PDF - HTML source',
    'load arguments' => [
      'commerce_invoice',
    ],
    'page callback' => 'commerce_invoice_pdf_html',
    'page arguments' => [
      1,
    ],
    'access arguments' => [
      'administer commerce_invoice entities',
    ],
    'type' => MENU_CALLBACK,
  ];
  return $items;
}

/**
 * Implements hook_entity_info_alter().
 */
function commerce_invoice_pdf_entity_info_alter(&$entity_info) {
  $entity_info['commerce_invoice']['view modes']['pdf'] = array(
    'label' => t('PDF'),
    'custom settings' => FALSE,
  );
}

/**
 * Implements hook_entity_view().
 */
function commerce_invoice_pdf_entity_view($entity, $type, $view_mode, $langcode) {

  // Add "Download PDF" link to the bottom of invoice page.
  if ($type == 'commerce_invoice' && $view_mode == 'full') {
    $entity->content['pdf_link'] = [
      '#theme' => 'link',
      '#weight' => 50,
      '#text' => t('Download PDF'),
      '#path' => 'invoices/' . $entity->invoice_id . '/pdf',
      '#options' => [
        'query' => [
          'download' => 1,
        ],
        'html' => FALSE,
        'attributes' => [],
      ],
    ];
  }
}

/**
 * Debug page callback to view the HTML used for generating the PDF.
 */
function commerce_invoice_pdf_html(Invoice $invoice) {
  drupal_add_http_header('Content-Type', 'text/html; charset=utf-8');
  echo theme('commerce_invoice_pdf_page', [
    'invoice' => $invoice,
    'browser' => TRUE,
  ]);
  drupal_exit();
}

/**
 * Page callback to generate and deliver a PDF file for an invoice.
 */
function commerce_invoice_pdf_deliver(Invoice $invoice) {
  $file = commerce_invoice_pdf_create($invoice);
  if (!$file || !is_file($file->uri)) {

    // If this error happens, it's probably to do with file permissions.
    drupal_set_message(t('The invoice PDF file could not be found.'), 'error');
    return MENU_NOT_FOUND;
  }

  // Transfer the file.
  $headers = array(
    'Content-Type' => $file->filemime,
    'Content-Length' => $file->filesize,
  );

  // The 'download' GET parameter will attempt to force a download, rather than
  // viewing the file directly.
  if (!empty($_GET['download'])) {
    $headers = array(
      'Content-Type' => 'force-download',
      'Content-Disposition' => 'attachment; filename="' . $file->filename . '"',
      'Content-Transfer-Encoding' => 'binary',
      'Accept-Ranges' => 'bytes',
    );
  }

  // The following invocation of hook_file_download() uses the same logic as the
  // core function file_download().
  // @see file_download()
  foreach (module_implements('file_download') as $module) {
    $function = $module . '_file_download';
    $result = $function($file->uri);
    if ($result == -1) {
      $headers = array();
      break;
    }
    if (isset($result) && is_array($result)) {
      $headers = array_merge($headers, $result);
    }
  }
  if (count($headers)) {
    file_transfer($file->uri, $headers);
  }
  return MENU_ACCESS_DENIED;
}

/**
 * Generate a PDF for an invoice.
 *
 * @param \Drupal\commerce_invoice\Entity\Invoice $invoice
 *
 * @return string
 *   The PDF contents, as a string.
 */
function commerce_invoice_pdf_generate(Invoice $invoice) {
  $dompdf = commerce_invoice_pdf_load_dompdf();
  if (!$dompdf) {
    throw new RuntimeException('The DOMPDF library was not found.');
  }
  $html = theme('commerce_invoice_pdf_page', [
    'invoice' => $invoice,
  ]);
  $dompdf
    ->set_paper('a4', 'portrait');
  $dompdf
    ->load_html($html);
  $dompdf
    ->render();
  return $dompdf
    ->output();
}

/**
 * Create a PDF for an invoice.
 *
 * @param Invoice $invoice
 * @param bool $create
 *   Whether to create a new file if it doesn't already exist (default: TRUE).
 *   You can check whether the file exists already by setting this to FALSE.
 * @param bool $recreate
 *   Whether to recreate (replace) the file if it already exists (default:
 *   FALSE).
 *
 *
 * @return object|FALSE
 *   An object describing the managed file for the PDF (including the 'uri'
 *   property), or FALSE on failure.
 *
 */
function commerce_invoice_pdf_create(Invoice $invoice, $create = TRUE, $recreate = FALSE) {

  // Find existing pdf if it exists.
  $fid = db_select('file_usage', 'f')
    ->fields('f', array(
    'fid',
  ))
    ->condition('module', 'commerce_invoice_pdf')
    ->condition('type', 'commerce_invoice')
    ->condition('id', $invoice->invoice_id)
    ->execute()
    ->fetchField();

  // If file exists and does not need to be recreated, just return it!
  if (!$recreate && !empty($fid)) {
    $file = file_load($fid);
    if ($file && file_exists($file->uri)) {
      return $file;
    }
  }

  // Return false if file should not be created.
  if (!$create) {
    return FALSE;
  }

  // Invoices must not be public! Make sure private file system is configured.
  if (!drupal_realpath('private://')) {
    watchdog('commerce_invoice', 'The private filesystem directory is not configured.', [], WATCHDOG_ERROR);
    return FALSE;
  }
  $dir = 'private://commerce_invoice/' . date('Y-m', $invoice->created);
  $filename = preg_replace('/[^a-z0-9-]+/i', '_', 'invoice_' . $invoice
    ->getInvoiceNumber()) . '.pdf';
  if (!file_prepare_directory($dir, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS)) {
    watchdog('commerce_invoice', 'The directory does not exist or is not writable: @dir', [
      '@dir' => $dir,
    ], WATCHDOG_ERROR);
    return FALSE;
  }
  $data = commerce_invoice_pdf_generate($invoice);

  // file_save_data() cannot be used as it insists on global $user to be owner
  // of the file. In this use case, owner of the invoice must be owner of the
  // pdf.
  if ($uri = file_unmanaged_save_data($data, $dir . '/' . $filename, FILE_EXISTS_REPLACE)) {

    // Create a file object.
    $file = new stdClass();
    $file->fid = empty($fid) ? NULL : $fid;
    $file->uri = $uri;
    $file->filename = drupal_basename($uri);
    $file->filemime = file_get_mimetype($file->uri);
    $file->uid = $invoice->uid;
    $file->status = FILE_STATUS_PERMANENT;
    $file = file_save($file);

    // Increment usage only if the file was not recreated.
    if (empty($fid)) {
      file_usage_add($file, 'commerce_invoice_pdf', 'commerce_invoice', $invoice->invoice_id);
    }
    rules_invoke_all('commerce_invoice_pdf_created', $invoice, $file);
    return $file;
  }
  watchdog('commerce_invoice', 'Failed to create PDF file for invoice @id, path @path', [
    '@id' => $invoice->invoice_id,
    '@path' => $dir . '/' . $filename,
  ], WATCHDOG_ERROR);
  return FALSE;
}

/**
 * Helper function that instantiates new DOMPDF class.
 *
 * Uses config functionality from Commerce Billy.
 *
 * @see commerce_billy_pdf_prepare_dompdf()
 *
 * @return DOMPDF|\Dompdf\Dompdf|FALSE
 */
function commerce_invoice_pdf_load_dompdf() {
  $path = libraries_get_path('dompdf');
  if (!empty($path)) {
    $dir = drupal_realpath('public://');

    //@todo: Ditch these constants once DOMPDF 0.6.* support gets removed.
    if (!defined('DOMPDF_FONT_DIR')) {
      define('DOMPDF_FONT_DIR', $dir . '/fonts/');
    }
    if (!defined('DOMPDF_TEMP_DIR')) {
      define('DOMPDF_TEMP_DIR', file_directory_temp());
    }

    // Support for Dompdf 0.7.0 and newer, using PHP 5.3 namespaces.
    if (file_exists(DRUPAL_ROOT . '/' . $path . '/autoload.inc.php')) {
      require_once DRUPAL_ROOT . '/' . $path . '/autoload.inc.php';
      $dompdf = new \Dompdf\Dompdf([
        'tempDir' => DOMPDF_TEMP_DIR,
        'fontDir' => DOMPDF_FONT_DIR,
        'isHtml5ParserEnabled' => TRUE,
      ]);
      $dompdf
        ->setPaper('a4', 'portrait');
    }
    else {
      require_once DRUPAL_ROOT . '/' . $path . '/dompdf_config.inc.php';
      spl_autoload_register('DOMPDF_autoload');
      $dompdf = new DOMPDF();
      $dompdf
        ->set_paper('a4', 'portrait');
    }
    return $dompdf;
  }
  return FALSE;
}

/**
 * Implements hook_views_api().
 */
function commerce_invoice_pdf_views_api() {
  return [
    'api' => 3,
  ];
}

/**
 * Implements hook_theme().
 */
function commerce_invoice_pdf_theme() {
  $items['commerce_invoice_pdf_page'] = array(
    'variables' => [
      'invoice' => NULL,
      'inline_css' => '',
      'browser' => FALSE,
    ],
    'template' => 'commerce-invoice-pdf-page',
    'path' => drupal_get_path('module', 'commerce_invoice_pdf') . '/templates',
  );
  return $items;
}

/**
 * Preprocess function for the invoice PDF page template.
 */
function template_preprocess_commerce_invoice_pdf_page(&$variables) {

  /** @var Invoice $invoice */
  $invoice = $variables['invoice'];
  $variables['content'] = $invoice
    ->buildContent('pdf', NULL);
  $variables['title'] = commerce_invoice_title($invoice);
  $variables['invoice_date'] = format_date($invoice->invoice_date, 'custom', 'Y-m-d');
  $variables['invoice_due'] = format_date($invoice->invoice_due, 'custom', 'Y-m-d');
  $variables['invoice_number'] = check_plain($invoice
    ->getInvoiceNumber());
  if (!empty($invoice->order_id)) {
    $variables['order_number'] = check_plain($invoice
      ->wrapper()->order->order_number
      ->value());
  }
  $variables['logo'] = '';
  $logo = variable_get('commerce_invoice_pdf_logo', 'misc/druplicon.png');
  if ($logo) {
    $logo = !empty($variables['browser']) ? file_create_url($logo) : drupal_realpath($logo);
    $variables['logo'] = theme('image', [
      'path' => $logo,
    ]);
  }
  $price = $invoice
    ->wrapper()->commerce_invoice_total->amount
    ->value();
  $currency_code = $invoice
    ->wrapper()->commerce_invoice_total->currency_code
    ->value();
  $variables['total'] = commerce_currency_format($price, $currency_code);
  $css_files = variable_get('commerce_invoice_pdf_css_files', array(
    drupal_get_path('module', 'commerce_invoice_pdf') . '/css/pdf.css',
  ));
  $variables['inline_css'] = "";
  foreach ($css_files as $file) {
    $variables['inline_css'] .= file_get_contents($file);
  }
  $settings = variable_get('commerce_invoice_pdf_text_settings', array());
  foreach ([
    'header',
    'footer',
    'text',
  ] as $part) {
    $variables['invoice_' . $part] = isset($settings['invoice_' . $part]) ? $settings['invoice_' . $part] : '';
  }
  $variables['theme_hook_suggestions'][] = 'commerce_invoice_pdf_page';
  $variables['theme_hook_suggestions'][] = 'commerce_invoice_pdf_page__' . $invoice->number_pattern;
}

/**
 * Implements hook_action_info().
 */
function commerce_invoice_pdf_action_info() {
  $items['commerce_invoice_pdf_download_invoices'] = array(
    'type' => 'commerce_invoice',
    'label' => t('Zip and download PDF invoices'),
    'configurable' => FALSE,
    'vbo_configurable' => TRUE,
    'pass rows' => FALSE,
  );
  $items['commerce_invoice_pdf_recreate_pdfs'] = array(
    'type' => 'commerce_invoice',
    'label' => t('Recreate PDF invoices'),
    'configurable' => FALSE,
    'vbo_configurable' => FALSE,
    'pass rows' => FALSE,
  );
  return $items;
}

/**
 * Configuration form shown to the user before the action gets executed.
 */
function commerce_invoice_pdf_download_invoices_form($context) {
  $form['filename'] = array(
    '#type' => 'textfield',
    '#title' => t('Filename'),
    '#default_value' => 'invoices-archive--' . format_date(time(), 'custom', 'Y-m'),
    '#field_suffix' => '.zip',
    '#description' => t('The name of the archive file.'),
  );
  return $form;
}

/**
 * Assembles a unique URI for the archive.
 */
function commerce_invoice_pdf_download_invoices_submit($form, $form_state) {
  $dir = 'private://commerce_invoice/archives';
  if (!file_prepare_directory($dir, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS)) {
    throw new Exception(t('The directory does not exist or is not writable: @dir', [
      '@dir' => $dir,
    ]));
  }
  $destination = $dir . basename($form_state['values']['filename']) . '.zip';

  // If the chosen filename already exists, file_destination() will append
  // an integer to it in order to make it unique.
  $destination = file_destination($destination, FILE_EXISTS_RENAME);
  return array(
    'destination' => $destination,
  );
}

/**
 * Action callback to generate a PDF.
 */
function commerce_invoice_pdf_download_invoices($invoice, $context) {
  global $user;
  $destination = $context['destination'];
  _commerce_invoice_pdf_ensure_default_theme();

  // Get the invoice file.
  if ($file = commerce_invoice_pdf_create($invoice)) {
    $zip = new ZipArchive();

    // If the archive already exists, open it. If not, create it.
    if (file_exists($destination)) {
      $opened = $zip
        ->open(drupal_realpath($destination));
    }
    else {
      $opened = $zip
        ->open(drupal_realpath($destination), ZIPARCHIVE::CREATE | ZIPARCHIVE::OVERWRITE);
    }
    if ($opened) {
      $zip
        ->addFile(drupal_realpath($file->uri), $file->filename);
      $zip
        ->close();
    }
  }

  // The operation is complete, create a temporary file entity and provide a
  // download link to the user.
  if ($context['progress']['current'] == $context['progress']['total']) {
    $archive_file = new stdClass();
    $archive_file->uri = $destination;
    $archive_file->filename = basename($destination);
    $archive_file->filemime = file_get_mimetype($destination);
    $archive_file->uid = $user->uid;
    $archive_file->status = FALSE;
    clearstatcache();
    file_save($archive_file);
    $url = file_create_url($archive_file->uri);
    $url = l($url, $url, array(
      'absolute' => TRUE,
    ));
    _views_bulk_operations_log(t('An archive has been created and can be downloaded from: !url', array(
      '!url' => $url,
    )));
  }
}

/**
 * Action callback to generate a PDF.
 */
function commerce_invoice_pdf_recreate_pdfs($invoice) {
  _commerce_invoice_pdf_ensure_default_theme();
  commerce_invoice_pdf_create($invoice, TRUE, TRUE);
}

/**
 * Switch to the default theme, in order to render the invoice properly if the
 * admin theme was other than the default one.
 */
function _commerce_invoice_pdf_ensure_default_theme() {
  global $theme;
  $theme = NULL;
  $custom_theme =& drupal_static('menu_get_custom_theme');
  $custom_theme = variable_get('theme_default', 'bartik');
  drupal_theme_initialize();
}

/**
 * Implements hook_advanced_queue_info().
 */
function commerce_invoice_pdf_advanced_queue_info() {
  $items['commerce_invoice_pdf_create_queue'] = array(
    'label' => t('Create PDF invoices queue'),
    'worker callback' => 'commerce_invoice_pdf_create_worker',
    'retry after' => 30,
    'max attempts' => 5,
    // PDF creation is relatively slow process with a low priority
    'weight' => 100,
  );
  return $items;
}
function commerce_invoice_pdf_create_worker($item, $end_time = FALSE) {
  $data = $item->data;

  /** @var Invoice $invoice */
  if ($invoice = entity_load_single('commerce_invoice', $data['invoice_id'])) {
    _commerce_invoice_pdf_ensure_default_theme();

    // Make sure that any entity_access() down the line returns TRUE.
    global $user;
    if ($user->uid !== $invoice->uid) {
      $user = user_load($invoice->uid);
    }
    if (commerce_invoice_pdf_create($invoice, TRUE, $data['recreate'])) {
      return array(
        'status' => ADVANCEDQUEUE_STATUS_SUCCESS,
        'result' => 'Processed ' . $item->item_id,
      );
    }
  }
  return array(
    'status' => ADVANCEDQUEUE_STATUS_FAILURE,
    'result' => 'Error processing queue item ' . $item->item_id,
  );
}

Functions

Namesort descending Description
commerce_invoice_pdf_action_info Implements hook_action_info().
commerce_invoice_pdf_advanced_queue_info Implements hook_advanced_queue_info().
commerce_invoice_pdf_create Create a PDF for an invoice.
commerce_invoice_pdf_create_worker
commerce_invoice_pdf_deliver Page callback to generate and deliver a PDF file for an invoice.
commerce_invoice_pdf_download_invoices Action callback to generate a PDF.
commerce_invoice_pdf_download_invoices_form Configuration form shown to the user before the action gets executed.
commerce_invoice_pdf_download_invoices_submit Assembles a unique URI for the archive.
commerce_invoice_pdf_entity_info_alter Implements hook_entity_info_alter().
commerce_invoice_pdf_entity_view Implements hook_entity_view().
commerce_invoice_pdf_generate Generate a PDF for an invoice.
commerce_invoice_pdf_html Debug page callback to view the HTML used for generating the PDF.
commerce_invoice_pdf_load_dompdf Helper function that instantiates new DOMPDF class.
commerce_invoice_pdf_menu Implements hook_menu().
commerce_invoice_pdf_recreate_pdfs Action callback to generate a PDF.
commerce_invoice_pdf_theme Implements hook_theme().
commerce_invoice_pdf_views_api Implements hook_views_api().
template_preprocess_commerce_invoice_pdf_page Preprocess function for the invoice PDF page template.
_commerce_invoice_pdf_ensure_default_theme Switch to the default theme, in order to render the invoice properly if the admin theme was other than the default one.