You are here

contact_attach.module in Contact Attach 7

Same filename and directory in other branches
  1. 5 contact_attach.module
  2. 6 contact_attach.module

Allows attaching files to messages sent using contact forms.

This module gives users the ability of attaching files to messages sent using the site-wide contact form or a user's personal contact form.

File

contact_attach.module
View source
<?php

/**
 * @file
 * Allows attaching files to messages sent using contact forms.
 *
 * This module gives users the ability of attaching files to messages sent using
 * the site-wide contact form or a user's personal contact form.
 */

/**
 * Default number of attachments on contact forms.
 */
define('CONTACT_ATTACH_DEFAULT_NUMBER', 1);

/**
 * Default attachment extensions that will be permitted.
 */
define('CONTACT_ATTACH_DEFAULT_EXTENSIONS', '');

/**
 * Default maximum attachment size that will be permitted, in megabytes.
 */
define('CONTACT_ATTACH_DEFAULT_UPLOADSIZE', 0.0009765625);

/**
 * Implements hook_help().
 */
function contact_attach_help($path, $arg) {
  switch ($path) {
    case 'admin/config/media/contact_attach':
      return '<p>' . t('The roles that are listed here are those that have the necessary permissions to attach files on the specified contact form. To make a role appear here so that the settings for that role can be changed, grant the necessary permissions in the module\'s section on the <a href="@permissions">permissions</a> page.', array(
        '@permissions' => url('admin/people/permissions'),
      )) . '</p>';
  }
}

/**
 * Implements hook_permission().
 */
function contact_attach_permission() {
  $allowed_permissions = array(
    'attach files on site-wide contact form' => array(
      'title' => t('Attach files on the site-wide contact form'),
      'description' => t('Send messages with attachments from the site-wide contact form.'),
    ),
    'attach files on personal contact forms' => array(
      'title' => t('Attach files on personal contact forms'),
      'description' => t('Send messages with attachments from personal contact forms.'),
    ),
  );
  return $allowed_permissions;
}

/**
 * Implements hook_menu().
 */
function contact_attach_menu() {
  $items['admin/config/media/contact_attach'] = array(
    'title' => 'Contact form attachments',
    'description' => 'Configure settings for attaching files on contact forms.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'contact_attach_admin_settings',
    ),
    'access arguments' => array(
      'administer site configuration',
    ),
    'file' => 'contact_attach.admin.inc',
  );
  return $items;
}

/**
 * Implements hook_form_alter().
 */
function contact_attach_form_alter(&$form, &$form_state, $form_id) {
  if ($form_id === 'contact_personal_form' && user_access('attach files on personal contact forms') || $form_id === 'contact_site_form' && user_access('attach files on site-wide contact form')) {
    switch ($form_id) {
      case 'contact_site_form':
        $contact_form_short = 'site';
        $contact_form_permission = 'attach files on site-wide contact form';
        break;
      case 'contact_personal_form':
        $contact_form_short = 'user';
        $contact_form_permission = 'attach files on personal contact forms';
        break;
    }
    $contact_attach_numbers = variable_get('contact_attach_number_' . $contact_form_short, array());
    $roles = _contact_attach_get_valid_roles($contact_form_permission, $contact_attach_numbers);
    if (module_exists('file') && variable_get('contact_attach_simple_field', 0) !== 1) {
      $file_field_type = 'managed_file';
    }
    else {
      $file_field_type = 'file';
    }

    // Send these values along to the form validation and submit handlers.
    $form['file_field_type'] = array(
      '#type' => 'value',
      '#value' => $file_field_type,
    );
    $form['attachments_allowed'] = array(
      '#type' => 'value',
      '#value' => _contact_attach_return_max_attachments($roles, $contact_attach_numbers),
    );
    $form['allowed_extensions'] = array(
      '#type' => 'value',
      '#value' => _contact_attach_return_allowed_extensions($roles, $contact_form_short),
    );
    $form['file_size_limit'] = array(
      '#type' => 'value',
      '#value' => _contact_attach_return_max_file_size($roles, $contact_form_short),
    );
    $description = t('Files must be less than !size.', array(
      '!size' => '<strong>' . format_size($form['file_size_limit']['#value']) . '</strong>',
    ));
    $description .= '<br />' . t('Allowed file types: !extensions.', array(
      '!extensions' => '<strong>' . $form['allowed_extensions']['#value'] . '</strong>',
    ));
    if ($form['attachments_allowed']['#value'] !== 1) {
      $form['attachments'] = array(
        '#type' => 'fieldset',
        '#title' => t('Attachments'),
      );
      $form['attachments']['#description'] = $description;
    }
    for ($i = 1; $i <= $form['attachments_allowed']['#value']; $i++) {
      $form['attachments']['contact_attach_' . $i] = array(
        '#type' => $form['file_field_type']['#value'],
        '#weight' => $i,
      );
      if ($form['attachments_allowed']['#value'] !== 1) {
        $form['attachments']['contact_attach_' . $i]['#title'] = t('Attachment #@i', array(
          '@i' => $i,
        ));
      }
      else {
        $form['attachments']['contact_attach_' . $i]['#title'] = t('Attachment');
        $form['attachments']['contact_attach_' . $i]['#description'] = $description;
      }
      if ($form['file_field_type']['#value'] === 'managed_file') {
        $form['attachments']['contact_attach_' . $i]['#upload_validators'] = array(
          'file_validate_extensions' => array(
            $form['allowed_extensions']['#value'],
          ),
          'file_validate_size' => array(
            (string) $form['file_size_limit']['#value'],
          ),
        );
        $form['attachments']['contact_attach_' . $i]['#progress_message'] = t('Attaching...');
      }
    }

    // Use only our form submission handler.
    $form['#submit'] = array(
      'contact_attach_' . $form_id . '_submit',
    );
    $form['actions']['submit']['#weight'] = $i + 1;
    if ($form['file_field_type']['#value'] === 'file') {

      // Add a form validation handler to validate attachments.
      $form['#validate'][] = 'contact_attach_contact_form_validate';
    }
  }
}

/**
 * Form validation handler for contact_site_form() and contact_personal_form().
 *
 * Validates the attachments.
 *
 * @see contact_attach_contact_site_form_submit()
 * @see contact_attach_contact_personal_form_submit()
 * @see contact_attach_form_alter()
 */
function contact_attach_contact_form_validate($form, &$form_state) {
  $validators = array(
    'file_validate_extensions' => array(
      $form_state['values']['allowed_extensions'],
    ),
    'file_validate_size' => array(
      $form_state['values']['file_size_limit'],
    ),
  );

  // Loop through each possible attachment.
  foreach ($_FILES['files']['name'] as $temp_name => $file_name) {
    $file = file_save_upload($temp_name, $validators);
    if ($file === FALSE) {
      form_set_error($temp_name, t('Failed to attach the file %name.', array(
        '%name' => $file_name,
      )));
    }
  }
}

/**
 * Form submission handler for contact_site_form().
 *
 * Overrides contact_site_form_submit().
 *
 * @see contact_attach_contact_form_validate()
 * @see contact_attach_form_alter()
 */
function contact_attach_contact_site_form_submit($form, &$form_state) {
  global $user, $language;
  $values = $form_state['values'];
  $values['sender'] = $user;
  $values['sender']->name = $values['name'];
  $values['sender']->mail = $values['mail'];
  $values['category'] = contact_load($values['cid']);

  // Save the anonymous user information to a cookie for reuse.
  if (!$user->uid) {
    user_cookie_save(array_intersect_key($values, array_flip(array(
      'name',
      'mail',
    ))));
  }

  // Get the to and from e-mail addresses.
  $to = $values['category']['recipients'];
  $from = $values['sender']->mail;

  // Send the e-mail to the recipients using the site default language.
  $results['mail'] = drupal_mail('contact', 'page_mail', $to, language_default(), $values, $from);

  // If the user requests it, send a copy using the current language.
  if ($values['copy']) {
    $results['copy'] = drupal_mail('contact', 'page_copy', $from, $language, $values, $from);
  }

  // Send an auto-reply if necessary using the current language.
  if ($values['category']['reply']) {
    $results['autoreply'] = drupal_mail('contact', 'page_autoreply', $from, $language, $values, $to);
  }
  if (!empty($results['mail']['result'])) {
    flood_register_event('contact', variable_get('contact_threshold_window', 3600));
    watchdog('mail', '%sender-name (@sender-from) sent an e-mail regarding %category.', array(
      '%sender-name' => $values['name'],
      '@sender-from' => $from,
      '%category' => $values['category']['category'],
    ));
    $user_message = t('Your message has been sent.');
    if ($values['copy'] && empty($results['copy']['result'])) {
      watchdog('mail', 'The mail system failed to send a copy of the message to the site-wide contact form user.', array(), WATCHDOG_ERROR);
      $user_message .= ' ' . t('However, the copy asked for failed to send.');
    }
    if ($values['category']['reply'] && empty($results['autoreply']['result'])) {
      watchdog('mail', 'The mail system failed to send an auto-reply to the site-wide contact form user.', array(), WATCHDOG_ERROR);
    }
    drupal_set_message($user_message);
  }
  else {
    watchdog('mail', '%sender-name (@sender-from) attempted to send an e-mail regarding %category, but was unsuccessful.', array(
      '%sender-name' => $values['name'],
      '@sender-from' => $from,
      '%category' => $values['category']['category'],
    ), WATCHDOG_ERROR);
    drupal_set_message(t('There was a problem sending your message. Please try again later.'), 'error');
  }

  // Jump to home page rather than back to contact page to avoid contradictory
  // messages if flood control has been activated.
  $form_state['redirect'] = '';
}

/**
 * Form submission handler for contact_personal_form().
 *
 * Overrides contact_personal_form_submit().
 *
 * @see contact_attach_contact_form_validate()
 * @see contact_attach_form_alter()
 */
function contact_attach_contact_personal_form_submit($form, &$form_state) {
  global $user, $language;
  $values = $form_state['values'];
  $values['sender'] = $user;
  $values['sender']->name = $values['name'];
  $values['sender']->mail = $values['mail'];

  // Save the anonymous user information to a cookie for reuse.
  if (!$user->uid) {
    user_cookie_save(array_intersect_key($values, array_flip(array(
      'name',
      'mail',
    ))));
  }

  // Get the to and from e-mail addresses.
  $to = $values['recipient']->mail;
  $from = $values['sender']->mail;

  // Send the e-mail in the requested user language.
  $results['mail'] = drupal_mail('contact', 'user_mail', $to, user_preferred_language($values['recipient']), $values, $from);

  // Send a copy if requested, using current page language.
  if ($values['copy']) {
    $results['copy'] = drupal_mail('contact', 'user_copy', $from, $language, $values, $from);
  }
  if (!empty($results['mail']['result'])) {
    flood_register_event('contact', variable_get('contact_threshold_window', 3600));
    watchdog('mail', '%sender-name (@sender-from) sent %recipient-name an e-mail.', array(
      '%sender-name' => $values['name'],
      '@sender-from' => $from,
      '%recipient-name' => $values['recipient']->name,
    ));
    $user_message = t('Your message has been sent.');
    if ($values['copy'] && empty($results['copy']['result'])) {
      watchdog('mail', 'The mail system failed to send a copy of the message to the personal contact form user.', array(), WATCHDOG_ERROR);
      $user_message .= ' ' . t('However, the copy asked for failed to send.');
    }
    drupal_set_message($user_message);
  }
  else {
    watchdog('mail', '%sender-name (@sender-from) attempted to send %recipient-name an e-mail, but was unsuccessful.', array(
      '%sender-name' => $values['name'],
      '@sender-from' => $from,
      '%recipient-name' => $values['recipient']->name,
    ), WATCHDOG_ERROR);
    drupal_set_message(t('There was a problem sending your message. Please try again later.'), 'error');
  }

  // Jump to the contacted user's profile page if the user is allowed.
  $form_state['redirect'] = user_access('access user profiles') ? 'user/' . $values['recipient']->uid : '';
}

/**
 * Implements hook_mail_alter().
 */
function contact_attach_mail_alter(&$message) {
  if (isset($message['params']['attachments_allowed'])) {
    switch ($message['id']) {
      case 'contact_page_mail':
      case 'contact_page_copy':
      case 'contact_user_mail':
      case 'contact_user_copy':
        $return_message = _contact_attach_process_attachments($message);
        if (!empty($return_message)) {
          $message['headers'] = $return_message['headers'];
          $message['body'] = $return_message['body'];
        }
        break;
    }
  }
}

/**
 * Checks for attachments and processes them, if one or more exist.
 *
 * @param array $message
 *   The message, as it exists so far.
 *
 * @return array
 *   The message, including processed attachment(s).
 */
function _contact_attach_process_attachments($message) {
  $return_message = array();
  if ($message['params']['file_field_type'] === 'managed_file') {

    // Loop through each possible attachment when using managed_file fields.
    for ($i = 1; $i <= $message['params']['attachments_allowed']; $i++) {
      if ($message['params']['contact_attach_' . $i] !== 0) {

        // An attachment exists, so save it to an array for later processing.
        $files[] = file_load($message['params']['contact_attach_' . $i]);
      }
    }
  }
  else {

    // Loop through each possible attachment when using simple file fields.
    foreach ($_FILES['files']['name'] as $temp_name => $file_name) {
      if ($file = file_save_upload($temp_name)) {

        // Check to see if the attachment exists.
        if ($file->filesize > 0) {

          // An attachment exists, so save it to an array for later processing.
          $files[] = $file;
        }
      }
    }
  }

  // If the array contains something, we have one or more attachments to
  // process. If it does not contain anything, we send back an empty $body,
  // indicating no attachments exist.
  if (!empty($files)) {

    // Set initial values.
    $attachments = '';
    $body_text = '';
    $boundary_id = md5(uniqid(time()));
    $mail_system = variable_get('mail_system', array());
    $message['headers']['Content-Type'] = 'multipart/mixed; boundary="' . $boundary_id . '"';

    // Add the body text.
    $body_text = "\n--{$boundary_id}\n";
    $body_text .= "Content-Type: text/plain; charset=UTF-8; format=flowed;\n\n";
    $body_text .= implode("\n\n", $message['body']);
    $body_text .= "\n\n\n";

    // Add the attachments.
    // Loop through each possible attachment.
    foreach ($files as $file_object) {

      // Process the attachment.
      $attachments .= "--{$boundary_id}\n";
      $attachments .= _contact_attach_add_attachment($file_object, $mail_system);
      $attachments .= "\n\n";
    }
    $attachments .= "--{$boundary_id}--\n\n";
    $return_message['headers'] = $message['headers'];
    $return_message['body'][0] = $body_text;
    $return_message['body'][1] = $attachments;
  }
  return $return_message;
}

/**
 * Returns a fully-encoded attachment ready to be included into a message body.
 *
 * @param object $file
 *   An attachment to add to the message.
 * @param array $mail_system
 *   (optional) An associative array containing the contents of the persistent
 *   variable mail_system. Defaults to array().
 * @param bool $delete
 *   (optional) A boolean indicating if the file will be deleted after it has
 *   been base64 encoded. Tests can set this to FALSE so that they can use the
 *   file to compare against after the message has been sent. Defaults to TRUE.
 *
 * @return string
 *   The processed attachment, ready for appending to the message.
 */
function _contact_attach_add_attachment($file, $mail_system = array(), $delete = TRUE) {
  $attachment = 'Content-Type: ' . $file->filemime . '; name="' . basename($file->filename) . "\"\n";
  $attachment .= "Content-Transfer-Encoding: base64\n";

  // SMTP module pulls the file path from the filename attribute in the header,
  // so it can not contain only the file name if the SMTP module is used.
  if (!empty($mail_system) && $mail_system['default-system'] === 'SmtpMailSystem') {
    $attachment .= 'Content-Disposition: attachment; filename="' . $file->uri . "\"\n\n";
  }
  else {
    $attachment .= 'Content-Disposition: attachment; filename="' . $file->filename . "\"\n\n";
  }
  if (file_exists($file->uri)) {
    $attachment .= chunk_split(base64_encode(file_get_contents($file->uri)));
  }
  else {
    $attachment .= chunk_split(base64_encode(file_get_contents(file_directory_temp() . '/' . $file->filename)));
  }
  if ($delete) {

    // Delete the file after it has been embedded, as it no longer serves a
    // purpose. Drupal deletes them after DRUPAL_MAXIMUM_TEMP_FILE_AGE has
    // passed on the next cron run, but we can save Drupal the trouble by
    // cleaning up after ourselves. This can also be important for privacy.
    file_delete($file);
  }
  return $attachment;
}

/**
 * Gets active user's valid roles to be considered in aggregation of settings.
 *
 * @param string $contact_form_permission
 *   The contact form permission to check permissions against.
 * @param array $contact_attach_numbers
 *   An associative array of the number of attachments allowed for each role.
 *
 * @return array
 *   An associative array of the active user's valid roles that will be
 *   considered in the aggregation and overriding of settings.
 */
function _contact_attach_get_valid_roles($contact_form_permission, $contact_attach_numbers) {
  global $user;
  $permitted_roles = user_roles(FALSE, $contact_form_permission);
  if (count($user->roles) === 1) {
    $roles = array_keys($user->roles);
  }
  elseif (array_key_exists(DRUPAL_AUTHENTICATED_RID, $user->roles) && array_key_exists(DRUPAL_AUTHENTICATED_RID, $permitted_roles)) {

    // If the user has the authenticated user role and it is permitted to attach
    // files, all of the user's roles are valid, as all created roles inherit
    // this role. Hence, no use in checking if they have permissions.
    $roles = $user->roles;

    // Exclude the authenticated user role when the user has settings set by
    // other roles, as all created users automatically get this role and it
    // should not override the settings for the explicitly assigned role.
    $has_specific_setting = FALSE;
    foreach ($roles as $rid => $name) {
      if ($rid !== DRUPAL_AUTHENTICATED_RID && array_key_exists($rid, $contact_attach_numbers)) {
        $has_specific_setting = TRUE;
        break;
      }
    }
    if (array_key_exists(DRUPAL_AUTHENTICATED_RID, $roles) && $has_specific_setting) {
      unset($roles[DRUPAL_AUTHENTICATED_RID]);
    }
    $roles = array_keys($roles);
  }
  else {

    // Figure out which of the user's roles are permitted to add attachments.
    $roles = array_keys(array_intersect_assoc($user->roles, $permitted_roles));
  }
  return $roles;
}

/**
 * Returns maximum number of attachments allowed based on all supplied roles.
 *
 * @param array $roles
 *   An associative array of the active user's valid roles.
 * @param array $contact_attach_numbers
 *   An associative array containing the number of allowed attachments for every
 *   role that has this setting defined.
 *
 * @return int
 *   The maximum number of attachments allowed based on all supplied roles.
 */
function _contact_attach_return_max_attachments($roles, $contact_attach_numbers) {
  $attachments_allowed = 1;
  foreach ($roles as $rid) {
    $attachments_allowed_role = !empty($contact_attach_numbers[$rid]) ? (int) $contact_attach_numbers[$rid] : CONTACT_ATTACH_DEFAULT_NUMBER;
    if ($attachments_allowed_role > $attachments_allowed) {
      $attachments_allowed = $attachments_allowed_role;
    }
  }
  return $attachments_allowed;
}

/**
 * Returns allowed extensions for attachments based on all supplied roles.
 *
 * @param array $roles
 *   An associative array of the active user's valid roles.
 * @param string $contact_form
 *   Short form of the contact form to return allowed extensions for.
 *
 * @return string
 *   An aggregated set of allowed extensions based on all supplied roles.
 */
function _contact_attach_return_allowed_extensions($roles, $contact_form) {
  $extensions = '';
  $contact_attach_extensions = variable_get('contact_attach_extensions_' . $contact_form, array());

  // Build an aggregated set of allowed extensions.
  foreach ($roles as $rid) {

    // Concatenate the role's allowed file extensions with what we already have.
    $extensions .= !empty($contact_attach_extensions[$rid]) ? $contact_attach_extensions[$rid] . ' ' : CONTACT_ATTACH_DEFAULT_EXTENSIONS;
  }

  // Remove duplicates from the aggregated set of allowed extensions.
  $extensions = rtrim(implode(' ', array_unique(explode(' ', $extensions))));
  return $extensions;
}

/**
 * Returns the allowed file size for attachments based on all supplied roles.
 *
 * @param array $roles
 *   An associative array of the active user's valid roles.
 * @param string $contact_form
 *   Short form of the contact form to return the maximum allowed file size for.
 *
 * @return float
 *   The maximum allowed file size for attachments based on all supplied roles.
 */
function _contact_attach_return_max_file_size($roles, $contact_form) {
  $file_size_limit = 0;
  $contact_attach_uploadsizes = variable_get('contact_attach_uploadsize_' . $contact_form, array());
  foreach ($roles as $rid) {

    // Get the role's allowed file size.
    $file_size_limit_role = (!empty($contact_attach_uploadsizes[$rid]) ? (double) $contact_attach_uploadsizes[$rid] : CONTACT_ATTACH_DEFAULT_UPLOADSIZE) * 1024 * 1024;

    // If the role's allowed file size is greater than what we already have, use
    // it instead.
    if ($file_size_limit_role > $file_size_limit) {
      $file_size_limit = $file_size_limit_role;
    }
  }
  return $file_size_limit;
}

Functions

Namesort descending Description
contact_attach_contact_form_validate Form validation handler for contact_site_form() and contact_personal_form().
contact_attach_contact_personal_form_submit Form submission handler for contact_personal_form().
contact_attach_contact_site_form_submit Form submission handler for contact_site_form().
contact_attach_form_alter Implements hook_form_alter().
contact_attach_help Implements hook_help().
contact_attach_mail_alter Implements hook_mail_alter().
contact_attach_menu Implements hook_menu().
contact_attach_permission Implements hook_permission().
_contact_attach_add_attachment Returns a fully-encoded attachment ready to be included into a message body.
_contact_attach_get_valid_roles Gets active user's valid roles to be considered in aggregation of settings.
_contact_attach_process_attachments Checks for attachments and processes them, if one or more exist.
_contact_attach_return_allowed_extensions Returns allowed extensions for attachments based on all supplied roles.
_contact_attach_return_max_attachments Returns maximum number of attachments allowed based on all supplied roles.
_contact_attach_return_max_file_size Returns the allowed file size for attachments based on all supplied roles.

Constants

Namesort descending Description
CONTACT_ATTACH_DEFAULT_EXTENSIONS Default attachment extensions that will be permitted.
CONTACT_ATTACH_DEFAULT_NUMBER Default number of attachments on contact forms.
CONTACT_ATTACH_DEFAULT_UPLOADSIZE Default maximum attachment size that will be permitted, in megabytes.