You are here

commerce_billy_pdf.module in Commerce Billy 7

Commerce Billy module file.

File

modules/commerce_billy_pdf/commerce_billy_pdf.module
View source
<?php

/**
 * @file
 * Commerce Billy module file.
 */

/**
 * Implements hook_menu().
 */
function commerce_billy_pdf_menu() {
  $items['invoice-pdf/%commerce_order'] = array(
    'title' => 'Invoice',
    'page callback' => 'commerce_billy_pdf',
    'page arguments' => array(
      1,
    ),
    'access callback' => 'commerce_billy_pdf_view_access',
    'access arguments' => array(
      1,
    ),
    'type' => MENU_CALLBACK,
  );

  // Add a HTML version of the invoice for easier debugging for themers and
  // printing.
  $items['invoice-pdf/%commerce_order/print'] = array(
    'title' => 'HTML Invoice',
    'page callback' => 'commerce_billy_pdf_html_page',
    'page arguments' => array(
      1,
    ),
    'access callback' => 'commerce_billy_pdf_view_access',
    'access arguments' => array(
      1,
    ),
    'type' => MENU_CALLBACK,
  );
  $items['admin/commerce/config/billy-invoice/pdf'] = array(
    'title' => 'PDF',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'commerce_billy_pdf_admin_form',
    ),
    'access arguments' => array(
      'configure order settings',
    ),
    'type' => MENU_LOCAL_TASK,
    'file' => 'commerce_billy_pdf.admin.inc',
  );
  $items['admin/commerce/orders/%commerce_order/pdf-invoice'] = array(
    'title' => 'Invoice (PDF)',
    'page callback' => 'commerce_billy_pdf',
    'page arguments' => array(
      3,
    ),
    'access callback' => 'commerce_billy_pdf_view_access',
    'access arguments' => array(
      3,
    ),
    'type' => MENU_LOCAL_TASK,
    'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
    'weight' => 10,
  );
  return $items;
}

/**
 * Implements hook_admin_paths_alter().
 *
 * Render PDF using default theme.
 */
function commerce_billy_pdf_admin_paths_alter(&$paths) {
  $paths['admin/commerce/orders/*/pdf-invoice'] = FALSE;
}

/**
 * Access callback for the PDF invoice.
 *
 * Deny access if order is not in status 'invoiced' or the current user does not
 * have view access to the order itself.
 * Furthermore admins can access 'canceled' orders (credit memo).
 */
function commerce_billy_pdf_view_access($order) {
  if ($order->status == 'canceled' && $order->order_id != $order->order_number && user_access('administer commerce_order entities')) {
    return TRUE;
  }
  elseif (!in_array($order->status, variable_get('commerce_billy_pdf_order_statuses', array(
    'invoiced',
  )))) {
    return FALSE;
  }
  return commerce_order_customer_order_view_access($order);
}

/**
 * Implements hook_views_api().
 */
function commerce_billy_pdf_views_api() {
  return array(
    'api' => 3,
    'path' => drupal_get_path('module', 'commerce_billy_pdf') . '/includes/views',
  );
}

/**
 * Implements hook_entity_info_alter().
 */
function commerce_billy_pdf_entity_info_alter(&$info) {
  $info['commerce_order']['view modes']['pdf'] = array(
    'label' => t('PDF'),
    'custom settings' => FALSE,
  );
  $info['commerce_order']['view modes']['canceled'] = array(
    'label' => t('Credit memo'),
    'custom settings' => FALSE,
  );
}

/**
 * Implements hook_theme().
 */
function commerce_billy_pdf_theme() {

  // Register the template specific for the commerce pdf order.
  $items['commerce_order__commerce_order__pdf'] = array(
    'render element' => 'elements',
    'template' => 'commerce_order--commerce_order--pdf',
    'path' => drupal_get_path('module', 'commerce_billy_pdf') . '/templates',
  );
  $items['commerce_order__commerce_order__canceled'] = array(
    'render element' => 'elements',
    'template' => 'commerce_order--commerce_order--canceled',
    'path' => drupal_get_path('module', 'commerce_billy_pdf') . '/templates',
  );
  $items['commerce_billy_pdf_page'] = array(
    'variables' => array(
      'viewed_orders' => array(),
      'inline_css' => '',
    ),
    'template' => 'commerce_billy_pdf_page',
    'path' => drupal_get_path('module', 'commerce_billy_pdf') . '/templates',
  );
  return $items;
}

/**
 * Implements hook_preprocess_entity().
 *
 * Adds theme hook suggestions for the pdf and canceled view modes, allowing the
 * commerce_billy_pdf templates to be used for orders of all bundles.
 */
function commerce_billy_pdf_preprocess_entity(&$variables) {
  $entity_type = $variables['entity_type'];
  $view_mode = $variables['view_mode'];
  $pdf_view_modes = array(
    'pdf',
    'canceled',
  );
  if ($entity_type == 'commerce_order' && in_array($view_mode, $pdf_view_modes)) {
    $variables['theme_hook_suggestions'][] = $entity_type . '__commerce_order__' . $view_mode;
  }
}

/**
 * Page callback for invoice PDF.
 */
function commerce_billy_pdf($order) {
  $html = commerce_billy_pdf_html($order);
  $filename = preg_replace('/[^a-z0-9]/', '_', drupal_strtolower('invoice_' . $order->order_number));
  commerce_billy_pdf_output($html, $filename);

  // print $html;
  drupal_exit();
}

/**
 * Page callback for the HTML version of the invoice.
 */
function commerce_billy_pdf_html_page($order) {
  $html = commerce_billy_pdf_html($order);
  print $html;
  drupal_exit();
}

/**
 * Helper function that returns the generated HTML for the invoice PDFs.
 *
 * @param object[] $orders
 *   Array of order objects.
 */
function commerce_billy_pdf_html($orders) {

  // Backwards compatibilty: also accept a single order object.
  if (is_object($orders)) {
    $orders = array(
      $orders,
    );
  }
  foreach ($orders as $order) {
    $vars['viewed_orders'][] = entity_view('commerce_order', array(
      $order->order_id => $order,
    ), 'pdf', NULL, TRUE);

    // Add a credit memo.
    if ($order->status == 'canceled') {
      $vars['viewed_orders'][] = entity_view('commerce_order', array(
        $order->order_id => $order,
      ), 'canceled', NULL, TRUE);
    }
  }
  $css_files = variable_get('commerce_billy_pdf_css_files', array(
    drupal_get_path('module', 'commerce_billy_pdf') . '/css/pdf.css',
  ));
  if (variable_get('commerce_billy_pdf_converter', 'dompdf') == 'wkhtmltopdf') {
    $css_files[] = drupal_get_path('module', 'commerce_billy_pdf') . '/css/pdf_wkhtmltopdf.css';
  }
  $vars['inline_css'] = "";
  foreach ($css_files as $file) {
    $vars['inline_css'] .= file_get_contents($file);
  }
  return theme('commerce_billy_pdf_page', $vars);
}

/**
 * Implements hook_commerce_order_view().
 */
function commerce_billy_pdf_commerce_order_view($order, $view_mode) {

  // Add content variables for the PDF generation.
  $settings = variable_get('commerce_billy_pdf_text_settings', array());
  $custom_date_format = !empty($settings['invoice_date_format']) ? $settings['invoice_date_format'] : 'Y-m-d';
  if ($view_mode == "pdf" || $view_mode == 'canceled') {
    $order->content['invoice_footer'] = array(
      '#markup' => isset($settings['invoice_footer']) ? $settings['invoice_footer'] : '',
    );
    $order->content['invoice_header'] = array(
      '#markup' => isset($settings['invoice_header']) ? $settings['invoice_header'] : '',
    );
    $order->content['invoice_text'] = array(
      '#markup' => isset($settings['invoice_text']) ? $settings['invoice_text'] : '',
    );
    $date_field_name = $view_mode == 'pdf' ? 'field_commerce_billy_i_date' : 'field_commerce_billy_cancel_date';
    $date_formatted = format_date($order->{$date_field_name}[LANGUAGE_NONE][0]['value'], 'custom', $custom_date_format);
    if (!empty($settings['invoice_location'])) {
      $invoice_header_date_text = t('@location, @date', array(
        '@location' => $settings['invoice_location'],
        '@date' => $date_formatted,
      ));
    }
    else {
      $invoice_header_date_text = $date_formatted;
    }
    $order->content['invoice_header_date'] = array(
      '#markup' => $invoice_header_date_text,
    );
    $order->content['order_number'] = array(
      '#markup' => t('Invoice No.: @id', array(
        '@id' => $order->order_number,
      )),
    );
    $order->content['order_id'] = array(
      '#markup' => t('Order No.: @id', array(
        '@id' => $order->order_id,
      )),
    );
    $logo_path = variable_get('commerce_billy_pdf_logo', 'misc/druplicon.png');
    if (variable_get('commerce_billy_pdf_converter', 'dompdf') == 'wkhtmltopdf') {
      $logo_path = DRUPAL_ROOT . '/' . $logo_path;
    }
    $order->content['invoice_logo'] = array(
      '#value' => $logo_path,
    );
  }
}

/**
 * Transforms HTML to PDF and outputs it to the browser.
 *
 * @param $html
 *   The HTML to render as PDF
 *
 * @param $filename
 *   The filename to be used for the PDF.
 *
 * @param $as_file
 *   TRUE if a filecontent should be returned (e.g. for mails), else as stream
 *   for downloads.
 */
function commerce_billy_pdf_output($html, $filename, $as_file = FALSE) {
  $pdf_converter = variable_get('commerce_billy_pdf_converter', 'dompdf');
  if ($pdf_converter == 'dompdf') {
    return commerce_billy_pdf_dompdf($html, $filename, $as_file);
  }
  elseif ($pdf_converter == 'wkhtmltopdf') {
    return commerce_billy_pdf_wkhtmltopdf($html, $filename, $as_file);
  }
}

/**
 * DOMPDF converter
 */
function commerce_billy_pdf_dompdf($html, $filename, $as_file = FALSE) {
  $dompdf = commerce_billy_pdf_prepare_dompdf($html, $filename);
  if ($dompdf) {
    try {
      if ($as_file) {
        $filecontent = $dompdf
          ->output();
        return $filecontent;
      }
      else {
        return $dompdf
          ->stream($filename, array(
          'Attachment' => TRUE,
        ));
      }
    } catch (\Dompdf\Exception $e) {
      drupal_set_message(t('Error generating PDF invoice. Please contact the website administrator.'), 'error');
      watchdog('commerce_billy_pdf', 'DOMPDF exception while generating pdf invoice: %message', array(
        '%message' => $e
          ->getMessage(),
      ), WATCHDOG_ERROR);
      return '';
    }
  }
}

/**
 * WKHTMLTOPDF converter
 */
function commerce_billy_pdf_wkhtmltopdf($html, $filename, $as_file = FALSE) {
  $path = libraries_get_path('phpwkhtmltopdf');
  if (!empty($path)) {
    $wkhtmltopdf_path = variable_get('wkhtmltopdf_path', '/usr/bin/wkhtmltopdf');
    require_once DRUPAL_ROOT . '/' . $path . '/WkHtmlToPdf.php';
    $filename .= '.pdf';
    $tmp = file_directory_temp();
    $pdf = new WkHtmlToPdf(array(
      'binPath' => $wkhtmltopdf_path,
      'tmp' => $tmp,
      'no-outline',
      'margin-top' => 0,
      'margin-right' => 0,
      'margin-bottom' => 0,
      'margin-left' => 0,
      // Default page options
      'disable-smart-shrinking',
    ));
    $pdf
      ->addPage($html);
    if ($as_file) {
      $pdf
        ->saveAs($tmp . '/' . $filename);
      $file_content = file_get_contents($tmp . '/' . $filename);
      return $file_content;
    }
    else {
      $output = $pdf
        ->send($filename);
      $error = $pdf
        ->getError();

      // Log any errors.
      if (!empty($error)) {
        watchdog('commerce_billy_pdf', 'WkHtmlToPdf exception while generating pdf resume: %message', array(
          '%message' => $error,
        ), WATCHDOG_ERROR);
        return t('Error generating PDF resume. Please contact the website administrator.');
      }
      else {
        return $output;
      }
    }
  }
  return t('WkHtmlToPdf not found');
}

/**
 * Helper function that instantiates new DOMPDF class and renders the HTML.
 *
 * The returned dompdf object can then be used to either directly stream the
 * output or to create a file.
 */
function commerce_billy_pdf_prepare_dompdf($html, $filename) {

  // dompdf needs write access to its font directory.
  // Copy "libraries/dompdf/lib/fonts" to your public files directory in order
  // for this to work. Older versions of Dompdf use these constants.
  if (!defined('DOMPDF_FONT_DIR')) {
    $dir = drupal_realpath('public://');
    define('DOMPDF_FONT_DIR', $dir . '/fonts/');
  }
  if (!defined('DOMPDF_TEMP_DIR')) {
    define('DOMPDF_TEMP_DIR', file_directory_temp());
  }

  // First check if dompdf is already auto-loaded with composer, if not try to
  // load it with Libraries API.
  if (class_exists('\\Dompdf\\Dompdf')) {
    $dompdf = new \Dompdf\Dompdf();
    $font_dir = drupal_realpath('public://fonts');

    // Dompdf writes out font files for caching, make sure they are in a
    // writeable directory in the files folder.
    $dompdf
      ->set_option('fontDir', $font_dir);
    $dompdf
      ->set_option('fontCache', $font_dir);
  }
  else {
    $path = libraries_get_path('dompdf');
    if (empty($path)) {
      return FALSE;
    }

    // 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();
    }
    else {
      require_once DRUPAL_ROOT . '/' . $path . '/dompdf_config.inc.php';
      spl_autoload_register('DOMPDF_autoload');
      $dompdf = new DOMPDF();
    }
  }
  $dompdf
    ->set_paper('a4', 'portrait');
  $html = htmlspecialchars_decode(htmlentities($html, ENT_NOQUOTES, 'UTF-8'), ENT_NOQUOTES);
  $dompdf
    ->load_html($html);
  $dompdf
    ->render();
  return $dompdf;
}

/**
 * Implements hook_action_info().
 */
function commerce_billy_pdf_action_info() {
  return array(
    'commerce_billy_pdf_print_invoices' => array(
      'label' => t('Print invoice'),
      'type' => 'commerce_order',
      'aggregate' => TRUE,
      'configurable' => FALSE,
      'triggers' => array(
        'any',
      ),
    ),
  );
}

/**
 * Callback to generate a PDF.
 */
function commerce_billy_pdf_print_invoices($orders) {
  $html = commerce_billy_pdf_html($orders);

  // The .pdf file extension is added by DOMPDF.
  $filename = 'invoices';
  commerce_billy_pdf_output($html, $filename);
  drupal_exit();
}

Functions

Namesort descending Description
commerce_billy_pdf Page callback for invoice PDF.
commerce_billy_pdf_action_info Implements hook_action_info().
commerce_billy_pdf_admin_paths_alter Implements hook_admin_paths_alter().
commerce_billy_pdf_commerce_order_view Implements hook_commerce_order_view().
commerce_billy_pdf_dompdf DOMPDF converter
commerce_billy_pdf_entity_info_alter Implements hook_entity_info_alter().
commerce_billy_pdf_html Helper function that returns the generated HTML for the invoice PDFs.
commerce_billy_pdf_html_page Page callback for the HTML version of the invoice.
commerce_billy_pdf_menu Implements hook_menu().
commerce_billy_pdf_output Transforms HTML to PDF and outputs it to the browser.
commerce_billy_pdf_prepare_dompdf Helper function that instantiates new DOMPDF class and renders the HTML.
commerce_billy_pdf_preprocess_entity Implements hook_preprocess_entity().
commerce_billy_pdf_print_invoices Callback to generate a PDF.
commerce_billy_pdf_theme Implements hook_theme().
commerce_billy_pdf_views_api Implements hook_views_api().
commerce_billy_pdf_view_access Access callback for the PDF invoice.
commerce_billy_pdf_wkhtmltopdf WKHTMLTOPDF converter