'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
FROM {files} f
LEFT JOIN {wpd_protected_files} p USING(fid)
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 Protected Downloads configuration of this webform and change the Mail confirmation field or unprotect all files.
Go back to the form', 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 Protected Downloads configuration 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
FROM {wpd_access_hashes} h
LEFT JOIN {webform_submissions} s USING(sid)
LEFT JOIN {wpd_node_configuration} n USING(nid)
WHERE (expires < %d AND expires != 0)
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("
'. 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.") .'
'; 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 FROM {wpd_protected_files} p LEFT JOIN {webform_submissions} s ON (s.nid = p.nid) LEFT JOIN {wpd_node_configuration} n ON (p.nid = n.nid) LEFT JOIN {wpd_access_hashes} h ON (h.sid = s.sid) WHERE p.nid = %d AND p.fid = %d AND expires IS NOT NULL AND hash IS NOT NULL 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'] .= "