<?php

/**
 * @file
 * Displays Printer-friendly versions of Drupal pages.
 *
 * @ingroup print
 */

define('PRINT_PDF_PDF_TOOL_DEFAULT', 0);
define('PRINT_PDF_CONTENT_DISPOSITION_DEFAULT', 2);
define('PRINT_PDF_PAPER_SIZE_DEFAULT', 'A4');
define('PRINT_PDF_PAGE_ORIENTATION_DEFAULT', 'portrait');
define('PRINT_PDF_IMAGES_VIA_FILE_DEFAULT', 0);
define('PRINT_PDF_AUTOCONFIG_DEFAULT', 1);
define('PRINT_PDF_FILENAME_DEFAULT', '[site:name] - [node:title] - [node:changed:custom:Y-m-d]');
define('PRINT_PDF_CACHE_ENABLED_DEFAULT', 0);
define('PRINT_PDF_CACHE_LIFETIME_DEFAULT', 86400); // 1 day

/**
 * Implements hook_print_link().
 */
function print_pdf_print_link() {
  return array(
    'format' => 'pdf',
    'text' => t('PDF version'),
    'description' => t('Display a PDF version of this page.'),
    'path' => 'printpdf',
    'class' => 'print-pdf',
    'icon' => 'pdf_icon.png',
    'module' => 'print_pdf',
  );
}

/**
 * Implements hook_print_new_window_alter().
 */
function print_pdf_print_new_window_alter(&$new_window, $format) {
  $new_window = (variable_get('print_pdf_content_disposition', PRINT_PDF_CONTENT_DISPOSITION_DEFAULT) == 1);
}

/**
 * Implements hook_permission().
 */
function print_pdf_permission() {
  return array(
    'access PDF version' => array(
      'title' => t('Access the PDF version'),
      'description' => t('View the PDF versions and the links to them in the original pages.'),
    ),
  );
}

/**
 * Implements hook_init().
 */
function print_pdf_init() {
  if (variable_get('print_pdf_autoconfig', PRINT_PDF_AUTOCONFIG_DEFAULT)) {
    $print_pdf_pdf_tool = variable_get('print_pdf_pdf_tool', PRINT_PDF_PDF_TOOL_DEFAULT);
    $tool = explode('|', $print_pdf_pdf_tool);
    $function = $tool[0] . '_pdf_tool_info';
    if (function_exists($function)) {
      $info = $function();
    }

    if (isset($info['public_dirs'])) {
      foreach ($info['public_dirs'] as $dir) {
        $directory = 'public://print_pdf/' . $tool[0] . '/' . $dir;
        $wrapper = file_stream_wrapper_get_instance_by_uri($directory);
        $real_directory_path = $wrapper->getDirectoryPath() . "/" . file_uri_target($directory);
        $result = file_prepare_directory($real_directory_path, FILE_CREATE_DIRECTORY);
        if (!$result) {
          watchdog('print_pdf', 'Failed to create directory "%dir" for %tool.', array('%dir' => $directory, '%tool' => $tool[0]), WATCHDOG_CRITICAL);
        }
      }
    }
  }
  if (variable_get('print_pdf_cache_enabled', PRINT_PDF_CACHE_ENABLED_DEFAULT)) {
    $directory = print_pdf_cache_dir();
    $wrapper = file_stream_wrapper_get_instance_by_uri($directory);
    $real_directory_path = $wrapper->getDirectoryPath() . "/" . file_uri_target($directory);
    $result = file_prepare_directory($real_directory_path, FILE_MODIFY_PERMISSIONS | FILE_CREATE_DIRECTORY);
    if (!$result) {
      watchdog('print_pdf', 'Failed to create directory "%dir" for print_pdf cache.', array('%dir' => $directory), WATCHDOG_CRITICAL);
    }
  }
}

/**
 * Implements hook_flush_caches().
 */
function print_pdf_flush_caches() {
  print_pdf_cache_delete();

  return array();
}

/**
 * Implements hook_menu().
 */
function print_pdf_menu() {
  $link = print_pdf_print_link();
  $items = array();

  $items[$link['path']] = array(
    'title' => 'Printer-friendly PDF',
    'page callback' => 'print_pdf_controller',
    'access arguments' => array('access PDF version'),
    'type' => MENU_CALLBACK,
    'file' => 'print_pdf.pages.inc',
  );
  $items[$link['path'] . '/' . $link['path']] = array(
    'access callback' => FALSE,
  );
  $items['admin/config/user-interface/print/pdf'] = array(
    'title' => 'PDF',
    'description' => 'Configure the settings of the PDF generation functionality.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('print_pdf_settings'),
    'access arguments'  => array('administer print'),
    'weight' => 3,
    'type' => MENU_LOCAL_TASK,
    'file' => 'print_pdf.admin.inc',
  );
  $items['admin/config/user-interface/print/pdf/options'] = array(
    'title' => 'Options',
    'weight' => -1,
    'type' => MENU_DEFAULT_LOCAL_TASK,
  );

  return $items;
}

/**
 * Implements hook_variable_info().
 */
function print_pdf_variable_info($options) {
  $link = print_pdf_print_link();

  $variable['print_pdf_link_text'] = array(
    'title' => t('PDF version'),
    'description' => t('Text used in the link to the PDF version.'),
    'type' => 'string',
    'default' => t($link['text']),
  );

  return $variable;
}

/**
 * Implements hook_block_info().
 */
function print_pdf_block_info() {
  $block['print_pdf-top']['info'] = t('Most PDFd');
  $block['print_pdf-top']['cache'] = DRUPAL_CACHE_GLOBAL;
  return $block;
}

/**
 * Implements hook_block_view().
 */
function print_pdf_block_view($delta = 0) {
  switch ($delta) {
    case 'print_pdf-top':
      $block['subject'] = t('Most PDFd');
      $result = db_query_range("SELECT path FROM {print_pdf_page_counter} LEFT JOIN {node} n ON path = CONCAT('node/', n.nid) WHERE status <> 0 OR status IS NULL ORDER BY totalcount DESC", 0, 3)
                  ->fetchAll();
      if (count($result)) {
        $items = array();
        foreach ($result as $obj) {
          $items[] = l(_print_get_title($obj->path), $obj->path);
        }
        $block['content'] = theme('item_list', array('items' => $items, 'type' => 'ul'));
      }
      break;
  }
  return $block;
}

/**
 * Implements hook_requirements().
 */
function print_pdf_requirements($phase) {
  $requirements = array();
  $t = get_t();
  switch ($phase) {
    // At runtime, make sure that a PDF generation tool is selected
    case 'runtime':
      $print_pdf_pdf_tool = variable_get('print_pdf_pdf_tool', PRINT_PDF_PDF_TOOL_DEFAULT);
      if (empty($print_pdf_pdf_tool)) {
        $requirements['print_pdf_tool'] = array(
          'title' => $t('Printer, email and PDF versions - PDF generation library'),
          'value' => $t('No PDF tool selected'),
          'description' => $t('Please configure it in the !url.', array('!url' => l($t('PDF settings page'), 'admin/config/user-interface/print/pdf'))),
          'severity' => REQUIREMENT_ERROR,
        );
      }
      else {
        // Tool is defined, get some data from it's handler module
        $tool = explode('|', $print_pdf_pdf_tool);
        $function = $tool[0] . '_pdf_tool_info';
        if (function_exists($function)) {
          $info = $function();
        }

        // Is the file there?
        if (!is_file($tool[1]) || !is_readable($tool[1])) {
          $requirements['print_pdf_tool'] = array(
            'title' => $t('Printer, email and PDF versions - PDF generation library'),
            'value' => $t('File not found'),
            'description' => $t('The currently selected PDF generation library (%file) is no longer accessible.', array('%file' => $tool[1])),
            'severity' => REQUIREMENT_ERROR,
          );
        }
        else {
          // Get the version number
          $function = $tool[0] . '_pdf_tool_version';
          if (function_exists($function)) {
            $version = $function($tool[1]);

            if (isset($info['min_version']) && version_compare($version, $info['min_version'], '<')) {
              $requirements['print_pdf_tool_version'] = array(
                'title' => $t('Printer, email and PDF versions - PDF generation library'),
                'value' => $t('Unsupported %lib version', array('%lib' => $info['name'])),
                'description' => $t('The currently selected version of %lib (@version) is not supported. Please update to a !url.',
                                    array('%lib' => $info['name'], '@version' => $version, '!url' => l($t('newer version'), $info['url']))),
                'severity' => REQUIREMENT_ERROR,
              );
            }
            else {
              $requirements['print_pdf_tool_version'] = array(
                'title' => $t('Printer, email and PDF versions - PDF generation library'),
                'value' => $info['name'] . ' ' . $version,
              );
            }
          }
        }

        // If auto-config is on, check for write access to the appropriate dirs
        if (variable_get('print_pdf_autoconfig', PRINT_PDF_AUTOCONFIG_DEFAULT)) {
          $directories = array();
          if (isset($info['public_dirs'])) {
            foreach ($info['public_dirs'] as $dir) {
              $directories[] = 'public://print_pdf/' . $tool[0] . '/' . $dir;
            }
          }
          if (isset($info['tool_dirs'])) {
            foreach ($info['tool_dirs'] as $dir) {
              $directories[] = dirname($tool[1]) . '/' . $dir;
            }
          }

          foreach ($directories as $dir) {
            if (!is_dir($dir) || !is_writable($dir)) {
              $requirements['print_pdf_tool_' . $dir] = array(
                'title' => $t('%lib directory', array('%lib' => $info['name'])),
                'value' => $t('Non-writable permissions'),
                'description' => $t('You must change the %libdir permissions to be writable, as %lib requires write-access to that directory.', array('%lib' => $info['name'], '%libdir' => $dir)),
                'severity' => REQUIREMENT_ERROR,
              );
            }
          }
        }
      }
      break;
  }
  return $requirements;
}

/**
 * Implements hook_node_load().
 */
function print_pdf_node_load($nodes, $types) {
  $ids = array();
  foreach ($nodes as $node) {
    $ids[] = $node->nid;
  }

  $link = print_pdf_print_link();

  $size = 'print_' . $link['format'] . '_size';
  $orientation = 'print_' . $link['format'] . '_orientation';

  $result = db_query('SELECT nid, size, orientation FROM {print_pdf_node_conf} WHERE nid IN (:nids)', array(':nids' => $ids))->fetchAllAssoc('nid');

  foreach ($nodes as $node) {
    $node->{$size} = (isset($result[$node->nid]) && !empty($result[$node->nid]->size)) ? $result[$node->nid]->size : variable_get($size . '_' . $node->type);
    $node->{$orientation} = (isset($result[$node->nid]) && !empty($result[$node->nid]->orientation)) ? $result[$node->nid]->orientation : variable_get($orientation . '_' . $node->type);
  }
}

/**
 * Implements hook_node_insert().
 */
function print_pdf_node_insert($node) {
  return print_pdf_node_update($node);
}

/**
 * Implements hook_node_update().
 */
function print_pdf_node_update($node) {
  if (user_access('administer print') || user_access('node-specific print configuration')) {
    $link = print_pdf_print_link();

    $size = 'print_' . $link['format'] . '_size';
    $orientation = 'print_' . $link['format'] . '_orientation';

    if (!isset($node->{$size})) $node->{$size} = variable_get($size . '_' . $node->type);
    if (!isset($node->{$orientation})) $node->{$orientation} = variable_get($orientation . '_' . $node->type);

    db_merge('print_pdf_node_conf')
      ->key(array('nid' => $node->nid))
      ->fields(array(
        'size' => $node->{$size},
        'orientation' => $node->{$orientation},
      ))
      ->execute();
  }

  print_pdf_cache_delete($node->nid);
}

/**
 * Implements hook_node_delete().
 */
function print_pdf_node_delete($node) {
  db_delete('print_pdf_page_counter')
    ->condition('path', 'node/' . $node->nid)
    ->execute();

  print_pdf_cache_delete($node->nid);
}

/**
 * Implements hook_form_alter().
 */
function print_pdf_form_alter(&$form, &$form_state, $form_id) {
  // Add the node-type settings option to activate the printer-friendly version link
  if ((user_access('administer print') || user_access('node-specific print configuration')) &&
      (($form_id == 'node_type_form') || !empty($form['#node_edit_form']))) {
    $link = print_pdf_print_link();

    $size = 'print_' . $link['format'] . '_size';
    $orientation = 'print_' . $link['format'] . '_orientation';

    $form['print']['print_' . $link['format']][$size] = array(
      '#type' => 'select',
      '#title' => t('Paper size'),
      '#options' => _print_pdf_paper_sizes(TRUE),
      '#description' => t('Choose the paper size of the generated PDF.'),
    );

    $form['print']['print_' . $link['format']][$orientation] = array(
      '#type' => 'select',
      '#title' => t('Page orientation'),
      '#options' => array('' => 'Unchanged', 'portrait' => t('Portrait'), 'landscape' => t('Landscape')),
      '#description' => t('Choose the page orientation of the generated PDF.'),
    );

    if ($form_id == 'node_type_form') {
      $form['print']['print_' . $link['format']][$size]['#default_value'] = variable_get($size . '_' . $form['#node_type']->type);
      $form['print']['print_' . $link['format']][$orientation]['#default_value'] = variable_get($orientation . '_' . $form['#node_type']->type);
    }
    else {
      $node = $form['#node'];
      $form['print']['print_' . $link['format']][$size]['#default_value'] = isset($node->{$size}) ? $node->{$size} : variable_get($size . '_' . $node->type);
      $form['print']['print_' . $link['format']][$orientation]['#default_value'] = isset($node->{$orientation}) ? $node->{$orientation} : variable_get($orientation . '_' . $node->type);
    }
  }
}

/**
 * Auxiliary function to display a formatted PDF version link
 *
 * Function made available so that developers may call this function from
 * their defined pages/blocks.
 *
 * @param string $path
 *   path to be used in the link. If not specified, the current URL is used.
 * @param object $node
 *   node object, to be used in checking node access. If the path argument is
 *   not provided, the path used will be node/nid.
 * @param string $location
 *   where in the page where the link is being inserted ('link', 'corner',
 *   'block', 'help').
 *
 * @return bool
 *   string with the HTML link to the printer-friendly page
 *
 * @ingroup print_api
 */
function print_pdf_insert_link($path = NULL, $node = NULL, $location = '') {
  if (function_exists('print_ui_insert_link')) {
    return print_ui_insert_link(print_pdf_print_link(), array('path' => $path, 'node' => $node, 'location' => $location));
  }
  else {
    return FALSE;
  }
}

/**
 * Check if the link to the PDF version is allowed depending on the settings
 *
 * @param array $args
 *   array containing the possible parameters:
 *    view_mode, node, type, path
 *
 * @return bool
 *   FALSE if not allowed, TRUE otherwise
 */
function print_pdf_link_allowed($args) {
  $print_pdf_pdf_tool = variable_get('print_pdf_pdf_tool', PRINT_PDF_PDF_TOOL_DEFAULT);

  return (user_access('access PDF version') && (!empty($print_pdf_pdf_tool)));
}

/**
 * Implements hook_cron().
 */
function print_pdf_cron() {
  print_pdf_cache_clean();
}

/**
 * Removes pdf files for nodes/paths if they are older than the lifetime.
 */
function print_pdf_cache_clean() {
  $lifetime = variable_get('print_pdf_cache_lifetime', PRINT_PDF_CACHE_LIFETIME_DEFAULT);

  if ($lifetime > 0) {
    $files = file_scan_directory(print_pdf_cache_dir(), '!\d+\.pdf$!');
    foreach ($files as $file) {
      // For all files in the cache directory, see when they were last accessed
      $result = db_query("SELECT timestamp FROM {print_pdf_page_counter} WHERE path = :path", array(':path' => 'node/' . $file->name))
                  ->fetchField();

      // Keep the file only if the last access was within the cache max life value
      if (($result === FALSE) || ($result + $lifetime < REQUEST_TIME)) {
        print_pdf_cache_delete($file->name);
      }
    }
  }
}

/**
 * Returns the cache directory.
 *
 * @return string
 *   The scheme://path of the cache directory
 */
function print_pdf_cache_dir() {
  $scheme = 'private';
  if (!file_stream_wrapper_valid_scheme($scheme)) {
    $scheme = 'temporary';
  }
  return $scheme . '://print_pdf/cache';
}

/**
 * Deletes one or more files from the PDF cache directory.
 *
 * @param int nid
 *   The node ID of the page for which the cached PDF should be deleted.
 *   If not provided, the entire cache directory will be deleted.
 */
function print_pdf_cache_delete($nid = NULL) {
  $directory = print_pdf_cache_dir();

  if ($nid) {
    $filename = $directory . '/' . $nid . '.pdf';
    if (is_file($filename)) {
      file_unmanaged_delete($filename);
    }
  }
  else {
    // If no nid is provided, flush the entire cache.
    if (is_dir($directory)) {
      file_unmanaged_delete_recursive($directory);
    }
  }
}

/**
 * Displays the PDF as inline or a downloadable file.
 *
 * @param string $pdf
 *   PDF content string
 * @param string $filename
 *   Filename of the generated PDF
 *
 * @return string
 *   The disposed PDF file
 */
function print_pdf_dispose_content($pdf, $filename) {
  if (headers_sent()) {
    exit('Unable to stream pdf: headers already sent');
  }
  header('Cache-Control: private');
  header('Content-Type: application/pdf');

  $content_disposition = variable_get('print_pdf_content_disposition', PRINT_PDF_CONTENT_DISPOSITION_DEFAULT);
  $attachment =  ($content_disposition == 2) ?  'attachment' :  'inline';

  header("Content-Disposition: $attachment; filename=\"$filename\"");

  echo $pdf;
  flush();

  return TRUE;
}

/**
 * Generate a PDF version of the provided HTML.
 *
 * @param string $html
 *   HTML content of the PDF
 * @param array $meta
 *   Meta information to be used in the PDF
 *   - url: original URL
 *   - name: author's name
 *   - title: Page title
 *   - node: node object
 * @param string $filename
 *   (optional) Filename of the generated PDF
 * @param string $paper_size
 *   (optional) Paper size of the generated PDF
 * @param string $page_orientation
 *   (optional) Page orientation of the generated PDF
 *
 * @return
 *   generated PDF page, or NULL in case of error
 *
 * @see print_pdf_controller()
 *
 * @ingroup print_api
 */
function print_pdf_generate_html($html, $meta, $filename = NULL, $paper_size = NULL, $page_orientation = NULL) {
  $pdf_tool = explode('|', variable_get('print_pdf_pdf_tool', PRINT_PDF_PDF_TOOL_DEFAULT));

  module_load_include('inc', $pdf_tool[0], $pdf_tool[0] . '.pages');

  $function = $pdf_tool[0] . '_print_pdf_generate';
  if (function_exists($function)) {
    $pdf = $function($html, $meta, $paper_size, $page_orientation);
  }
  if ($filename) {
    return print_pdf_dispose_content($pdf, $filename);
  }

  return $pdf;
}

/**
 * Implements hook_views_api().
 */
function print_pdf_views_api() {
  return array(
    'api' => 2.0,
    'path' => drupal_get_path('module', 'print_pdf'),
  );
}

/**
 * Lists all possible paper sizes
 *
 * @return
 *   array of strings with the available paper sizes
 */
function _print_pdf_paper_sizes($include_default = FALSE) {
  $ret = array();

  $ret = ($include_default) ? array('' => 'Unchanged') : array();

  $ret += array(
    '4A0' => '4A0', '2A0' => '2A0', 'A0' => 'A0',
    'A1' => 'A1', 'A2' => 'A2', 'A3' => 'A3', 'A4' => 'A4',
    'A5' => 'A5', 'A6' => 'A6', 'A7' => 'A7', 'A8' => 'A8',
    'A9' => 'A9', 'A10' => 'A10', 'B0' => 'B0', 'B1' => 'B1',
    'B2' => 'B2', 'B3' => 'B3', 'B4' => 'B4', 'B5' => 'B5',
    'B6' => 'B6', 'B7' => 'B7', 'B8' => 'B8', 'B9' => 'B9',
    'B10' => 'B10', 'C0' => 'C0', 'C1' => 'C1', 'C2' => 'C2',
    'C3' => 'C3', 'C4' => 'C4', 'C5' => 'C5', 'C6' => 'C6',
    'C7' => 'C7', 'C8' => 'C8', 'C9' => 'C9', 'C10' => 'C10',
    'RA0' => 'RA0', 'RA1' => 'RA1', 'RA2' => 'RA2',
    'RA3' => 'RA3', 'RA4' => 'RA4', 'SRA0' => 'SRA0',
    'SRA1' => 'SRA1', 'SRA2' => 'SRA2', 'SRA3' => 'SRA3',
    'SRA4' => 'SRA4', 'LETTER' => 'Letter', 'LEGAL' => 'Legal',
    'EXECUTIVE' => 'Executive', 'FOLIO' => 'Folio',
  );

  return $ret;
}
