You are here

attachment_links.module in Attachment Links 6

Same filename and directory in other branches
  1. 7 attachment_links.module

The Attachment Links module provides permanent links to files attached to a node. A single, easy-to-remember URL can be used to retrieve the preferred (canonical) or newest version of a file regardless of how many versions of that file have been attached to a node.

Typically, users will want to create a "File" content type and enable the Attachment Links module for that content type. Each "File" node should refer to a single file: "Handbook," "Company logo," "Team roster," etc. Though each node could contain multiple versions of the file, Attachment Link provides permanent link to access the preferred or newest versions.

The "preferred" version is the first file attachment listed in a node's "File attachments" fieldset.

The module's output is fully themeable using the included template.

File

attachment_links.module
View source
<?php

/**
 * @file
 * The Attachment Links module provides permanent links to files attached to a
 * node. A single, easy-to-remember URL can be used to retrieve the preferred
 * (canonical) or newest version of a file regardless of how many versions of
 * that file have been attached to a node.
 *
 * Typically, users will want to create a "File" content type and enable the
 * Attachment Links module for that content type. Each "File" node should
 * refer to a single file: "Handbook," "Company logo," "Team roster," etc.
 * Though each node could contain multiple versions of the file, Attachment
 * Link provides permanent link to access the preferred or newest versions.
 *
 * The "preferred" version is the first file attachment listed in a node's
 * "File attachments" fieldset.
 *
 * The module's output is fully themeable using the included template.
 */

// -- --------------------------------------------------------------------------
// -- Core Drupal hooks.
// -- --------------------------------------------------------------------------

/**
 * Implementation of hook_enable(). Make this module heavy to encourage Drupal
 * to run its hooks after the Upload module's hooks.
 *
 * @return None.
 */
function attachment_links_enable() {
  $query = "UPDATE {system} SET `weight` = 99 WHERE `name` = 'attachment_links'";
  db_query($query);
}

/**
 * Implementation of hook_form_alter().
 *
 * @param $form The form array to alter.
 * @param $form_state The current form state.
 * @param $form_id The ID of the form being processed.
 *
 * @return None.
 */
function attachment_links_form_alter(&$form, $form_state, $form_id) {
  if ($form_id == 'node_type_form' && isset($form['identity']['type'])) {
    $desc = t('Attachments must be enabled in order for attachment links to appear.');
    $form['workflow']['attachment_links'] = array(
      '#type' => 'radios',
      '#title' => t('Attachment links'),
      '#description' => $desc,
      '#default_value' => variable_get('attachment_links_' . $form['#node_type']->type, 0),
      '#options' => array(
        t('Disabled'),
        t('Enabled'),
      ),
      '#weight' => 5,
    );
  }
  if (isset($form['type']) && isset($form['#node']) && isset($form['attachments']) && variable_get('attachment_links_' . $form['#node']->type, 0)) {
    $form['attachments']['attachment_links'] = array(
      '#type' => 'fieldset',
      '#collapsible' => TRUE,
      '#collapsed' => TRUE,
      '#title' => t('Attachment links'),
      '#description' => t('Attachment links provide permanent links for files attached to this node. The "preferred" version is the first file attachment listed above. Click and drag the handles to the left of each file to reorder them.'),
      '#weight' => 0,
    );

    // -- Display different help text for node creation and node editing forms.
    if ($form['nid']['#value']) {
      $form['attachments']['attachment_links']['help'] = array(
        '#value' => theme('attachment_links', $form['#node'], 'upload_form'),
      );
    }
    else {
      $form['attachments']['attachment_links']['help'] = array(
        '#value' => t('Attachment links will be created after you save the node.'),
        '#prefix' => '<div>',
        '#suffix' => '</div>',
      );
    }
  }
}

/**
 * Implementation of hook_menu().
 *
 * @return Array of menu items.
 */
function attachment_links_menu() {
  $items = array();
  $items['node/%node/attachment'] = array(
    'title' => 'Authoritative Attachment',
    'description' => 'The canonical or "lightest" attached file.',
    'type' => MENU_CALLBACK,
    'page callback' => 'attachment_links_retrieve',
    'page arguments' => array(
      1,
      'authoritative',
    ),
    'access callback' => 'user_access',
    'access arguments' => array(
      'view uploaded files',
    ),
  );
  $items['node/%node/attachment/newest'] = array(
    'title' => 'Latest Attachment',
    'description' => 'The most recently attached file.',
    'type' => MENU_CALLBACK,
    'page callback' => 'attachment_links_retrieve',
    'page arguments' => array(
      1,
      'newest',
    ),
    'access callback' => 'user_access',
    'access arguments' => array(
      'view uploaded files',
    ),
  );
  return $items;
}

/**
 * Implementation of hook_nodeapi().
 *
 * @param $node The node being operated on.
 * @param $op The operation being performed. This implementation responds to:
 *        "view"
 * @param $a3 For "view", passes in the $teaser parameter from node_view().
 * @param $a4 Ignored.
 *
 * @return None.
 */
function attachment_links_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
  if (variable_get("attachment_links_{$node->type}", 0) == 0 || empty($node->files)) {
    return;
  }
  if ($op == 'view' && $a3 == FALSE && user_access('view uploaded files')) {
    $node->content['attachment_links'] = array(
      '#value' => theme('attachment_links', $node, 'node'),
      '#weight' => 3,
    );
  }
}

/**
 * Implementation of hook_theme().
 *
 * @param $existing $existing An array of existing implementations that may be
 *        used for override purposes.
 * @param $type $type What 'type' is being processed.
 * @param $theme The actual name of theme that is being being checked.
 * @param $path The directory path of the theme or module.
 *
 * @return A keyed array of theme hooks.
 */
function attachment_links_theme($existing, $type, $theme, $path) {
  $hooks = array();
  $hooks['attachment_links'] = array(
    'template' => 'attachment-links',
    'arguments' => array(
      'node' => NULL,
      'display_type' => 'node',
    ),
  );
  return $hooks;
}

// -- --------------------------------------------------------------------------
// -- Module functions.
// -- --------------------------------------------------------------------------

/**
 * Fetch the reqested file for the given node.
 *
 * @param $node The node the file is attached to.
 *
 * @param $type The type of file requested: authoritative, newest, etc.
 *
 * @return Nothing if the download was successful. Otherwise, return a 404 error
 *         page if the node type doesn't have attachment links enabled or if the
 *         file could not be found.
 */
function attachment_links_retrieve($node, $type) {

  // Check if this node type has attachment links enabled. If not, return a 404.
  if (!variable_get('attachment_links_' . $node->type, 0)) {
    return drupal_not_found();
  }
  switch ($type) {
    case 'newest':
      $query = "SELECT `filepath` FROM {files} f\n               INNER JOIN {upload} u ON f.`fid` = u.`fid`\n               WHERE u.`vid` = %d\n               ORDER BY f.`timestamp` DESC";
      break;
    case 'authoritative':
    default:
      $query = "SELECT `filepath` FROM {files} f\n\t             INNER JOIN {upload} u ON f.`fid` = u.`fid`\n\t             WHERE u.`vid` = %d\n\t             ORDER BY u.`weight` ASC";
      break;
  }
  $path = db_result(db_query($query, array(
    $node->vid,
  )));
  _attachment_links_download($path);
}

/**
 * Download the given file over HTTP.
 *
 * @param $filepath The local path to the file.
 *
 * @return None if the call was successful. Returns a themed 404 error page if
 *         the file could not be found.
 */
function _attachment_links_download($filepath) {

  // -- Copied from file_download().
  if (file_exists(file_create_path($filepath))) {
    $headers = module_invoke_all('file_download', $filepath);
    if (in_array(-1, $headers)) {
      return drupal_access_denied();
    }
    if (count($headers)) {

      // -- PHP's basepath function screws up filenames with a space in them.
      $parts = explode(DIRECTORY_SEPARATOR, $filepath);
      $name = array_pop($parts);

      // -- Backslashed escaped characters inside the filename value seem to
      // -- confuse browsers, so double quotes are converted to single quotes.
      // -- The browser will then adjust the filename to match the host OS'
      // -- filename restrictions.
      $name = str_replace('"', "'", $name);
      $headers[] = 'Expires: 0';
      $headers[] = 'Cache-Control: private';
      $headers[] = 'Pragma: cache';
      $headers[] = 'Content-Type: application/force-download';
      $headers[] = 'Content-Disposition: attachment; filename="' . $name . '"';
      file_transfer($filepath, $headers);
    }
  }
  return drupal_not_found();
}
function attachment_links_build_links($node) {

  // -- This section seems clunky at first, but this design is to accommodate
  // -- future plans for the module.
  $links = array();
  $options = array(
    'absolute' => TRUE,
  );
  $preferred_path = 'node/' . $node->nid . '/attachment';
  $links['preferred'] = array(
    'url' => $preferred_path,
    'weight' => -5,
    'default render' => t('Preferred version: !link', array(
      '!link' => l(url($preferred_path, $options), $preferred_path),
    )),
  );
  $newest_path = 'node/' . $node->nid . '/attachment/newest';
  $links['newest'] = array(
    'url' => $newest_path,
    'weight' => 0,
    'default render' => t('Newest version: !link', array(
      '!link' => l(url($newest_path, $options), $newest_path),
    )),
  );
  return $links;
}

// -- --------------------------------------------------------------------------
// -- Module theming functions.
// -- --------------------------------------------------------------------------

/**
 * Preprocess the output for attachment links.
 *
 * @param $vars The theme variables array. After this function is called, these
 *        keys will be available in the array:
 *        - attachment_links: An array of keyed attachment link arrays. Each
 *          array in this array has the following keys:
 *              - 'url' - The URL for that item's attachment.
 *              - 'weight' - The relative weight for that attachment within
 *                 attachment_links.
 *              - 'default render' - A default rendering of the singular item.
 *        - default_render: A default unordered list rendering for the
 *          attachment links as generated by theme('item_list',...).
 *
 * @return unknown_type
 */
function template_preprocess_attachment_links(&$vars) {

  // -- Define a translatable title. This will not appear in the upload form.
  if ($vars['display_type'] != 'upload_form') {
    $vars['attachment_links_title'] = t('Attachment links');
  }

  // -- Build an array of links.
  $links = attachment_links_build_links($vars['node']);

  // -- Create a default item list rendering.
  $items = array();
  foreach ($links as $key => $link) {
    $items[] = $link['default render'];
  }
  $vars['attachment_links'] = theme('item_list', $items);
}

Functions

Namesort descending Description
attachment_links_build_links
attachment_links_enable Implementation of hook_enable(). Make this module heavy to encourage Drupal to run its hooks after the Upload module's hooks.
attachment_links_form_alter Implementation of hook_form_alter().
attachment_links_menu Implementation of hook_menu().
attachment_links_nodeapi Implementation of hook_nodeapi().
attachment_links_retrieve Fetch the reqested file for the given node.
attachment_links_theme Implementation of hook_theme().
template_preprocess_attachment_links Preprocess the output for attachment links.
_attachment_links_download Download the given file over HTTP.