You are here

pdfpreview.module in PDFPreview 7.2

This file contains hooks for the pdfpreview module

@author Juanjo Garcia <juanjo.gcia@gmail.com> @author Florian Auer <floeschie@gmail.com>

File

pdfpreview.module
View source
<?php

/**
 * @file
 * This file contains hooks for the pdfpreview module
 *
 * @author Juanjo Garcia <juanjo.gcia@gmail.com>
 * @author Florian Auer <floeschie@gmail.com>
 *
 */
define('PDFPREVIEW_DEFAULT_QUALITY', 75);
define('PDFPREVIEW_DEFAULT_SIZE', '1024x1024');
define('PDFPREVIEW_DEFAULT_SHOW_DESCRIPTION', 0);
define('PDFPREVIEW_DEFAULT_TAG', 'span');
define('PDFPREVIEW_FILENAMES_MACHINE', 0);
define('PDFPREVIEW_FILENAMES_HUMAN', 1);
define('PDFPREVIEW_DEFAULT_FALLBACK_FORMATTER', 'file_default');

/**
 * Implements hook_field_formatter_info()
 *
 */
function pdfpreview_field_formatter_info() {
  $formatters = array(
    'pdfpreview' => array(
      'label' => t('PDF Preview'),
      'field types' => array(
        'file',
      ),
      'description' => 'Displays an snapshot of the first page of the PDF',
      'settings' => array(
        'image_style' => '',
        'image_link' => '',
        'show_description' => PDFPREVIEW_DEFAULT_SHOW_DESCRIPTION,
        'tag' => PDFPREVIEW_DEFAULT_TAG,
        'fallback_formatter' => PDFPREVIEW_DEFAULT_FALLBACK_FORMATTER,
      ),
    ),
  );
  if (module_exists('picture')) {
    $formatters['pdfpreview_picture'] = $formatters['pdfpreview'];
    $formatters['pdfpreview']['label'] = t('PDF Preview (Image)');
    $formatters['pdfpreview_picture']['label'] = t('PDF Preview (Picture)');
  }
  return $formatters;
}

/**
 * Implements hook_field_formatter_info_alter().
 */
function pdfpreview_field_formatter_info_alter(&$info) {
  $info['pdfpreview']['settings'] += $info['image']['settings'];
  if (isset($info['picture'])) {
    $info['pdfpreview_picture']['settings'] += $info['picture']['settings'];
  }
}

/**
 * Implements hook_field_formatter_settings_form().
 */
function pdfpreview_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) {
  $form = array();

  // Re-use image or picture formatter setting form.
  switch ($instance['display'][$view_mode]['type']) {
    case 'pdfpreview':
      $form += image_field_formatter_settings_form($field, $instance, $view_mode, $form, $form_state);
      break;
    case 'pdfpreview_picture':
      $instance['display'][$view_mode]['type'] = 'picture';
      $form += picture_field_formatter_settings_form($field, $instance, $view_mode, $form, $form_state);
      break;
  }

  // Add additional, PDF Preview only settings.
  $settings = $instance['display'][$view_mode]['settings'];
  $form['show_description'] = array(
    '#type' => 'checkbox',
    '#title' => t('Description'),
    '#description' => t('Show file description beside image'),
    '#options' => array(
      0 => t('No'),
      1 => t('Yes'),
    ),
    '#default_value' => $settings['show_description'],
  );
  $form['tag'] = array(
    '#type' => 'radios',
    '#title' => t('HTML tag'),
    '#description' => t('Select which kind of HTML element will be used to theme elements'),
    '#options' => array(
      'span' => 'span',
      'div' => 'div',
    ),
    '#default_value' => $settings['tag'],
  );
  $form['fallback_formatter'] = array(
    '#type' => 'checkbox',
    '#title' => t('Fallback to default file formatter'),
    '#description' => t('When enabled, non-PDF files will be formatted using a default file formatter.'),
    '#default_value' => isset($settings['fallback_formatter']) ? (bool) $settings['fallback_formatter'] : TRUE,
    '#return_value' => PDFPREVIEW_DEFAULT_FALLBACK_FORMATTER,
  );
  return $form;
}

/**
 * Implements hook_field_formatter_settings_summary().
 */
function pdfpreview_field_formatter_settings_summary($field, $instance, $view_mode) {

  // Again, act like the image or picture module.
  $summary = '';
  switch ($instance['display'][$view_mode]['type']) {
    case 'pdfpreview':
      $summary .= image_field_formatter_settings_summary($field, $instance, $view_mode);
      break;
    case 'pdfpreview_picture':
      $instance['display'][$view_mode]['type'] = 'picture';
      $summary .= picture_field_formatter_settings_summary($field, $instance, $view_mode);
      break;
  }
  $summary .= '<br />' . t('Using %tag as separator', array(
    '%tag' => $instance['display'][$view_mode]['settings']['tag'],
  ));
  $summary .= '<br />' . t('Descriptions are %desc', array(
    '%desc' => $instance['display'][$view_mode]['settings']['show_description'] ? t('visible') : t('hidden'),
  ));
  if (!isset($instance['display'][$view_mode]['settings']['fallback_formatter']) || $instance['display'][$view_mode]['settings']['fallback_formatter']) {
    $summary .= '<br />' . t('Using the default file formatter for non-PDF file.');
  }
  return $summary;
}

/**
 * Implements hook_field_formatter_view()
 *
 * @see _pdfpreview_create_preview()
 */
function pdfpreview_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
  $element = array();
  $settings = $display['settings'];
  $image_items = array();
  $non_pdfs = array();
  list($preview_width, $preview_height) = explode('x', variable_get('pdfpreview_previewsize', PDFPREVIEW_DEFAULT_SIZE));
  foreach ($items as $delta => $item) {
    if (isset($item['display']) && !$item['display']) {
      continue;
    }

    //don't show hidden files
    if ($item['filemime'] == 'application/pdf' && ($preview_uri = _pdfpreview_create_preview($item))) {
      $image_items[$delta] = array(
        'fid' => $item['fid'],
        'uri' => $preview_uri,
        'alt' => isset($item['description']) ? $item['description'] : '',
        'width' => $preview_width,
        'height' => $preview_height,
        'title' => isset($item['description']) ? $item['description'] : '',
        'attributes' => array(
          'class' => array(
            'pdfpreview-file',
          ),
        ),
      );
    }
    else {

      //For non pdf files, we defer processing.
      $non_pdfs[$delta] = $item;
    }
  }

  // Render PDF preview files using the image or picture formatter.
  switch ($display['type']) {
    case 'pdfpreview':
      $element += image_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $image_items, $display);
      break;
    case 'pdfpreview_picture':
      $picture_display = array_merge(array(), $display);
      $picture_display['type'] = 'picture';
      $element += picture_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $image_items, $picture_display);
      break;
  }
  foreach (element_children($element) as $delta) {
    $element[$delta]['#theme_wrappers'][] = 'pdfpreview_formatter';
    $element[$delta]['#settings'] = $settings;
    $element[$delta]['#fid'] = $items[$delta]['fid'];
  }
  if ((!isset($settings['fallback_formatter']) || $settings['fallback_formatter']) && !empty($non_pdfs)) {

    // Process non PDF files using default file formatter.
    module_load_include('inc', 'file', 'file.field');
    $display['type'] = 'file_default';
    $element = array_merge($element, file_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $non_pdfs, $display));
  }
  return $element;
}

/**
 * Implements hook_menu()
 */
function pdfpreview_menu() {
  $items = array(
    'admin/config/media/pdfpreview' => array(
      'title' => 'PDF Preview',
      'description' => 'Configure PDF Preview settings',
      'access arguments' => array(
        'administer site configuration',
      ),
      'page callback' => 'drupal_get_form',
      'page arguments' => array(
        'pdfpreview_admin',
      ),
      'type' => MENU_NORMAL_ITEM,
    ),
  );
  return $items;
}

/**
 * Implements hook_theme().
 *
 */
function pdfpreview_theme() {
  $theme = array(
    'pdfpreview_formatter' => array(
      'function' => 'theme_pdfpreview_formatter',
      'render element' => 'element',
    ),
  );
  return $theme;
}

/**
 * Implements hook_file_delete()
 */
function pdfpreview_file_delete($file) {
  file_unmanaged_delete(_pdfpreview_output_filename($file));
}

/**
 * Implements hook_file_update()
 */
function pdfpreview_file_update($file) {
  if ($file->uri != $file->original->uri || $file->filesize != $file->original->filesize) {
    file_unmanaged_delete(_pdfpreview_output_filename($file->original));
  }
}

// ----- custom module functions ----- //

/**
 * Creates the module settings render array
 *
 * If you want to extend the module's settings form, append the elements you
 * need to the $form array. Don't forget to update the pdfpreview_uninstall()
 * routine in the pdfpreview.install file as well.
 *
 */
function pdfpreview_admin() {
  $form = array(
    'pdfpreview_pathtoimages' => array(
      '#type' => 'textfield',
      '#title' => t('Images folder'),
      '#description' => t('Path, inside files directory, where snapshots are stored. For example <em>pdfpreview</em>'),
      '#default_value' => variable_get('pdfpreview_pathtoimages', 'pdfpreview'),
    ),
    'pdfpreview_previewsize' => array(
      '#type' => 'textfield',
      '#title' => t('Preview size'),
      '#description' => t('Size of the preview in pixels. For example <em>100x100</em>. You must set this to a value big enought to apply your image styles.'),
      '#default_value' => variable_get('pdfpreview_previewsize', PDFPREVIEW_DEFAULT_SIZE),
    ),
    'pdfpreview_quality' => array(
      '#type' => 'textfield',
      '#size' => 3,
      '#maxlenght' => 3,
      '#title' => t('Image quality'),
      '#field_suffix' => '%',
      '#description' => t('Image extraction quality in percentage.'),
      '#default_value' => variable_get('pdfpreview_quality', PDFPREVIEW_DEFAULT_QUALITY),
    ),
    'pdfpreview_filenames' => array(
      '#type' => 'radios',
      '#options' => array(
        PDFPREVIEW_FILENAMES_MACHINE => t('Filename hash'),
        PDFPREVIEW_FILENAMES_HUMAN => t('From pdf filename'),
      ),
      '#default_value' => variable_get('pdfpreview_filenames', PDFPREVIEW_FILENAMES_MACHINE),
      '#title' => t('Generated filenames'),
      '#description' => t('This changes how filenames will be used on genereated previews. If you change this after some files were generated, you must delete them manually.'),
    ),
  );

  // Callback function which checks for existing preview directory on save
  $form['#submit'][] = '_pdfpreview_prepare_filesystem';
  return system_settings_form($form);
}

/**
 * Convert the first page of a PDF document
 *
 * This function converts the first page of a PDF document using ImageMagick's
 * convert executable and returns TRUE on success, FALSE else. You can provide
 * optional parameters for the convert executable by simply passing them with
 * in an array to the $args parameter. For details about convert arguments see
 * the <a href="http://www.imagemagick.org/Usage/basics/#cmdline">ImageMagick
 * documentation</a>.
 *
 * @param $source URI to the PDF file
 * @param $dest URI where the preview file will be saved
 * @param $args Optional arguments for the convert executable
 * @see _imagemagick_convert()
 * @see _pdfpreview_create_preview()
 */
function _pdfpreview_convert_first_page($source, $dest, $args = array()) {
  $args['background'] = '-background white';
  $args['flatten'] = '-flatten';
  $args['previewsize'] = '-resize ' . escapeshellarg(variable_get('pdfpreview_previewsize', PDFPREVIEW_DEFAULT_SIZE));
  $args['pdfpreview'] = TRUE;
  $output_directory = dirname($dest);
  if (file_prepare_directory($output_directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS)) {
    return _imagemagick_convert($source, $dest, $args);
  }
  else {
    return FALSE;
  }
}

/**
 * Implements hook_imagemagick_arguments_alter().
 */
function pdfpreview_imagemagick_arguments_alter(&$args, &$context) {
  if (!empty($args['pdfpreview'])) {
    $context['source'] .= '[0]';
    $args['quality'] = '-quality ' . escapeshellarg(variable_get('pdfpreview_quality', PDFPREVIEW_DEFAULT_QUALITY));
    unset($args['pdfpreview']);
  }
}

/**
 * Creates the PDF preview file and returns its URI.
 *
 * @param object|array $file
 *   A Drupal file structure (array or object).
 * @return string
 *   The URI of the newly created preview image, or NULL if the preview image can not be created.
 *
 * @see _pdfpreview_convert_first_page()
 * @see pdfpreview_field_formatter_view()
 */
function _pdfpreview_create_preview($file) {
  if (!is_array($file)) {
    $file = (array) $file;
  }
  $output_filename = _pdfpreview_output_filename($file);

  // Check if a preview already exists.
  if (file_exists($output_filename)) {

    // Check if the existing preview is older than the file itself.
    if (filemtime($file['uri']) <= filemtime($output_filename)) {

      // The existing preview can be used, nothing to do.
      return $output_filename;
    }
    else {

      // Delete the existing but out-of-date preview.
      file_unmanaged_delete($output_filename);

      // Clears cached versions of a the existing preview file in all image styles.
      image_path_flush($output_filename);
    }
  }
  if (_pdfpreview_convert_first_page($file['uri'], $output_filename)) {
    return $output_filename;
  }
  else {
    return NULL;
  }
}

/**
 * Generates the filename for the preview
 * @paream File $file
 * @return Filename for the generated preview
 */
function _pdfpreview_output_filename($file) {
  if (!is_object($file)) {
    $file = (object) $file;
  }
  $output_dir = file_default_scheme() . '://' . variable_get('pdfpreview_pathtoimages', 'pdfpreview');
  if (variable_get('pdfpreview_filenames', PDFPREVIEW_FILENAMES_MACHINE) == PDFPREVIEW_FILENAMES_HUMAN) {
    $filename = basename($file->filename, '.pdf');
    if (module_exists('transliteration')) {
      $filename = transliteration_clean_filename($filename);
    }
    return $output_dir . '/' . $file->fid . '-' . $filename . '.jpg';
  }
  else {
    return $output_dir . '/' . md5('pdfpreview' . $file->fid) . '.jpg';
  }
}

/**
 * Prepare the file system for PDF preview image creation
 *
 * This function is called after the module's setting form is submitted. It
 * checks whether the target directory for preview images exists. If this is not
 * the case, the directory will be created.
 *
 * @see _pdfpreview_admin_settings()
 * @see system_settings_form_submit()
 */
function _pdfpreview_prepare_filesystem($form, &$form_state) {
  $output_uri = file_default_scheme() . '://' . $form['pdfpreview_pathtoimages']['#value'];
  if (!file_prepare_directory($output_uri, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS)) {
    drupal_set_message(t('Error pepraring directory %dir', array(
      '%dir' => $output_uri,
    )), 'error');
    watchdog('pdfpreview', 'Error creating directory %dir', array(
      '%dir' => $output_uri,
    ), WATCHDOG_ERROR);
  }
}

/**
 * Default theme function to wrap a rendered PDF preview image.
 *
 * This function is used to wrap the rendered PDF preview image with a dedicated
 * container element. If enabled, the description of the image is also appended.
 *
 * @param array $variables
 *   A associative array containing:
 *   - element: A rendered PDF file preview (using one of the image or picture
 *     file formatter).
 *
 * @return string
 *   A wrapped rendered PDF preview image.
 */
function theme_pdfpreview_formatter($variables) {
  $element = $variables['element'];
  $item = $element['#item'];
  $wrapper_tag = $element['#settings']['tag'];
  $description = $element['#settings']['show_description'] && isset($item['description']) ? '<' . $wrapper_tag . ' class="pdfpreview-description">' . $item['description'] . '</' . $wrapper_tag . '>' : '';
  return sprintf('<div class="pdfpreview" id="pdfpreview-%s">' . ' <%s class="pdfpreview-image-wrapper">%s</%s>' . ' %s' . '</div>', $item['fid'], $wrapper_tag, $element['#children'], $wrapper_tag, $description);
}