You are here

webform_protected_downloads.module in Webform Protected Downloads 6

This file contains hook declarations and functions for the Webform Protected Downloads module.

See also

README.txt for more information

File

webform_protected_downloads.module
View source
<?php

/**
 * @file
 * This file contains hook declarations and functions for the Webform Protected
 * Downloads module.
 *
 * @see README.txt for more information
 */

/**
 * The session key.
 */
define('WEBFORM_PROTECTED_DOWNLOADS_SESSION_KEY', 'webform_protected_downloads_access');

/**
 * The access type "One-time access"
 */
define('WEBFORM_PROTECTED_DOWNLOADS_ACCESS_TYPE_SINGLE', 0);

/**
 * The access type "Expires"
 */
define('WEBFORM_PROTECTED_DOWNLOADS_ACCESS_TYPE_EXPIRES', 1);

/**
 * The default access type for new webforms
 */
define('WEBFORM_PROTECTED_DOWNLOADS_DEFAULT_ACCESS_TYPE', 0);

/**
 * The default expiration interval for the download itself in seconds.
 */
define('WEBFORM_PROTECTED_DOWNLOADS_DEFAULT_EXPIRATION_DOWNLOAD', 60 * 60 * 24 * 7);

/**
 * The default expiration interval for the file access that is managed by the
 * session information itself in seconds.
 */
define('WEBFORM_PROTECTED_DOWNLOADS_DEFAULT_EXPIRATION_SESSION', 60 * 60 * 24);

/**
 * Default setting for retroactive access, meaning whether or not a user can
 * access files that have been protected after the first form submission.
 */
define('WEBFORM_PROTECTED_DOWNLOADS_DEFAULT_RETROACTIVE', 1);

/**
 * Default setting for direct redirect to the downloads page after form
 * submission.
 */
define('WEBFORM_PROTECTED_DOWNLOADS_DEFAULT_REDIRECT', 0);

/**
 * Our identifier for watchdog messages
 */
define('WEBFORM_PROTECTED_DOWNLOADS_WATCHDOG_ID', 'Protected Downloads');

/**
 * Implementation of hook_menu().
 */
function webform_protected_downloads_menu() {
  $items = array();
  $items['node/%webform_menu/download'] = array(
    'title' => 'Download',
    'type' => MENU_CALLBACK,
    'page callback' => 'webform_protected_downloads_download_page',
    'page arguments' => array(
      1,
    ),
    'access callback' => 'webform_protected_downloads_access',
    'access arguments' => array(
      'view',
      1,
    ),
    'file' => 'webform_protected_downloads.page.inc',
  );
  $items['node/%webform_menu/download/%'] = array(
    'type' => MENU_CALLBACK,
    'page callback' => 'webform_protected_downloads_download_page',
    'page arguments' => array(
      1,
      3,
    ),
    'access callback' => 'webform_protected_downloads_access',
    'access arguments' => array(
      'view',
      1,
    ),
    'file' => 'webform_protected_downloads.page.inc',
  );
  $items['node/%webform_menu/protected-downloads'] = array(
    'title' => 'Protected Downloads',
    'type' => MENU_LOCAL_TASK,
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'webform_protected_downloads_configuration_form',
      1,
    ),
    'access callback' => 'webform_protected_downloads_access',
    'access arguments' => array(
      'update',
      1,
    ),
    'weight' => 3,
    'file' => 'webform_protected_downloads.form.inc',
  );
  return $items;
}

/**
 * Custom access callback
 *
 * @param object $node 
 * @return boolean
 */
function webform_protected_downloads_access($op, $node) {
  switch ($op) {
    case 'view':

      // first check if there are any files attached to this node
      if (!isset($node->files) || !count($node->files)) {
        return FALSE;
      }
      return node_access($op, $node);
      break;
    case 'update':
      return node_access($op, $node) && user_access('administer webform protected downloads');
      break;
  }
  return FALSE;
}

/**
 * Implementation of hook_perm().
 */
function webform_protected_downloads_perm() {
  return array(
    'administer webform protected downloads',
  );
}

/**
 * Implementation of hook_theme().
 */
function webform_protected_downloads_theme() {
  return array(
    'webform_protected_downloads_configuration_form' => array(
      'arguments' => array(
        'form' => NULL,
      ),
    ),
    'webform_protected_downloads_mail_token_file_list' => array(
      'arguments' => array(
        'files' => NULL,
        'checksum' => NULL,
      ),
    ),
  );
}

/**
 * Implementation of hook_file_download().
 *
 * @param string $filepath 
 */
function webform_protected_downloads_file_download($filepath) {
  global $conf;
  if (strpos($filepath, '/') !== FALSE) {
    $filepath = array_pop(explode('/', $filepath));
  }

  // check if this is a protected file
  $result = db_query("SELECT    p.nid, f.fid, f.filemime, f.filesize\n                      FROM      {files} f\n                      LEFT JOIN {wpd_protected_files} p USING(fid)\n                      WHERE     f.filename = '%s'", $filepath);
  $protect = db_fetch_object($result);

  // check if the file is protected
  if (webform_protected_downloads_file_is_protected($protect->nid, $protect->fid)) {

    // don't cache access allowed or denied
    $conf['cache'] = CACHE_DISABLED;
    $admin_access = user_access('administer webform protected downloads');
    if (webform_protected_downloads_file_user_has_access($protect->nid, $protect->fid) || $admin_access) {

      // access granted
      return array(
        'Content-type:' . $protect->filemime,
        'Content-Length: ' . $protect->filesize,
      );
    }

    // access denied
    return -1;
  }

  // file is not protected so we have nothing to say
  return NULL;
}

/**
 * Implementation of hook_menu_alter().
 *
 * @param array $items 
 * @return void
 */
function webform_protected_downloads_menu_alter(&$items) {
  $item =& $items['node/%webform_menu/webform/components/%webform_menu_component/delete'];
  $item['access callback'] = 'webform_protected_downloads_component_delete_access';
  $item['access arguments'] = array(
    'update',
    1,
    4,
  );
}

/**
 * Custom access callback that checks wheather a webform component can be
 * deleted
 *
 * @param string $op 
 * @param object $node 
 * @param array $component 
 * @return boolean
 */
function webform_protected_downloads_component_delete_access($op, $node, $component) {

  // check whether the current component exists already, otherwhise the coming
  // checks would be unnecessary
  if (isset($component['cid'])) {
    $component_in_use = webform_protected_downloads_get_configuration($node->nid, 'mail_field_cid') == $component['cid'];
    $node_protected = webform_protected_downloads_node_has_protected_files($node->nid);
    if ($node_protected && $component_in_use) {
      if (arg(5) == 'delete') {
        drupal_set_message(t('Access to this action has been disabled by the Webform Protected Downloads module. The component that you want to delete is in use. Please got to the <a href="@protected_downloads_page">Protected Downloads configuration</a> of this webform and change the Mail confirmation field or unprotect all files. <br /><a href="@last_page">Go back to the form</a>', array(
          '@protected_downloads_page' => url('node/' . $node->nid . '/protected-downloads'),
          '@last_page' => url($_GET['destination']),
        )));
      }
      else {

        // we are most probably on the components edit form
        drupal_set_message(t('Deletion of this component has been disabled by the Webform Protected Downloads module, because the component is currently in use. This can be changed on the <a href="@protected_downloads_page">Protected Downloads configuration</a> page.', array(
          '@protected_downloads_page' => url('node/' . $node->nid . '/protected-downloads'),
        )));
      }
      return FALSE;
    }
  }
  return node_access($op, $node);
}

/**
 * Implementation of hook_cron().
 *
 * @return void
 */
function webform_protected_downloads_cron() {

  // delete expired hash codes
  db_query("DELETE    h\n            FROM      {wpd_access_hashes} h\n            LEFT JOIN {webform_submissions} s USING(sid)\n            LEFT JOIN {wpd_node_configuration} n USING(nid)\n            WHERE     (expires < %d AND expires != 0)\n            OR        (n.access_type = %d AND h.used != 0)", time(), WEBFORM_PROTECTED_DOWNLOADS_ACCESS_TYPE_SINGLE);
}

/**
 * Implementation of hook_nodeapi().
 *
 * @param object $node 
 * @param string $op 
 * @param string $arg3 
 * @param string $arg4 
 * @return void
 */
function webform_protected_downloads_nodeapi(&$node, $op, $arg3, $arg4) {
  if (!webform_protected_downloads_node_is_webform($node)) {

    // make sure any non webform is free of protected files governed by this
    // module this is an edge case scenario described in
    // http://drupal.org/node/1558260
    if ($op == 'presave') {
      webform_protected_downloads_remove_deleted_files($node->nid);
    }
    return;
  }
  switch ($op) {
    case 'load':
      if (!isset($node->wpd_valid)) {

        // check if the node has protected files
        if (webform_protected_downloads_node_has_protected_files($node->nid)) {

          // check if the registered component still exists, needed, because we
          // can't prevent webform from deleting a component that we might use
          // as a mail field for the protected downloads
          $result = db_result(db_query("SELECT COUNT(*) FROM {wpd_node_configuration} n LEFT JOIN {webform_component} c ON c.cid = n.mail_field_cid AND c.nid = n.nid WHERE n.nid = %d AND c.cid IS NOT NULL", $node->nid));
          $meta['wpd_valid'] = $result > 0;
        }
        else {
          $meta['wpd_valid'] = TRUE;
        }
        return $meta;
      }
    case 'view':
      if (!$node->wpd_valid && user_access('administer webform protected downloads')) {
        _webform_protected_downloads_set_warning();
      }
      break;
    case 'presave':

      // make sure that protected files stay protected
      if (webform_protected_downloads_node_has_protected_files($node->nid)) {
        foreach ($node->files as $fid => $file) {
          $_file = (object) $file;
          if (webform_protected_downloads_file_is_protected($node->nid, $fid)) {
            $_file->list = FALSE;
            $_file->private = TRUE;
          }
          $node->files[$fid] = is_array($file) ? (array) $_file : $_file;
        }
      }
      break;
    case 'delete':
      db_query("DELETE FROM {wpd_node_configuration} WHERE nid = %d", $node->nid);
      db_query("DELETE FROM {wpd_protected_files} WHERE nid = %d", $node->nid);
      break;
  }
}

/**
 * Check whether the given node is used as a webform
 *
 * @param object $node 
 * @return boolean
 */
function webform_protected_downloads_node_is_webform($node) {
  $webform_types = webform_variable_get('webform_node_types');
  return is_array($webform_types) && in_array($node->type, $webform_types);
}

/**
 * Implementation of hook_help().
 */
function webform_protected_downloads_help($page, $arg) {
  $output = '';
  switch ($page) {
    case 'admin/help#webform_protected_downloads':
      $output = t("<h3>Purpose of this module</h3>\n      This module is based on the webform module and lets you configure protected downloads, meaning files of any type that can be accessed by a user only after the user has submitted a webform with a valid e-mail address.\n\n      <h3>How it works</h3>\n      This module operates on nodes that are used as webforms. A webform node that has been configured for protected downloads acts just like a normal webform survey, expect that the user must give a valid e-mail address. After the user submits the webform an e-mail will be sent to the given e-mail address. This e-mail contains a link to a download page where all protected files are listed and can be downloaded from.\n      In order to use a webform node for protected downloads this node must meet the following criteria:\n      <ul>\n      <li>at least one private file must have been attached to the node</li>\n      <li>the webform must have at least one mandatory e-mail component</li>\n      </ul>\n\n      <h3>Step-by-step guide to use this module</h3>\n      <ol>\n      <li>Enable core file uploads for the content type that you use as a webform</li>\n      <li>Check that the <a href='@private_upload_url'>private upload module</a> is configured correctly</li>\n      <li><a href='@webform_help_url'>Create a webform</a></li>\n      <li>Add a a mandatory mail component to the webform</li>\n      <li>Upload at least one file to the cretaed node and mark it as private</li>\n      <li>Go to the <em>Protected downloads</em> tab of the node and configure details like\n      <ul>\n      <li>how long should the download stay available for a given user</li>\n      <li>the mail text of the outgoing mail, including a placeholder for the download link</li>\n      <li>custom text that appears on the downloads page above the file listing</li>\n      <li>custom text for an access denied page</li>\n      </ul>\n      </li>\n      </ol>\n\n      <h3>How to send HTML formatted mails</h3>\n      The e-mails containing the access information for the protected files are send using default drupal mail functionality. That's why you should be able to use any mailengine that offers html support in order to send formatted mails. The only mailengine that has actively been tested in conjunction with this module is the <a href='@mimemail_project_url'>mimemail module</a>.\n      \n      <h3>Useful references</h3>\n      <ul>\n      <li><a href='@reference_files_url'>Working with files in Drupal 6</li>\n      </ul>", array(
        '@private_upload_url' => url('admin/settings/private_upload'),
        '@webform_help_url' => url('admin/help/webform'),
        '@mimemail_project_url' => url('http://drupal.org/project/mimemail'),
        '@reference_files_url' => url('http://drupal.org/documentation/modules/upload'),
      ));
      break;
    case 'node/%/edit/protected-downloads':
      $output .= '<p>' . t("This page displays files that are currently attached to this webform. You can select one or more of these files to be protected downloads. This means, that they won't be listed on the normal webform view page. Instead when the user submits the form, he receives an email to a given mail (you can choose any webform component of the type mail that you have already added to this webform) containing a link to download the protected file.") . '</p>';
      break;
  }
  return $output;
}

/**
 * Set the protected status for the given node / file combination
 *
 * @param int $nid 
 * @param int $fid 
 * @param boolean $protected 
 * @return void
 */
function webform_protected_downloads_file_set_protected($nid, $fid, $protected) {
  if (webform_protected_downloads_file_is_protected($nid, $fid) && !$protected) {
    db_query("DELETE FROM {wpd_protected_files} WHERE nid = %d AND fid = %d", $nid, $fid);
  }
  elseif (!webform_protected_downloads_file_is_protected($nid, $fid) && $protected) {
    $record = array(
      'nid' => $nid,
      'fid' => $fid,
      'created' => time(),
    );
    drupal_write_record('wpd_protected_files', $record);
  }
}

/**
 * Checks wheather the given file is protected for the given node
 *
 * @param int $nid 
 * @param int $fid 
 * @return boolean
 */
function webform_protected_downloads_file_is_protected($nid, $fid) {
  static $wpd_protected;
  if (!isset($wpd_protected[$nid])) {
    $wpd_protected[$nid] = array();
    $result = db_query("SELECT fid FROM {wpd_protected_files} WHERE nid = %d", $nid);
    while ($row = db_fetch_object($result)) {
      if (!in_array($row->fid, $wpd_protected[$nid])) {
        $wpd_protected[$nid][] = $row->fid;
      }
    }
  }
  return isset($wpd_protected[$nid]) ? in_array($fid, $wpd_protected[$nid]) : FALSE;
}

/**
 * Checks wheather the current user has access to the given file for the given
 * node
 *
 * @param int $nid 
 * @param int $fid 
 * @return boolean
 */
function webform_protected_downloads_file_user_has_access($nid, $fid) {

  // if it is protected, allow access only if the hash has been added to the
  // session
  if (!isset($_SESSION[WEBFORM_PROTECTED_DOWNLOADS_SESSION_KEY])) {
    return FALSE;
  }
  $sql = "SELECT    expires, hash\n          FROM      {wpd_protected_files} p\n          LEFT JOIN {webform_submissions} s ON (s.nid = p.nid)\n          LEFT JOIN {wpd_node_configuration} n ON (p.nid = n.nid)\n          LEFT JOIN {wpd_access_hashes} h ON (h.sid = s.sid)\n          WHERE     p.nid = %d\n          AND       p.fid = %d\n          AND       expires IS NOT NULL\n          AND       hash IS NOT NULL\n          AND       (n.retroactive = 1 OR (n.retroactive = 0 AND s.submitted > p.created))";
  $result = db_query($sql, array(
    $nid,
    $fid,
  ));
  while ($row = db_fetch_object($result)) {
    $ok = FALSE;

    // necessary condition
    if (isset($_SESSION[WEBFORM_PROTECTED_DOWNLOADS_SESSION_KEY][$row->hash])) {
      $session_expires = $_SESSION[WEBFORM_PROTECTED_DOWNLOADS_SESSION_KEY][$row->hash]['expires'];
      if ($session_expires == 0 || $session_expires > time()) {
        $ok = TRUE;
      }
      else {
        unset($_SESSION[WEBFORM_PROTECTED_DOWNLOADS_SESSION_KEY][$row->hash]);
        $ok = FALSE;
      }
    }
    else {
      $ok = FALSE;
    }
    $ok = $ok && ($row->expires == 0 || $row->expires > time());
    if ($ok) {
      return TRUE;
    }
  }
  return FALSE;
}

/**
 * Returns an array of files attached to the given node.
 * 
 * @param int $nid
 */
function webform_protected_downloads_get_attached_files($nid) {

  // Query copied form upload.module, but uses nid instead of vid
  $sql = 'SELECT      *
          FROM        {files} f
          INNER JOIN  {upload} r ON f.fid = r.fid
          WHERE       r.nid = %d
          ORDER BY    r.weight, f.fid';
  $files_result = db_query($sql, $nid);
  $files = array();
  while ($file = db_fetch_object($files_result)) {
    $files[$file->fid] = $file;
  }
  return $files;
}

/**
 * Removes any erroneous rows from {wpd_protected_files} that can be left over when files are
 * deleted from a webform-enabled node whilst it was previously NOT webform-enabled
 * 
 * @param int $nid
 */
function webform_protected_downloads_remove_deleted_files($nid) {
  $files = webform_protected_downloads_get_attached_files($nid);
  if (count($files)) {
    $sql = "DELETE FROM {wpd_protected_files} WHERE nid = %d AND fid NOT IN (%s)";
    $args = array(
      $nid,
      implode(',', array_keys($files)),
    );
    db_query($sql, $args);
  }
  else {
    $sql = "DELETE FROM {wpd_protected_files} WHERE nid = %d";
    $args = array(
      $nid,
    );
    db_query($sql, $args);
  }
}

/**
 * Checks if the given node has protected files
 *
 * @param int $nid 
 * @return void
 */
function webform_protected_downloads_node_has_protected_files($nid) {
  static $wpd_nodes;
  if (!isset($wpd_nodes[$nid])) {
    $files = webform_protected_downloads_get_attached_files($nid);
    if (count($files)) {

      // the node has attached files, so check if there are any protected ones
      $sql = "SELECT COUNT(*) FROM {wpd_protected_files} WHERE nid = %d AND fid IN (%s)";
      $args = array(
        $nid,
        implode(',', array_keys($files)),
      );
      $result = db_result(db_query($sql, $args));
      $wpd_nodes[$nid] = $result > 0;
    }
    else {

      // no files there, so there can't be any protected ones
      $wpd_nodes[$nid] = FALSE;
    }
  }
  return $wpd_nodes[$nid];
}

/**
 * Set the configuration for the given node
 *
 * @param int $nid 
 * @param int $cid 
 * @param string $subject 
 * @param string $body 
 * @param string $text_access 
 * @param string $text_noaccess 
 * @return void
 */
function webform_protected_downloads_set_configuration($nid, $args) {
  $configuration = array(
    'nid' => $nid,
    'mail_field_cid' => $args['mail_field_cid'],
    'mail_from' => $args['mail_from'],
    'mail_subject' => $args['mail_subject'],
    'mail_body' => $args['mail_body'],
    'access_type' => $args['access_type'],
    'expiration_download' => $args['expiration_download'],
    'expiration_session' => $args['expiration_session'],
    'retroactive' => $args['retroactive'],
    'redirect' => $args['redirect'],
    'text_download_access' => $args['text_download_access'],
    'text_download_noaccess' => $args['text_download_noaccess'],
  );
  drupal_write_record('wpd_node_configuration', $configuration, webform_protected_downloads_get_configuration($nid) ? array(
    'nid',
  ) : array());
}

/**
 * Get the configuration for the given node
 *
 * @param int $nid 
 * @param string $field optional
 * @return void
 */
function webform_protected_downloads_get_configuration($nid, $field = NULL, $default = NULL) {
  static $wpd_node_conf;
  if (!isset($wpd_node_conf[$nid])) {
    $result = db_query("SELECT * FROM {wpd_node_configuration} WHERE nid = %d", $nid);
    $wpd_node_conf[$nid] = db_fetch_object($result);
  }
  if ($wpd_node_conf[$nid]) {
    return $field !== NULL ? isset($wpd_node_conf[$nid]->{$field}) && !empty($wpd_node_conf[$nid]->{$field}) ? $wpd_node_conf[$nid]->{$field} : $default : $wpd_node_conf[$nid];
  }
  else {
    return FALSE;
  }
}

/**
 * Process unprocessed webform submissions
 *
 * @return void
 */
function webform_protected_downloads_process_submissions($form, &$form_state) {

  // get basic submission details
  $sid = $form_state['values']['details']['sid'];
  $nid = $form_state['values']['details']['nid'];

  // check whether the node has protected files, otherwise we can skip the
  // following steps
  if (!webform_protected_downloads_node_has_protected_files($nid)) {
    return;
  }

  // load the configuration for this node
  $conf = webform_protected_downloads_get_configuration($nid);

  // this should not happen, if it does something is seriously not working and
  // we can't figurue out where to send the mail, so skip it
  if (!isset($form_state['values']['submitted'][$conf->mail_field_cid])) {
    _webform_protected_downloads_log('Problem with node !nid: The mail field could not be found in the form submission. No e-mail has been send.', array(
      '!nid' => $nid,
    ), WATCHDOG_ERROR);
    return;
  }

  // now get the mail adress that should be used
  $mail = $form_state['values']['submitted'][$conf->mail_field_cid];

  // create hash, calculate expiration timestamp
  $hash = webform_protected_downloads_create_hash();
  $processed = time();
  $expires = $conf->expiration_download == 0 ? 0 : $processed + $conf->expiration_download;

  // we need to save before sending the mail
  $record = array(
    'sid' => $sid,
    'hash' => $hash,
    'processed' => $processed,
    'expires' => $expires,
    'used' => 0,
  );
  drupal_write_record('wpd_access_hashes', $record);

  // now send the mail
  webform_protected_downloads_send_mail($sid, $nid, $mail, $hash);
  if ($conf->redirect) {
    $form_state['redirect'] = 'node/' . $nid . '/download/' . $hash;
  }
}

/**
 * Create a hash that users can use to access the download page
 *
 * @param string $row 
 * @return void
 */
function webform_protected_downloads_create_hash() {
  $seed = 'JvKnrQWPsThuJteNQAuH';
  $hash = sha1(uniqid($seed . mt_rand(), true));
  $hash = substr($hash, 0, 32);
  return $hash;
}

/**
 * Retrieve details for this hash
 *
 * @param string $hash 
 * @return void
 */
function webform_protected_downloads_get_hash_details($hash) {
  static $wpd_hash;
  if (!isset($wpd_hash[$hash])) {
    $result = db_query("SELECT * FROM {wpd_access_hashes} WHERE hash = '%s'", $hash);
    $wpd_hash[$hash] = db_fetch_object($result);
  }
  return $wpd_hash[$hash];
}

/**
 * Retrieve the webform node based on the given hash
 *
 * @param string $hash 
 * @return void
 */
function webform_protected_downloads_get_node_from_hash($hash) {
  $result = db_query("SELECT nid FROM {wpd_access_hashes} LEFT JOIN {webform_submissions} USING(sid) WHERE hash = '%s'", $hash);
  $row = db_fetch_object($result);
  return $row->nid ? $row->nid : FALSE;
}

/**
 * Send mail to the user with a valid hash so that he can access the download page
 *
 * @param string $mail 
 * @param string $hash 
 * @return void
 */
function webform_protected_downloads_send_mail($sid = NULL, $nid, $mail, $hash) {
  global $user;

  // include the webform inc file
  module_load_include('inc', 'webform', 'includes/webform.submissions');

  // load the webform node, needed for token replacement
  $node = node_load($nid);

  // get the submission, including all it's data
  // NOTE: for test mails, triggered in
  // webform_protected_downloads_configuration_form_submit() $sid will be NULL
  $submission = $sid !== NULL ? webform_get_submission($nid, $sid) : FALSE;

  // the sender address
  $from = webform_protected_downloads_get_configuration($nid, 'mail_from', variable_get('site_mail', NULL));

  // build the subject
  $subject = webform_protected_downloads_get_configuration($nid, 'mail_subject');
  $subject = _webform_protected_downloads_token_replace($subject, $node, $hash);

  // build the body
  $body = webform_protected_downloads_get_configuration($nid, 'mail_body');
  $body = _webform_protected_downloads_token_replace($body, $node, $hash);
  if ($submission) {
    $subject = _webform_filter_values($subject, $node, $submission, $email = NULL, $strict = FALSE, $allow_anonymous = TRUE);
    $body = _webform_filter_values($body, $node, $submission, $email = NULL, $strict = FALSE, $allow_anonymous = TRUE);
  }
  $params = array(
    'subject' => $subject,
    'body' => $body,
    'From' => $from,
  );

  // send the mail
  drupal_mail('webform_protected_downloads', 'confirmation', $mail, $user->language, $params, $from);
}

/**
 * Implementation of hook_mail().
 */
function webform_protected_downloads_mail($key, &$message, $params) {
  $language = $message['language'];
  $variables = user_mail_tokens($params['account'], $language);
  switch ($key) {
    case 'confirmation':
      $message['subject'] = $params['subject'];
      $message['body'][] = $params['body'];
      break;
  }
}

/**
 * Helper function for token replacement
 *
 * @param string $string 
 * @param object $node 
 * @param string $hash 
 * @return string
 */
function _webform_protected_downloads_token_replace($string, $node, $hash) {
  if (!module_exists('token')) {
    return $string;
  }
  return token_replace_multiple($string, array(
    'webform_protected_downloads' => NULL,
    'global' => NULL,
  ), '[', ']', array(
    'node' => $node,
    'hash' => $hash,
  ));
}

/**
 * Implementation of hook_form_alter().
 * Doc says that $form_state is passed by reference, but that generates
 * warnings:
 * warning: Parameter 2 to webform_protected_downloads_form_alter() expected to
 * be a reference, value given in /includes/common.inc on line 2883.
 * Since we don't use that parameter here, we can safely set it to value
 * instead of reference.
 *
 * @param array $form 
 * @param array $form_state 
 * @param string $form_id 
 * @return void
 */
function webform_protected_downloads_form_alter(&$form, $form_state, $form_id) {
  if (strpos($form_id, 'webform_client_form_') !== FALSE) {

    // trigger processing after the form has been submitted and handled by
    // webform
    $form['#submit'][] = 'webform_protected_downloads_process_submissions';
  }

  // update the file upload form, so that protected files can't be deleted,
  // listed or have the private option removed
  if (strpos($form_id, '_node_form') !== FALSE && webform_protected_downloads_node_is_webform($form['#node'])) {

    // add information about this node, so that we can load it while
    // processing the upload_js form
    $form['attachments']['wrapper']['wpd_node_nid'] = array(
      '#type' => 'hidden',
      '#value' => $form['#node']->nid,
    );
    if (count($form['attachments']['wrapper']['files'])) {
      webform_protected_downloads_adjust_upload_form($form['attachments']['wrapper']['files'], $form['#node']->nid);
      if (webform_protected_downloads_node_has_protected_files($form['#node']->nid)) {
        $form['attachments']['#description'] .= "<br />" . t('One ore more of the following files are protected and can not be listed or deleted. You can change this on the <a href="@protected_downloads_page">Protected Downloads page</a>.', array(
          '@protected_downloads_page' => url('node/' . $form['#node']->nid . '/protected-downloads'),
        ));
      }
    }
  }
  switch ($form_id) {

    // update the file upload form, so that protected files can't be deleted,
    // listed or have the private option removed
    case 'upload_js':
      if (count($form['files'])) {

        // try to load the node
        $node = node_load($form['#post']['wpd_node_nid']);

        // if we have a node to operate on we can proceed
        if ($node->nid) {
          webform_protected_downloads_adjust_upload_form($form['files'], $node->nid);
        }
      }
      break;

    // disable redirect options on the form configuration form if the redirect
    // to the downloads page has been activated for protected downloads
    case 'webform_configure_form':
      if (webform_protected_downloads_get_configuration($form['nid']['#value'], 'redirect')) {
        $element =& $form['submission']['redirection'];
        $element['redirect']['#attributes']['disabled'] = 'disabled';
        $element['redirect']['#value'] = $element['redirect']['#default_value'];
        $element['redirect_url']['#attributes']['disabled'] = 'disabled';
        $element['redirect_url']['#value'] = $element['redirect_url']['#default_value'];
        $element['#description'] .= '<br />' . t('This setting has been deactivated, because this webform is configured to redirect to the downloads page for the protected files. You can change this on the <a href="@protected_downloads_page">Protected Downloads page</a>.', array(
          '@protected_downloads_page' => url('node/' . $form['nid']['#value'] . '/protected-downloads'),
        ));
      }
      break;

    // disable the mandatory checkbox on the webform component overview, for
    // the component that is in use as mail for protected downloads
    case 'webform_components_form':
      if (webform_protected_downloads_node_has_protected_files($form['#node']->nid)) {
        foreach ($form['components'] as $cid => &$element) {
          if (webform_protected_downloads_get_configuration($form['#node']->nid, 'mail_field_cid', NULL) == $cid) {
            $element['mandatory']['#disabled'] = TRUE;
          }
        }
      }
      break;

    // disable the mandatory checkbox on the webform component edit form, if
    // the current component is the one used as mail for protected downloads
    case 'webform_component_edit_form':
      if (webform_protected_downloads_node_has_protected_files($form['nid']['#value'])) {
        if (webform_protected_downloads_get_configuration($form['nid']['#value'], 'mail_field_cid', NULL) == $form['cid']['#value']) {
          $form['validation']['mandatory']['#disabled'] = TRUE;
          $form['validation']['mandatory']['#default_value'] = 1;
          $form['validation']['mandatory']['#description'] .= '<br />' . t('This field has been disabled because one or more files attached to this node have been protected. This field is used as the mail address for the confirmation mail that users need to access the protected files.');
          $form['display']['disabled']['#default_value'] = 0;
          $form['display']['disabled']['#disabled'] = TRUE;
          $form['display']['disabled']['#description'] .= '<br />' . t('This field has been disabled because one or more files attached to this node have been protected. This component is used as the mail address for the confirmation mail that users need to access the protected files so it must be possible to edit it.');
        }
      }
      break;
  }

  // check if this is a webform form and set a message if there is a problem
  // with this webform
  if (strpos($form_id, 'webform_') !== FALSE) {
    if (arg(0) == 'node' && is_numeric(arg(1)) && arg(2) != 'webform-results') {
      $node = node_load(arg(1));
      if (webform_protected_downloads_node_is_webform($node) && $node->wpd_valid === FALSE) {
        _webform_protected_downloads_set_warning();
      }
    }
  }
}

/**
 * Updates the file listing in an upload form to disable delete, list and
 * private checkboxes of files protected by this module.
 *
 * @param array $files FAPI structure
 * @param int $nid 
 * @return void
 */
function webform_protected_downloads_adjust_upload_form(&$files, $nid) {
  foreach ($files as $fid => &$file_form) {
    if ($fid[0] == '#' || $fid == 'new') {
      continue;
    }
    if (webform_protected_downloads_file_is_protected($nid, $fid)) {
      $file_form['remove']['#disabled'] = TRUE;
      $file_form['list']['#disabled'] = TRUE;
      $file_form['list']['#value'] = FALSE;
      $file_form['private']['#disabled'] = TRUE;
      $file_form['private']['#value'] = TRUE;
    }
  }
}

/**
 * Set a warning message about problems with this webform configuration
 *
 * @return void
 */
function _webform_protected_downloads_set_warning() {
  static $wpd_message;
  if (!isset($wpd_message)) {
    drupal_set_message(t('The mandatory email field has been removed from this webform. Protected downloads have been disabled. Please fix this problem.'));
    $wpd_message = TRUE;
  }
}

/**
 * Wrapper around watchdog
 *
 * @param string $msg 
 * @param array $variables 
 * @param string $severity 
 * @param string $link 
 * @return void
 */
function _webform_protected_downloads_log($msg, $variables, $severity = WATCHDOG_NOTICE, $link = NULL) {
  watchdog(WEBFORM_PROTECTED_DOWNLOADS_WATCHDOG_ID, $msg, $variables, $severity, $link);
}

/**
 * Implementation of hook_token_list().
 *
 * @param string $type 
 * @return array
 */
function webform_protected_downloads_token_list($type = 'all') {
  if ($type == 'webform_protected_downloads' || $type == 'all') {
    $tokens['Protected downloads']['download-url'] = t("A url to access the protected downloads");
    $tokens['Protected downloads']['download-expires'] = t("The date when the download url expires or 'never'.");
    $tokens['Protected downloads']['file-list'] = t("A list of the files attached to this node (not linked).");
    $tokens['Protected downloads']['file-list-checksum'] = t("A list of the files attached to this node including checksum (not linked).");
    return $tokens;
  }
}

/**
 * Implementation of hook_token_values().
 *
 * @param string $type 
 * @param object $object 
 * @param array $options 
 * @return array
 */
function webform_protected_downloads_token_values($type, $object = NULL, $options = array()) {
  if ($type == 'webform_protected_downloads') {
    $node = $options['node'];
    $hash = webform_protected_downloads_get_hash_details($options['hash']);
    $expires = $hash->expires > 0 ? format_date($hash->expires) : t('never');
    $tokens['download-url'] = url('node/' . $node->nid . '/download/' . $hash->hash, array(
      'absolute' => TRUE,
    ));
    $tokens['download-expires'] = $expires;
    $tokens['file-list'] = theme('webform_protected_downloads_mail_token_file_list', $node->files);
    $tokens['file-list-checksum'] = theme('webform_protected_downloads_mail_token_file_list', $node->files, TRUE);
    return $tokens;
  }
}

/**
 * Theme function for the file list that may be included in the confirmation
 * mail which is send to the user.
 *
 * @param array $files 
 * @param boolean $checksum 
 * @return string
 */
function theme_webform_protected_downloads_mail_token_file_list($files, $checksum = FALSE) {
  $rendered_files = array();
  foreach ($files as $file) {
    if (!webform_protected_downloads_file_is_protected($file->nid, $file->fid)) {
      continue;
    }
    $args = array(
      '!filename' => $file->filename,
      '!size' => format_size($file->filesize),
      '!type' => $file->filemime,
    );
    if ($checksum === FALSE) {
      $rendered_files[] = t('!filename (!size, !type)', $args);
    }
    else {
      $args['!checksum'] = md5_file($file->filepath);
      $rendered_files[] = t('!filename (!size, !type, checksum: !checksum)', $args);
    }
  }
  return count($rendered_files) ? implode("\n", $rendered_files) : '';
}

Functions

Namesort descending Description
theme_webform_protected_downloads_mail_token_file_list Theme function for the file list that may be included in the confirmation mail which is send to the user.
webform_protected_downloads_access Custom access callback
webform_protected_downloads_adjust_upload_form Updates the file listing in an upload form to disable delete, list and private checkboxes of files protected by this module.
webform_protected_downloads_component_delete_access Custom access callback that checks wheather a webform component can be deleted
webform_protected_downloads_create_hash Create a hash that users can use to access the download page
webform_protected_downloads_cron Implementation of hook_cron().
webform_protected_downloads_file_download Implementation of hook_file_download().
webform_protected_downloads_file_is_protected Checks wheather the given file is protected for the given node
webform_protected_downloads_file_set_protected Set the protected status for the given node / file combination
webform_protected_downloads_file_user_has_access Checks wheather the current user has access to the given file for the given node
webform_protected_downloads_form_alter Implementation of hook_form_alter(). Doc says that $form_state is passed by reference, but that generates warnings: warning: Parameter 2 to webform_protected_downloads_form_alter() expected to be a reference, value given in /includes/common.inc on…
webform_protected_downloads_get_attached_files Returns an array of files attached to the given node.
webform_protected_downloads_get_configuration Get the configuration for the given node
webform_protected_downloads_get_hash_details Retrieve details for this hash
webform_protected_downloads_get_node_from_hash Retrieve the webform node based on the given hash
webform_protected_downloads_help Implementation of hook_help().
webform_protected_downloads_mail Implementation of hook_mail().
webform_protected_downloads_menu Implementation of hook_menu().
webform_protected_downloads_menu_alter Implementation of hook_menu_alter().
webform_protected_downloads_nodeapi Implementation of hook_nodeapi().
webform_protected_downloads_node_has_protected_files Checks if the given node has protected files
webform_protected_downloads_node_is_webform Check whether the given node is used as a webform
webform_protected_downloads_perm Implementation of hook_perm().
webform_protected_downloads_process_submissions Process unprocessed webform submissions
webform_protected_downloads_remove_deleted_files Removes any erroneous rows from {wpd_protected_files} that can be left over when files are deleted from a webform-enabled node whilst it was previously NOT webform-enabled
webform_protected_downloads_send_mail Send mail to the user with a valid hash so that he can access the download page
webform_protected_downloads_set_configuration Set the configuration for the given node
webform_protected_downloads_theme Implementation of hook_theme().
webform_protected_downloads_token_list Implementation of hook_token_list().
webform_protected_downloads_token_values Implementation of hook_token_values().
_webform_protected_downloads_log Wrapper around watchdog
_webform_protected_downloads_set_warning Set a warning message about problems with this webform configuration
_webform_protected_downloads_token_replace Helper function for token replacement

Constants

Namesort descending Description
WEBFORM_PROTECTED_DOWNLOADS_ACCESS_TYPE_EXPIRES The access type "Expires"
WEBFORM_PROTECTED_DOWNLOADS_ACCESS_TYPE_SINGLE The access type "One-time access"
WEBFORM_PROTECTED_DOWNLOADS_DEFAULT_ACCESS_TYPE The default access type for new webforms
WEBFORM_PROTECTED_DOWNLOADS_DEFAULT_EXPIRATION_DOWNLOAD The default expiration interval for the download itself in seconds.
WEBFORM_PROTECTED_DOWNLOADS_DEFAULT_EXPIRATION_SESSION The default expiration interval for the file access that is managed by the session information itself in seconds.
WEBFORM_PROTECTED_DOWNLOADS_DEFAULT_REDIRECT Default setting for direct redirect to the downloads page after form submission.
WEBFORM_PROTECTED_DOWNLOADS_DEFAULT_RETROACTIVE Default setting for retroactive access, meaning whether or not a user can access files that have been protected after the first form submission.
WEBFORM_PROTECTED_DOWNLOADS_SESSION_KEY The session key.
WEBFORM_PROTECTED_DOWNLOADS_WATCHDOG_ID Our identifier for watchdog messages