You are here

webform.module in Webform 5.2

File

webform.module
View source
<?php

/**
 * This module provides a simple way to create forms and questionnaires.
 *
 * The initial development of this module was sponsered by ÅF Industri AB, Open
 * Source City and Karlstad University Library. Continued development sponsored
 * by Lullabot.
 *
 * @author Nathan Haug <nate@lullabot.com>
 * @author Pontus Ullgren <ullgren@user.sourceforge.net>
 */

/**
 * Implementation of hook_help().
 */
function webform_help($section = 'admin/help#webform') {
  $output = '';
  switch ($section) {
    case 'admin/settings/webform':
      $output = t('Webforms are forms and questionnaires. To add one, select <a href="!url">Create content -&gt; Webform</a>.', array(
        '!url' => url('node/add/webform'),
      ));
      break;
    case 'admin/help#webform':
      $output = t("<p>This module lets you create forms or questionnaires and define their content. Submissions from these forms are stored in the database and optionally also sent by e-mail to a predefined address.</p>\n      <p>Here is how to create one:</p>\n      <ul>\n       <li>Go to Create Content and add a webform</li>\n       <li>Add a description to be displayed as a teaser and above the actual form.</li>\n       <li>Add a confirmation message or redirect node that is to be displayed after successful submission.</li>\n       <li>Add one or more components to your form.</li>\n       <li>Optionally add an e-mail address to which submissions will be sent. If no email address is specified, no e-mail will be sent when submissions are made through the form.</li>\n       <li>Optionally select an e-mail (or hidden) component that will be used to populate the return e-mail address on any sent e-mail.</li>\n       <li>Optionally select a textfield (or hidden) component that will be used to populate the subject e-mail field on any sent e-mail.</li>\n      </ul>\n      <p>Help on adding and configuring the components will be shown after you add your first component.</p>\n      <p>The content of submitted forms is stored in the database table <i>webform_submitted_data</i> as key-value pairs.</p>\n      ");
      break;
    case 'node/add#webform':
      $output = t('A webform can be a questionnaires, contact or request forms. It can be used to let visitors make contact, register for a event or to enable a complex survey.');
      break;
  }
  if (strstr($section, 'admin/settings/webform#')) {

    // Call help hooks in plugins:
    $components = webform_load_components(TRUE);
    foreach ($components as $key => $component) {
      $help_function = '_webform_help_' . $key;
      if (function_exists($help_function)) {
        $output .= $help_function($section);
      }
    }
  }

  // Node form help.
  if (arg(0) == 'node' && is_numeric(arg(1))) {
    if ($section == 'node/' . arg(1) . '/edit/components') {
      $output .= '<p>' . t('This page displays all the components currently configured for this webform node. You may add any number of components to the form, even multiple of the same type. To add a new component, fill in a name and select a type from the fields at the bottom of the table. Submit the form to create the new component or update any changed form values.') . '</p>';
      $output .= '<p>' . t('Click on any existing component\'s name to edit its settings.') . '</p>';
    }
  }
  return $output;
}

/**
 * Implementation of hook_menu().
 */
function webform_menu($may_cache) {
  global $user;
  $items = array();
  if ($may_cache) {

    // Submissions listing.
    $items[] = array(
      'path' => 'admin/content/webform',
      'title' => t('Webforms'),
      'callback' => 'webform_admin_content',
      'access' => user_access('access webform results'),
      'description' => t('View and edit all the available webforms on your site.'),
      'type' => MENU_NORMAL_ITEM,
    );

    // Admin Settings.
    $items[] = array(
      'path' => 'admin/settings/webform',
      'title' => t('Webform'),
      'callback' => 'drupal_get_form',
      'callback arguments' => 'webform_admin_settings',
      'access' => user_access('administer site configuration'),
      'description' => t('Global configuration of webform functionality.'),
      'type' => MENU_NORMAL_ITEM,
    );
    return $items;
  }
  elseif (arg(0) == 'node' && is_numeric(arg(1))) {
    $nid = arg(1);
    $node = node_load($nid);
    $sid = arg(2) == 'submission' && is_numeric(arg(3)) ? arg(3) : NULL;
    if ($node->nid && $node->type == 'webform') {
      $items[] = array(
        'path' => 'node/' . $nid,
        'title' => $node->title,
        // Set the title for the node menu item.
        'callback' => 'node_page_view',
        'callback arguments' => array(
          $node,
        ),
        'access' => node_access('view', $node),
        'type' => MENU_CALLBACK,
      );
      $items[] = array(
        'path' => 'node/' . $nid . '/done',
        'title' => t('Webform confirmation'),
        'callback' => '_webform_confirmation',
        'callback arguments' => array(
          $node,
        ),
        'access' => node_access('view', $node),
        'type' => MENU_CALLBACK,
      );
      $items[] = array(
        'path' => 'node/' . $nid . '/edit/configuration',
        'title' => t('Configuration'),
        'callback' => 'node_page_edit',
        'callback arguments' => array(
          $node,
        ),
        'access' => node_access('update', $node),
        'weight' => 1,
        'type' => MENU_DEFAULT_LOCAL_TASK,
      );
      $items[] = array(
        'path' => 'node/' . $nid . '/edit/components',
        'title' => t('Form components'),
        'callback' => 'webform_components',
        'callback arguments' => array(
          $node,
        ),
        'access' => node_access('update', $node),
        'weight' => 2,
        'type' => MENU_LOCAL_TASK,
      );
      $items[] = array(
        'path' => 'node/' . $nid . '/results',
        'title' => t('Results'),
        'callback' => 'webform_results',
        'callback arguments' => array(
          $node,
        ),
        'access' => webform_results_access($node, 'access webform results'),
        'weight' => 2,
        'type' => MENU_LOCAL_TASK,
      );
      $items[] = array(
        'path' => 'node/' . $nid . '/results/submissions',
        'title' => t('Submissions'),
        'callback' => 'webform_results',
        'callback arguments' => array(
          $node,
        ),
        'access' => webform_results_access($node, 'access webform results'),
        'weight' => 4,
        'type' => MENU_DEFAULT_LOCAL_TASK,
      );
      $items[] = array(
        'path' => 'node/' . $nid . '/results/analysis',
        'title' => t('Analysis'),
        'callback' => 'webform_results',
        'callback arguments' => array(
          $node,
          'analysis',
        ),
        'access' => webform_results_access($node, 'access webform results'),
        'weight' => 5,
        'type' => MENU_LOCAL_TASK,
      );
      $items[] = array(
        'path' => 'node/' . $nid . '/results/table',
        'title' => t('Table'),
        'callback' => 'webform_results',
        'callback arguments' => array(
          $node,
          'table',
        ),
        'access' => webform_results_access($node, 'access webform results'),
        'weight' => 6,
        'type' => MENU_LOCAL_TASK,
      );
      $items[] = array(
        'path' => 'node/' . $nid . '/results/download',
        'title' => t('Download'),
        'callback' => 'webform_results',
        'callback arguments' => array(
          $node,
          'download',
        ),
        'access' => webform_results_access($node, 'access webform results'),
        'weight' => 7,
        'type' => MENU_LOCAL_TASK,
      );
      $items[] = array(
        'path' => 'node/' . $nid . '/results/clear',
        'title' => t('Clear'),
        'callback' => 'webform_results',
        'callback arguments' => array(
          $node,
          'clear',
        ),
        'access' => webform_results_access($node, 'clear webform results'),
        'weight' => 8,
        'type' => MENU_LOCAL_TASK,
      );
      $items[] = array(
        'path' => 'node/' . $nid . '/submissions',
        'title' => t('Submissions'),
        'callback' => 'webform_results',
        'callback arguments' => array(
          $node,
          'user_submissions',
        ),
        'access' => user_access('access webform results') || user_access('access webform submissions') || user_access('access own webform submissions') && $user->uid,
        'weight' => 2,
        'type' => MENU_CALLBACK,
      );
      if (isset($sid)) {
        include_once drupal_get_path('module', 'webform') . "/webform_submissions.inc";
        $submission = webform_get_submission($node->nid, $sid);
        $items[] = array(
          'path' => 'node/' . $nid . '/submission/' . $sid,
          'title' => t('Webform submission'),
          'callback' => 'drupal_get_form',
          'callback arguments' => array(
            'webform_client_form_' . $node->nid,
            $node,
            $submission,
            FALSE,
            FALSE,
          ),
          'access' => webform_submission_access($node, $submission, 'view'),
          'type' => MENU_CALLBACK,
        );
        $items[] = array(
          'path' => 'node/' . $nid . '/submission/' . $sid . '/view',
          'title' => t('View'),
          'callback' => 'drupal_get_form',
          'callback arguments' => array(
            'webform_client_form_' . $node->nid,
            $node,
            $submission,
            FALSE,
            FALSE,
          ),
          'access' => webform_submission_access($node, $submission, 'view'),
          'weight' => 0,
          'type' => MENU_DEFAULT_LOCAL_TASK,
        );
        $items[] = array(
          'path' => 'node/' . $nid . '/submission/' . $sid . '/edit',
          'title' => t('Edit'),
          'callback' => 'drupal_get_form',
          'callback arguments' => array(
            'webform_client_form_' . $node->nid,
            $node,
            $submission,
            TRUE,
            FALSE,
          ),
          'access' => webform_submission_access($node, $submission, 'edit'),
          'weight' => 1,
          'type' => MENU_LOCAL_TASK,
        );
        $items[] = array(
          'path' => 'node/' . $nid . '/submission/' . $sid . '/delete',
          'title' => t('Delete'),
          'callback' => 'drupal_get_form',
          'callback arguments' => array(
            'webform_submission_delete_form',
            $node,
            $submission,
          ),
          'access' => webform_submission_access($node, $submission, 'delete'),
          'weight' => 2,
          'type' => MENU_LOCAL_TASK,
        );
      }
    }
  }
  return $items;
}

/**
 * Menu access callback. Ensure a user both access and node 'view' permission.
 */
function webform_results_access($node, $perm) {
  return node_access('view', $node) && user_access($perm);
}

/**
 * Implementation of hook_perm().
 */
function webform_perm() {
  return array(
    'create webforms',
    'edit own webforms',
    'edit webforms',
    'access webform results',
    'clear webform results',
    'access own webform submissions',
    'edit own webform submissions',
    'edit webform submissions',
    'use PHP for additional processing',
  );
}

/**
 * Implementation of hook_node_info().
 */
function webform_node_info() {
  return array(
    'webform' => array(
      'name' => t('Webform'),
      'module' => 'webform',
      'description' => t('Create a new form or questionnaire accessible to users. Submission results and statistics are recorded and accessible to privileged users.'),
    ),
  );
}

/**
 * Implementation of hook_access().
 */
function webform_access($op, $node) {
  global $user;
  switch ($op) {
    case 'create':
      return user_access('create webforms');
    case 'update':
    case 'delete':
      if (user_access('edit webforms') || user_access('edit own webforms') && $user->uid == $node->uid) {
        return TRUE;
      }
  }
}

/**
 * Implementation of hook_forms().
 * All webform_client_form forms share the same form handler
 */
function webform_forms($args) {
  $form_id = $args[0];
  if (strpos($form_id, 'webform_client_form_') === 0) {
    $forms[$form_id]['callback'] = 'webform_client_form';
  }
  return $forms;
}

/**
 * Implementation of hook_file_download().
 *
 * Only allow users with view webform submissions to download files.
 */
function webform_file_download($file) {
  $file = file_check_location(file_directory_path() . '/' . $file, file_directory_path() . '/webform/');
  if ($file && user_access('access webform results')) {
    $info = image_get_info(file_create_path($file));
    if (isset($info['mime_type'])) {
      $headers = array(
        'Content-type: ' . $info['mime_type'],
      );
    }
    else {
      $headers = array(
        'Content-type: force-download',
        'Content-disposition: attachment',
      );
    }
    return $headers;
  }
}

/**
 * Implementation of hook_insert().
 */
function webform_insert($node) {
  include_once drupal_get_path('module', 'webform') . '/webform_components.inc';

  // Insert the Webform.
  db_query("INSERT INTO {webform} (nid, confirmation, teaser, submit_text, submit_limit, submit_interval, email, email_from_name, email_from_address, email_subject, additional_validate, additional_submit) VALUES (%d, '%s', %d, '%s', %d, %d, '%s', '%s', '%s', '%s', '%s', '%s')", $node->nid, $node->webform['confirmation'], $node->webform['teaser'], $node->webform['submit_text'], $node->webform['submit_limit'], $node->webform['submit_interval'], $node->webform['email'], $node->webform['email_from_name'], $node->webform['email_from_address'], $node->webform['email_subject'], $node->webform['additional_validate'], $node->webform['additional_submit']);

  // Insert the components into the database. Used with clone.module.
  if (isset($node->webform['components']) && !empty($node->webform['components'])) {
    foreach ($node->webform['components'] as $cid => $component) {
      $component['nid'] = $node->nid;
      webform_component_insert($component);
    }
  }

  // Set the per-role submission access control.
  foreach ($node->webform['roles'] as $rid) {
    db_query('INSERT INTO {webform_roles} (nid, rid) VALUES (%d, %d)', $node->nid, $rid);
  }
}

/**
 * Implementation of hook_update().
 */
function webform_update($node) {

  // Update the webform by deleting existing data and replacing with the new.
  db_query('DELETE FROM {webform} WHERE nid = %d', $node->nid);
  db_query('DELETE FROM {webform_component} WHERE nid = %d', $node->nid);
  db_query('DELETE FROM {webform_roles} WHERE nid = %d', $node->nid);
  webform_insert($node);
}

/**
 * Implementation of hook_delete().
 */
function webform_delete(&$node) {

  // Allow components clean up extra data, such as uploaded files.
  include_once drupal_get_path('module', 'webform') . '/webform_components.inc';
  foreach ($node->webform['components'] as $cid => $component) {
    webform_component_delete($node->nid, $cid);
  }

  // Remove any trace of webform data from the database.
  db_query('DELETE FROM {webform} WHERE nid = %d', $node->nid);
  db_query('DELETE FROM {webform_component} WHERE nid = %d', $node->nid);
  db_query('DELETE FROM {webform_roles} WHERE nid = %d', $node->nid);
  db_query('DELETE FROM {webform_submissions} WHERE nid = %d', $node->nid);
  db_query('DELETE FROM {webform_submitted_data} WHERE nid = %d', $node->nid);
}

/**
 * Implementation of hook_load().
 */
function webform_load($node) {
  include_once drupal_get_path('module', 'webform') . '/webform_components.inc';
  $additions = new stdClass();
  if ($webform = db_fetch_array(db_query('SELECT * FROM {webform} WHERE nid = %d', $node->nid))) {
    $additions->webform = $webform;
    $additions->webform['roles'] = array();
    $result = db_query('SELECT rid FROM {webform_roles} WHERE nid = %d', $node->nid);
    while ($role = db_fetch_object($result)) {
      $additions->webform['roles'][] = $role->rid;
    }
  }
  else {
    $additions->webform = array(
      'confirmation' => '',
      'teaser' => 0,
      'submit_text' => '',
      'submit_limit' => -1,
      'submit_interval' => -1,
      'email' => '',
      'email_from_name' => 'default',
      'email_from_address' => 'default',
      'email_subject' => 'default',
      'additional_validate' => '',
      'additional_submit' => '',
      'roles' => array(
        1,
        2,
      ),
    );
  }
  $additions->webform['components'] = array();
  $additions->webform['additional_emails'] = array();
  $result = db_query('SELECT * FROM {webform_component} WHERE nid = %d ORDER BY weight, name', $node->nid);
  while ($c = db_fetch_array($result)) {
    $component =& $additions->webform['components'][$c['cid']];
    $component['nid'] = $node->nid;
    $component['cid'] = $c['cid'];
    $component['form_key'] = $c['form_key'] ? $c['form_key'] : $c['cid'];
    $component['name'] = t($c['name']);
    $component['type'] = $c['type'];
    $component['value'] = $c['value'];
    $component['extra'] = unserialize($c['extra']);
    $component['mandatory'] = $c['mandatory'];
    $component['email'] = $c['email'];
    $component['pid'] = $c['pid'];
    $component['weight'] = $c['weight'];
    if (isset($component['extra']['email']) && $component['extra']['email']) {
      $additions->webform['additional_emails'][$c['cid']] = $c['cid'];
    }
    webform_component_defaults($component);
  }

  // Organize the components into a fieldset-based order.
  if (!empty($additions->webform['components'])) {
    $component_tree = array();
    $page_count = 1;
    _webform_components_tree_build($additions->webform['components'], $component_tree, 0, $page_count);
    $additions->webform['components'] = _webform_components_tree_flatten($component_tree['children']);
  }
  return $additions;
}

/**
 * Implementation of hook_link().
 * Always add a "view form" link.
 */
function webform_link($type, $node = NULL, $teaser = FALSE) {
  $links = array();
  if (isset($node->type) && $node->type === 'webform') {
    if ($teaser && !$node->webform['teaser']) {
      $links['webform_goto'] = array(
        'title' => t('Go to form'),
        'href' => 'node/' . $node->nid,
        'attributes' => array(
          'title' => t('View this form.'),
          'class' => 'read-more',
        ),
      );
    }
  }
  return $links;
}

/**
 * Implementation of hook_form().
 * Creates the standard form for editing or creating a webform.
 */
function webform_form(&$node, &$param) {
  $form['webform'] = array(
    '#type' => 'markup',
    '#tree' => TRUE,
  );

  // Set node defaults if empty.
  if (!isset($node->nid) && !isset($node->webform)) {
    $node->nid = 0;
    $additions = webform_load($node);
    $node->webform = $additions->webform;
    $node->nid = NULL;
  }

  /* Save Components in a value (helps with clone.module) */
  $form['webform']['components'] = array(
    '#type' => 'value',
    '#value' => $node->webform['components'],
  );

  /* Start Edit Form */
  $form['webform']['settings'] = array(
    '#type' => 'fieldset',
    '#title' => t('Webform Settings'),
    '#collapsible' => TRUE,
    '#collapsed' => FALSE,
    '#weight' => -4,
    '#parents' => array(
      'webform',
    ),
  );
  $form['webform']['settings']['title'] = array(
    '#type' => 'textfield',
    '#title' => t('Title'),
    '#default_value' => $node->title,
    '#maxlength' => 128,
    '#required' => TRUE,
    '#tree' => FALSE,
  );
  $form['webform']['settings']['body'] = array(
    '#type' => 'textarea',
    '#title' => t('Description'),
    '#description' => t('Text to be shown as teaser and before the form.'),
    '#default_value' => $node->body,
    '#cols' => 40,
    '#rows' => 10,
    '#tree' => FALSE,
  );
  $form['webform']['settings']['confirmation'] = array(
    '#type' => 'textarea',
    '#title' => t('Confirmation message or redirect URL'),
    '#description' => t('Message to be shown upon successful submission or a path to a redirect page. Preface message with <em>message:</em> for a simple message that does not require a page refresh. Redirect pages must start with <em>http://</em> for external sites or <em>internal:</em> for an internal path. i.e. <em>http://www.example.com</em> or <em>internal:node/10</em>'),
    '#default_value' => $node->webform['confirmation'],
    '#cols' => 40,
    '#rows' => 10,
  );
  $form['webform']['settings']['format'] = filter_form($node->format);

  /* End Edit Form */

  /* Start per-role submission control */
  $form['webform']['role_control'] = array(
    '#type' => 'fieldset',
    '#title' => t('Webform access control'),
    '#collapsible' => TRUE,
    '#collapsed' => FALSE,
    '#weight' => -3,
    '#parents' => array(
      'webform',
    ),
    '#description' => t('These permissions affect which roles can submit this webform. It does not prevent access to the webform page. If needing to prevent access to the webform page entirely, use a content access module such as <a href="http://drupal.org/project/taxonomy_access">Taxonomy Access</a> or <a href="http://drupal.org/project/node_privacy_byrole">Node Privacy by Role</a>.'),
    '#access' => variable_get('webform_submission_access_control', 1),
  );
  $user_roles = user_roles();
  $form['webform']['role_control']['roles'] = array(
    '#default_value' => $node->webform['roles'],
    '#options' => $user_roles,
    '#type' => 'checkboxes',
    '#title' => t('Roles that can submit this webform'),
    '#description' => t('Uncheck all roles to prevent new submissions. The %authenticated role applies to any user signed into the site, regardless of other assigned roles.', array(
      '%authenticated' => $user_roles[2],
    )),
  );

  /* End per-role submission control */

  /* Start E-mail Settings Form */
  $form['webform']['mail_settings'] = array(
    '#type' => 'fieldset',
    '#title' => t('Webform mail settings'),
    '#collapsible' => TRUE,
    '#collapsed' => FALSE,
    '#weight' => -2,
    '#parents' => array(
      'webform',
    ),
    '#theme' => 'webform_mail_settings_form',
  );
  $form['webform']['mail_settings']['email'] = array(
    '#type' => 'textfield',
    '#title' => t('E-mail to address'),
    '#maxlength' => 255,
    '#default_value' => $node->webform['email'],
    '#description' => t('Form submissions will be e-mailed to this address. Leave blank for none. Multiple e-mail addresses may be separated by commas.'),
  );
  $form['webform']['mail_settings']['email_components'] = array(
    '#type' => 'fieldset',
    '#collapsible' => TRUE,
    '#collapsed' => TRUE,
    '#theme' => 'webform_mail_components_form',
    '#title' => t('Conditional e-mail recipients'),
    '#description' => t('The settings below allow you to send e-mails to multiple recipients based off the value of a component.'),
    '#node' => $node,
  );
  $options = _webform_component_options($node->webform['components'], 'email');
  $default_value = array();
  if (is_array($node->webform['components'])) {
    foreach ($node->webform['components'] as $cid => $component) {
      if (isset($component['extra']['email']) && $component['extra']['email']) {
        $default_value[] = $cid;
      }
    }
  }
  $form['webform']['mail_settings']['email_components']['email_components'] = array(
    '#type' => 'checkboxes',
    '#options' => $options,
    '#default_value' => $default_value,
    '#parents' => array(
      'webform',
      'email_components',
    ),
  );
  foreach (array(
    'from_name',
    'from_address',
    'subject',
  ) as $field) {
    switch ($field) {
      case 'from_name':
        $default_value = webform_variable_get('webform_default_from_name');
        $title = t('E-mail from name');
        $description = t('After adding components to this form any email, select, or hidden form element may be selected as the sender\'s name for e-mails.');
        break;
      case 'from_address':
        $default_value = webform_variable_get('webform_default_from_address');
        $title = t('E-mail from address');
        $description = t('After adding components to this form any textfield, select, or hidden form element may be selected as the sender\'s e-mail address.');
        break;
      case 'subject':
        $default_value = webform_variable_get('webform_default_subject');
        $title = t('E-mail subject');
        $description = t('After adding components to this form any textfield, select, or hidden form element may be selected as the subject for e-mails.');
        break;
    }
    $form['webform']['mail_settings']['email_' . $field . '_option'] = array(
      '#title' => $title,
      '#type' => 'radios',
      '#default_value' => is_numeric($node->webform['email_' . $field]) ? 'component' : (empty($default_value) || $node->webform['email_' . $field] != 'default' && isset($node->webform['email_' . $field]) ? 'custom' : 'default'),
      '#description' => $description,
    );
    if (!empty($default_value)) {
      $form['webform']['mail_settings']['email_' . $field . '_option']['#options']['default'] = $default_value;
    }
    $form['webform']['mail_settings']['email_' . $field . '_option']['#options']['custom'] = 'custom';
    $form['webform']['mail_settings']['email_' . $field . '_option']['#options']['component'] = 'component';
    $form['webform']['mail_settings']['email_' . $field . '_custom'] = array(
      '#type' => 'textfield',
      '#size' => 40,
      '#default_value' => !is_numeric($node->webform['email_' . $field]) && $node->webform['email_' . $field] != 'default' ? $node->webform['email_' . $field] : NULL,
    );
    $options = _webform_component_options($node->webform['components'], $field == 'from_address' ? 'email' : 'string');
    $form['webform']['mail_settings']['email_' . $field . '_component'] = array(
      '#type' => 'select',
      '#default_value' => is_numeric($node->webform['email_' . $field]) ? $node->webform['email_' . $field] : NULL,
      '#options' => empty($options) ? array(
        '' => 'No available components',
      ) : $options,
      '#disabled' => empty($options) ? TRUE : FALSE,
      '#weight' => 6,
    );
  }

  /* End mail settings form */

  /* Start advanced settings form */
  $form['webform']['advanced'] = array(
    '#type' => 'fieldset',
    '#title' => t('Webform advanced settings'),
    '#collapsible' => TRUE,
    '#collapsed' => TRUE,
    '#weight' => -1,
    '#parents' => array(
      'webform',
    ),
  );
  $form['webform']['advanced']['teaser'] = array(
    '#type' => 'checkbox',
    '#title' => t('Show complete form in teaser'),
    '#default_value' => $node->webform['teaser'],
    '#description' => t('Display the entire form in the teaser display of this node.'),
  );
  $form['webform']['advanced']['submit_limit'] = array(
    '#type' => 'fieldset',
    '#title' => t('Limit the number of submissions a user may send within a specified time period'),
    '#theme' => 'webform_advanced_submit_limit_form',
  );
  $form['webform']['advanced']['submit_limit']['enforce_limit'] = array(
    '#type' => 'radios',
    '#options' => array(
      'no' => t('Unlimited'),
      'yes' => 'Limit to !count submission(s) !timespan',
    ),
    '#default_value' => $node->webform['submit_limit'] == -1 ? 'no' : 'yes',
    '#parents' => array(
      'webform',
      'enforce_limit',
    ),
  );
  $form['webform']['advanced']['submit_limit']['submit_limit'] = array(
    '#type' => 'textfield',
    '#maxlength' => 2,
    '#size' => 2,
    '#default_value' => $node->webform['submit_limit'] != -1 ? $node->webform['submit_limit'] : '',
    '#parents' => array(
      'webform',
      'submit_limit',
    ),
  );
  $form['webform']['advanced']['submit_limit']['submit_interval'] = array(
    '#type' => 'select',
    '#options' => array(
      '-1' => t('ever'),
      '1600' => t('every hour'),
      '86400' => t('every day'),
      '604800' => t('every week'),
    ),
    '#default_value' => $node->webform['submit_interval'],
    '#parents' => array(
      'webform',
      'submit_interval',
    ),
  );
  $form['webform']['advanced']['submit_text'] = array(
    '#type' => 'textfield',
    '#title' => t('Submit button text'),
    '#default_value' => $node->webform['submit_text'],
    '#description' => t('By default the submit button on this form will have the label <em>Submit</em>. Enter a new title here to override the default.'),
  );
  if (user_access('use PHP for additional processing')) {
    $form['webform']['advanced']['additional_validate'] = array(
      '#type' => 'textarea',
      '#title' => t('Additional Validation'),
      '#description' => t('Enter PHP code to perform additional validation for this form. Include the &lt;?php ?&gt; tags. $form_id and $form_values are available variables. If validation fails, use the form_set_error function to prevent the form from being submitted. Use the same syntax as a _validate function used in the <a href="http://api.drupal.org/api/file/developer/topics/forms_api.html">Forms API</a>.'),
      '#default_value' => $node->webform['additional_validate'],
      '#cols' => 40,
      '#rows' => 10,
    );
    $form['webform']['advanced']['additional_submit'] = array(
      '#type' => 'textarea',
      '#title' => t('Additional Processing'),
      '#description' => t('Enter PHP code to perform additional processing for this form (after the validation). Include the &lt;?php ?&gt; tags. $form_id and $form_values are available variables, use the same syntax as a _submit function used in the <a href="http://api.drupal.org/api/file/developer/topics/forms_api.html">Forms API</a>.'),
      '#default_value' => $node->webform['additional_submit'],
      '#cols' => 40,
      '#rows' => 10,
    );
  }
  else {
    $form['webform']['advanced']['additional_validate'] = array(
      '#type' => 'value',
      '#value' => $node->webform['additional_validate'],
    );
    $form['webform']['advanced']['additional_submit'] = array(
      '#type' => 'value',
      '#value' => $node->webform['additional_submit'],
    );
  }

  /* End Advanced Settings Form */
  return $form;
}

/**
 * Theme the component options for sending e-mails.
 */
function theme_webform_mail_components_form($form) {
  drupal_add_css(drupal_get_path('module', 'webform') . '/webform.css');
  $node = $form['#node'];
  $header = array(
    array(
      'data' => t('To'),
      'class' => 'webform-checkbox',
    ),
    t('Name'),
    t('Type'),
  );
  $rows = array();
  foreach (element_children($form['email_components']) as $cid) {
    $title = $form['email_components'][$cid]['#title'];
    unset($form['email_components'][$cid]['#title']);
    $rows[] = array(
      array(
        'data' => drupal_render($form['email_components'][$cid]),
        'class' => 'webform-checkbox',
      ),
      $title,
      $node->webform['components'][$cid]['type'],
    );
  }
  if (empty($rows)) {
    $rows[] = array(
      array(
        'colspan' => 6,
        'data' => t('No components yet in this webform.'),
      ),
    );
  }
  $form['#children'] = theme('table', $header, $rows);
  return drupal_render($form);
}

/**
 * Theme the Webform mail settings section of the node form.
 */
function theme_webform_mail_settings_form($form) {
  drupal_add_js(drupal_get_path('module', 'webform') . '/webform.js');

  // Loop through fields, rendering them into radio button options.
  foreach (array(
    'from_name',
    'from_address',
    'subject',
  ) as $field) {
    foreach (array(
      'custom' => t('Custom'),
      'component' => t('Component'),
    ) as $option => $title) {
      $form['email_' . $field . '_' . $option]['#attributes']['class'] = 'webform-set-active';
      $form['email_' . $field . '_option'][$option]['#title'] = $title . ': ' . drupal_render($form['email_' . $field . '_' . $option]);
    }

    // For spacing consitency, every option is wrapped in container-inline.
    foreach (element_children($form['email_' . $field . '_option']) as $option) {
      $form['email_' . $field . '_option'][$option]['#prefix'] = '<div class="container-inline">';
      $form['email_' . $field . '_option'][$option]['#suffix'] = '</div>';
    }

    // Wrap the default option in a placeholder tag..
    if (isset($form['email_' . $field . '_option']['#options']['default'])) {
      $form['email_' . $field . '_option']['default']['#title'] = t('Default') . ': ' . theme('placeholder', $form['email_' . $field . '_option']['default']['#title']);
    }
  }
  return drupal_render($form);
}

/**
 * Theme the submit limit fieldset on the webform node form.
 */
function theme_webform_advanced_submit_limit_form($form) {
  $form['submit_limit']['#attributes']['class'] = 'webform-set-active';
  $form['submit_interval']['#attributes']['class'] = 'webform-set-active';
  $replacements = array(
    '!count' => drupal_render($form['submit_limit']),
    '!timespan' => drupal_render($form['submit_interval']),
  );
  $form['enforce_limit']['no']['#prefix'] = '<div class="container-inline">';
  $form['enforce_limit']['no']['#suffix'] = '</div>';
  $form['enforce_limit']['yes']['#prefix'] = '<div class="container-inline">';
  $form['enforce_limit']['yes']['#suffix'] = '</div>';
  $form['enforce_limit']['yes']['#title'] = t('Limit to !count submission(s) !timespan', $replacements);
  return drupal_render($form);
}

/**
 * Implementation of hook_validate().
 */
function webform_validate(&$node) {

  // Ensure the entered e-mail addresses are valid.
  if (!empty($node->webform['email'])) {
    $emails = explode(',', $node->webform['email']);
    foreach ($emails as $email) {
      if (!valid_email_address(trim($email))) {
        form_set_error('webform][email', t('The entered email address %address is not a valid address.', array(
          '%address' => $email,
        )));
        break;
      }
    }
  }
  if ($node->webform['email_from_address_option'] == 'custom') {
    if (!valid_email_address($node->webform['email_from_address_custom'])) {
      form_set_error('webform][email_from_address_custom', t('The entered email address %address is not a valid address.', array(
        '%address' => $node->webform['email_from_address_custom'],
      )));
    }
  }
}

/**
 * Implementation of hook_submit().
 */
function webform_submit(&$node) {

  // Add the conditional e-mail recipients to components.
  if ($node->nid) {
    $original_node = node_load($node->nid);
    foreach ($original_node->webform['components'] as $cid => $component) {
      if (!isset($node->webform['components'][$cid])) {
        $node->webform['components'][$cid] = $component;
      }
      if (isset($node->webform['email_components'][$cid])) {
        $node->webform['components'][$cid]['extra']['email'] = $node->webform['email_components'][$cid];
      }
    }
  }
  unset($node->webform['email_components']);

  // Merge the e-mail name, address, and subject options into single values.
  foreach (array(
    'from_name',
    'from_address',
    'subject',
  ) as $field) {
    $option = $node->webform['email_' . $field . '_option'];
    if ($option == 'default') {
      $node->webform['email_' . $field] = 'default';
    }
    else {
      $node->webform['email_' . $field] = $node->webform['email_' . $field . '_' . $option];
    }
    unset($node->webform['email_' . $field . '_option']);
    unset($node->webform['email_' . $field . '_component']);
    unset($node->webform['email_' . $field . '_custom']);
  }

  // Set the submit limit to -1 if set to unlimited.
  if ($node->webform['enforce_limit'] == 'no') {
    $node->webform['submit_limit'] = -1;
    $node->webform['submit_interval'] = -1;
  }
  unset($node->webform['enforce_limit']);

  // Save roles.
  $node->webform['roles'] = array_keys(array_filter($node->webform['roles']));
}

/**
 * Implementation of hook_form_alter().
 */
function webform_form_alter($form_id, &$form) {
  if ($form_id == 'webform_node_form' && empty($form['nid']['#value'])) {
    $form['#submit']['webform_form_submit'] = array();

    // Force webforms to be unpublished initially.
    if (user_access('administer nodes')) {
      $form['options']['status']['#default_value'] = FALSE;
    }
    else {
      $form['status']['#value'] = FALSE;
    }
  }
}

/**
 * Submit handler for the webform node form.
 *
 * Redirect the user to the components form on new node inserts. Note that this
 * fires after the hook_submit() function above.
 */
function webform_form_submit($form_id, $form_values) {

  // There should be a more effective way to find the new node ID.
  $nid = db_result(db_query_range("SELECT nid FROM {node} WHERE type = 'webform' ORDER BY nid DESC", 0, 1));

  // Remove the submitted message added by node module.
  unset($_SESSION['messages']['status']);
  drupal_set_message(t('The new webform %title has been created. Add new fields to your webform with the form below.', array(
    '%title' => $form_values['title'],
  )));
  if (!$form_values['status']) {
    drupal_set_message(t('This webform is currently unpublished. After finishing your changes to the webform, use the <em>Publish</em> button below.'));
  }
  return 'node/' . $nid . '/edit/components';
}

/**
 * Implementation of hook_view().
 */
function webform_view(&$node, $teaser = 0, $page = 0) {
  global $user;

  // If a teaser, do not display the form.
  if ($teaser && !$node->webform['teaser']) {
    $node->content['teaser'] = array(
      '#value' => check_markup($node->teaser, $node->format, FALSE),
    );
    return $node;
  }
  $submission = array();
  $submission_count = 0;
  $enabled = TRUE;
  $preview = FALSE;
  $logging_in = FALSE;
  $limit_exceeded = FALSE;
  if (isset($_POST['op']) && $_POST['op'] == t('Preview')) {
    $preview = TRUE;
    $additions = webform_load($node);
    $node->webform['components'] = $additions->webform['components'];
  }

  // When logging in using a form on the same page as a webform node, surpress
  // output messages so that they don't show up after the user has logged in.
  // See http://drupal.org/node/239343.
  if (isset($_POST['op']) && isset($_POST['name']) && isset($_POST['pass'])) {
    $logging_in = TRUE;
  }

  // Check if the user's role can submit this webform.
  if (variable_get('webform_submission_access_control', 1)) {
    $allowed_roles = array();
    foreach ($node->webform['roles'] as $rid) {
      $allowed_roles[$rid] = isset($user->roles[$rid]) ? TRUE : FALSE;
    }
    if (array_search(TRUE, $allowed_roles) === FALSE && $user->uid != 1) {
      $enabled = FALSE;
    }
  }
  else {

    // If not using Webform submission access control, allow for all roles.
    $allowed_roles = array_keys(user_roles());
  }

  // Check if the user can add another submission.
  if ($node->webform['submit_limit'] != -1) {

    // -1: Submissions are never throttled.
    include_once drupal_get_path('module', 'webform') . '/webform_submissions.inc';

    // Disable the form if the limit is exceeded and page cache is not active.
    if ($limit_exceeded = _webform_submission_limit_check($node) && ($user->uid != 0 || variable_get('cache', CACHE_DISABLED) == CACHE_DISABLED)) {
      $enabled = FALSE;
    }
  }

  // Get a count of previous submissions by this user.
  if ($user->uid && (user_access('access own webform submissions') || user_access('access webform results') || user_access('access webform submissions'))) {
    $submission_count = db_result(db_query('SELECT count(*) FROM {webform_submissions} WHERE nid = %d AND uid = %d', $node->nid, $user->uid));
  }

  // Render the form and generate the output.
  $form = drupal_get_form('webform_client_form_' . $node->nid, $node, $submission, $enabled, $preview);
  $output = theme('webform_view', $node, $teaser, $page, $form, $enabled);

  // Remove the surrounding <form> tag if this is a preview.
  if ($preview) {
    $output = preg_replace('/<\\/?form[^>]*>/', '', $output);
  }

  // Print out messages for the webform.
  if ($node->build_mode != NODE_BUILD_PREVIEW && !$logging_in && ($user->uid != 0 || !variable_get('cache', CACHE_DISABLED))) {
    theme('webform_view_messages', $node, $teaser, $page, $submission_count, $limit_exceeded, $allowed_roles);
  }

  // Add the output to the node.
  $node = node_prepare($node, $teaser);
  if (isset($output)) {
    $node->content['webform'] = array(
      '#value' => $output,
      '#weight' => 1,
    );
  }
  return $node;
}

/**
 * Output the Webform into the node content.
 *
 * @param $node
 *   The webform node object.
 * @param $teaser
 *   If this webform is being displayed as the teaser view of the node.
 * @param $page
 *   If this webform node is being viewed as the main content of the page.
 * @param $form
 *   The rendered form.
 * @param $enabled
 *   If the form allowed to be completed by the current user.
 */
function theme_webform_view($node, $teaser, $page, $form, $enabled) {

  // Only show the form if this user is allowed access.
  if ($enabled) {
    return $form;
  }
}

/**
 * Display a message to a user if they are not allowed to fill out a form.
 *
 * @param $node
 *   The webform node object.
 * @param $teaser
 *   If this webform is being displayed as the teaser view of the node.
 * @param $page
 *   If this webform node is being viewed as the main content of the page.
 * @param $submission_count
 *   The number of submissions this user has already submitted. Not calculated
 *   for anonymous users.
 * @param $limit_exceeded
 *   Boolean value if the submission limit for this user has been exceeded.
 * @param $allowed_roles
 *   A list of user roles that are allowed to submit this webform.
 */
function theme_webform_view_messages($node, $teaser, $page, $submission_count, $limit_exceeded, $allowed_roles) {
  global $user;
  $type = 'notice';

  // If not allowed to submit the form, give an explaination.
  if (array_search(TRUE, $allowed_roles) === FALSE && $user->uid != 1) {
    if (empty($allowed_roles)) {

      // No roles are allowed to submit the form.
      $message = t('Submissions for this form are closed.');
    }
    elseif (isset($allowed_roles[2])) {

      // The "authenticated user" role is allowed to submit and the user is currently logged-out.
      $login = url('user/login', drupal_get_destination());
      $register = url('user/register', drupal_get_destination());
      if (variable_get('user_register', 1) == 0) {
        $message = t('You must <a href="!login">login</a> to view this form.', array(
          '!login' => $login,
        ));
      }
      else {
        $message = t('You must <a href="!login">login</a> or <a href="!register">register</a> to view this form.', array(
          '!login' => $login,
          '!register' => $register,
        ));
      }
    }
    else {

      // The user must be some other role to submit.
      $message = t('You do not have permission to view this form.');
    }
  }
  elseif ($limit_exceeded) {
    if ($node->webform['submit_interval'] == -1 && $node->webform['submit_limit'] > 1) {
      $message = t('You have submitted this form the maximum number of times (@count).', array(
        '@count' => $node->webform['submit_limit'],
      ));
    }
    elseif ($node->webform['submit_interval'] == -1 && $node->webform['submit_limit'] == 1) {
      $message = t('You have already submitted this form.');
    }
    else {
      $message = t('You may not submit another entry at this time.');
    }
    $type = 'error';
  }

  // If the user has submitted before, give them a link to their submissions.
  if ($submission_count > 0) {
    if (empty($message)) {
      $message = t('You have already submitted this form.') . ' ' . t('<a href="!url">View your previous submissions</a>.', array(
        '!url' => url('node/' . $node->nid . '/submissions'),
      ));
    }
    else {
      $message .= ' ' . t('<a href="!url">View your previous submissions</a>.', array(
        '!url' => url('node/' . $node->nid . '/submissions'),
      ));
    }
  }
  if ($page && isset($message)) {
    drupal_set_message($message, $type);
  }
}

/**
 * Menu callback for admin/webform/settings.
 */
function webform_admin_settings() {
  include_once drupal_get_path('module', 'webform') . '/webform_export.inc';
  $form['components'] = array(
    '#type' => 'fieldset',
    '#title' => t('Available components'),
    '#collapsible' => TRUE,
    '#collapsed' => FALSE,
    '#description' => t('These are the available field types for your installation of Webform. You may disable any of these components by unchecking its corresponding box. Only checked components will be available in existing or new webforms.'),
  );

  // Add each component to the form:
  $component_types = webform_load_components(TRUE);
  foreach ($component_types as $key => $component) {
    $form['components']['webform_enable_' . $key] = array(
      '#title' => $component,
      '#description' => module_invoke('webform', 'help', 'admin/settings/webform#' . $key . '_description'),
      '#type' => 'checkbox',
      '#checked_value' => 1,
      '#default_value' => variable_get('webform_enable_' . $key, 1),
    );
  }
  $form['email'] = array(
    '#type' => 'fieldset',
    '#title' => t('Default e-mail values'),
    '#collapsible' => TRUE,
    '#collapsed' => FALSE,
  );
  $form['email']['webform_default_from_address'] = array(
    '#type' => 'textfield',
    '#title' => t('From address'),
    '#default_value' => variable_get('webform_default_from_address', variable_get('site_mail', ini_get('sendmail_from'))),
    '#description' => t('The default sender address for emailed webform results; often the e-mail address of the maintainer of your forms.'),
  );
  $form['email']['webform_default_from_name'] = array(
    '#type' => 'textfield',
    '#title' => t('From name'),
    '#default_value' => variable_get('webform_default_from_name', variable_get('site_name', '')),
    '#description' => t('The default sender name which is used along with the default from address.'),
  );
  $form['email']['webform_default_subject'] = array(
    '#type' => 'textfield',
    '#title' => t('Default subject'),
    '#default_value' => variable_get('webform_default_subject', t('Form submission from: %title')),
    '#description' => t('The default subject line of any e-mailed results.'),
  );
  $form['advanced'] = array(
    '#type' => 'fieldset',
    '#title' => t('Advanced options'),
    '#collapsible' => TRUE,
    '#collapsed' => TRUE,
  );
  $form['advanced']['webform_use_cookies'] = array(
    '#type' => 'checkbox',
    '#checked_value' => 1,
    '#title' => t('Allow cookies for tracking submissions'),
    '#default_value' => variable_get('webform_use_cookies', 0),
    '#description' => t('<a href="http://www.wikipedia.org/wiki/HTTP_cookie">Cookies</a> can be used to help prevent the same user from repeatedly submitting a webform. This feature is not needed for limiting submissions per user, though it can increase accuracy in some situations. Besides cookies, Webform also uses IP addresses and site usernames to prevent repeated submissions.'),
  );
  $form['advanced']['webform_export_format'] = array(
    '#type' => 'radios',
    '#title' => t('Default export format'),
    '#options' => webform_export_list(),
    '#default_value' => variable_get('webform_export_format', 'delimited'),
  );
  $form['advanced']['webform_csv_delimiter'] = array(
    '#type' => 'select',
    '#title' => t('Default export delimiter'),
    '#description' => t('This is the delimiter used in the CSV/TSV file when downloading Webform results. Using tabs in the export is the most reliable method for preserving non-latin characters. You may want to change this to another character depending on the program with which you anticipate importing results.'),
    '#default_value' => variable_get('webform_csv_delimiter', '\\t'),
    '#options' => array(
      ',' => t('Comma (,)'),
      '\\t' => t('Tab (\\t)'),
      ';' => t('Semicolon (;)'),
      ':' => t('Colon (:)'),
      '|' => t('Pipe (|)'),
      '.' => t('Period (.)'),
      ' ' => t('Space ( )'),
    ),
  );
  $form['advanced']['webform_submission_access_control'] = array(
    '#type' => 'radios',
    '#title' => t('Submission access control'),
    '#options' => array(
      '1' => t('Select the user roles that may submit each individual webform'),
      '0' => t('Disable Webform submission access control'),
    ),
    '#default_value' => variable_get('webform_submission_access_control', 1),
    '#description' => t('By default, the configuration form for each webform allows the administrator to choose which roles may submit the form. You may want to allow users to always submit the form if you are using a separate node access module to control access to webform nodes themselves.'),
  );
  $form['advanced']['webform_debug'] = array(
    '#type' => 'select',
    '#title' => t('Webforms debug'),
    '#default_value' => variable_get('webform_debug', 0),
    '#options' => array(
      0 => t('Off'),
      1 => t('Log submissions'),
      2 => t('Full debug'),
    ),
    '#description' => t('Set to "Log submissions" to log all submissions in the watchdog. Set to "Full debug" to print debug info on submission.'),
  );
  return system_settings_form($form);
}
function theme_webform_admin_settings($form) {

  // Format the components into a table.
  foreach (element_children($form['components']) as $key) {
    $row = array();
    $row[] = $form['components'][$key]['#title'];
    $row[] = $form['components'][$key]['#description'];
    unset($form['components'][$key]['#title']);
    unset($form['components'][$key]['#description']);
    $row[] = array(
      'data' => drupal_render($form['components'][$key]),
      'align' => 'center',
    );
    $rows[] = $row;
  }
  $header = array(
    t('Name'),
    t('Description'),
    t('Enabled'),
  );

  // Create the table inside the form.
  $form['components']['table'] = array(
    '#value' => theme('table', $header, $rows),
  );
  $output = drupal_render($form);
  return $output;
}

/**
 * Client form generation function. If this is displaying an existing
 * submission, pass in the $submission variable with the contents of the
 * submission to be displayed.
 *
 * @param $node
 *   The current webform node.
 * @param $submission
 *   An object containing information about the form submission if we're
 *   displaying a result.
 * @param $enabled
 *   If displaying a result, specify if form elements are enabled for
 *   editing.
 * @param $form_values
 *   The current form values of a submission, used in multipage webforms.
 *   Note: The position of this parameter depends on all other parameters being
 *   specified when using drupal_get_form().
 */
function webform_client_form($node, $submission, $enabled, $preview, $form_values = NULL) {
  include_once drupal_get_path('module', 'webform') . '/webform_components.inc';
  webform_load_components();
  if (isset($submission->sid)) {
    drupal_set_title(t('Submission #@sid', array(
      '@sid' => $submission->sid,
    )));
  }

  // Set a header for navigating results.
  if ($submission && user_access('access webform results')) {

    // Add CSS to display submission info. Don't preprocess because this CSS file is used rarely.
    drupal_add_css(drupal_get_path('module', 'webform') . '/webform.css', 'module', 'all', FALSE);
    $previous = db_result(db_query('SELECT MAX(sid) FROM {webform_submissions} WHERE nid = %d AND sid < %d', array(
      $node->nid,
      $submission->sid,
    )));
    $next = db_result(db_query('SELECT MIN(sid) FROM {webform_submissions} WHERE nid = %d AND sid > %d', array(
      $node->nid,
      $submission->sid,
    )));
    $form['submission'] = array(
      '#type' => 'value',
      '#value' => $submission,
    );
    $form['navigation'] = array(
      '#prefix' => '<div class="webform-submission-navigation">',
      '#suffix' => '</div>',
    );
    $form['navigation']['previous'] = array(
      '#value' => $previous ? l(t('Previous submission'), 'node/' . $node->nid . '/submission/' . $previous . ($enabled ? '/edit' : ''), array(
        'class' => 'webform-submission-previous',
      ), $enabled ? 'destination=node/' . $node->nid . '/submission/' . $previous . '/edit' : NULL) : '<span class="webform-submission-previous">' . t('Previous submission') . '</span>',
    );
    $form['navigation']['next'] = array(
      '#value' => $next ? l(t('Next submission'), 'node/' . $node->nid . '/submission/' . $next . ($enabled ? '/edit' : ''), array(
        'class' => 'webform-submission-next',
      ), $enabled ? 'destination=node/' . $node->nid . '/submission/' . $next . '/edit' : NULL) : '<span class="webform-submission-next">' . t('Next submission') . '</span>',
    );
    $form['submission_info'] = array(
      '#title' => t('Submission Information'),
      '#type' => 'fieldset',
      '#collapsible' => FALSE,
    );
    $account = user_load(array(
      'uid' => $submission->uid,
    ));
    $form['submission_info']['user_picture'] = array(
      '#value' => theme('user_picture', $account),
    );
    $form['submission_info']['form'] = array(
      '#value' => '<div>' . t('Form: !form', array(
        '!form' => l($node->title, 'node/' . $node->nid),
      )) . '</div>',
    );
    $form['submission_info']['submitted'] = array(
      '#value' => '<div>' . t('Submitted by !name', array(
        '!name' => theme('username', $account),
      )) . '</div>',
    );
    $form['submission_info']['time'] = array(
      '#value' => '<div>' . format_date($submission->submitted, 'large') . '</div>',
    );
    $form['submission_info']['ip_address'] = array(
      '#value' => '<div>' . $submission->remote_addr . '</div>',
    );
  }

  // Add a theme function for this form.
  $form['#theme'] = 'webform_form_' . $node->nid;

  // Add a css class for all client forms.
  $form['#attributes'] = array(
    'class' => 'webform-client-form',
  );

  // Set the encoding type (necessary for file uploads).
  $form['#attributes']['enctype'] = 'multipart/form-data';
  $form['#base'] = 'webform_client_form';

  // Set the form action to the node ID in case this is being displayed on the
  // teaser, subsequent pages should be on the node page directly.
  if (arg(2) != 'submission') {
    $form['#action'] = url('node/' . $node->nid);
  }
  if (is_array($node->webform['components']) && !empty($node->webform['components'])) {

    // Prepare a new form array.
    $form['submitted'] = array(
      '#tree' => TRUE,
    );
    $form['details'] = array(
      '#tree' => TRUE,
    );

    // Put the components into a tree structure.
    $component_tree = array();
    $page_count = 1;
    $page_num = 1;
    _webform_components_tree_build($node->webform['components'], $component_tree, 0, $page_count);
    if (!$preview && $enabled) {
      if ($page_count > 1) {
        $next_page = t('Next Page >');
        $prev_page = t('< Previous Page');
        if (isset($form_values)) {
          $page_num = $form_values['details']['page_num'];
          if ($form_values['op'] == $prev_page && $page_num > 1) {
            $page_num--;
          }
          else {
            if ($form_values['op'] == $next_page && $page_num < $page_count) {
              $page_num++;
            }
          }
        }
        else {
          $page_num = 1;
        }
        $form['#multistep'] = TRUE;
        if ($_POST['op'] != (empty($node->webform['submit_text']) ? t('Submit') : $node->webform['submit_text'])) {
          $form['#redirect'] = FALSE;
        }
        $form['details']['page_num'] = array(
          '#type' => 'hidden',
          '#value' => $page_num,
        );
        $form['details']['page_count'] = array(
          '#type' => 'hidden',
          '#value' => $page_count,
        );

        // Add the submit button(s).
        if ($page_num > 1) {
          $form['previous'] = array(
            '#type' => 'submit',
            '#value' => $prev_page,
            '#weight' => 1000,
          );
        }
        if ($page_num == $page_count) {
          $form['submit'] = array(
            '#type' => 'submit',
            '#value' => empty($node->webform['submit_text']) ? t('Submit') : $node->webform['submit_text'],
            '#weight' => 1001,
          );
        }
        elseif ($page_num < $page_count) {
          $form['next'] = array(
            '#type' => 'submit',
            '#value' => $next_page,
            '#weight' => 1001,
          );
        }
      }
      else {
        $page_num = 1;

        // Add the submit button.
        $form['submit'] = array(
          '#type' => 'submit',
          '#value' => empty($node->webform['submit_text']) ? t('Submit') : $node->webform['submit_text'],
          '#weight' => 1000,
        );
      }
    }

    // Recursively add components to the form. Microweights keep things in webform order.
    $microweight = 0.001;
    foreach ($component_tree['children'] as $cid => $component) {
      _webform_client_form_add_component($cid, $component, $form['submitted'], $form, $submission, $page_num, $enabled);
      $form['submitted'][$component['form_key']]['#weight'] += $microweight;
      $microweight += 0.001;
    }

    // Do not display the submit button if this is a preview or submission view.
    if (!$preview && $enabled) {

      // Additional hidden elements.
      $form['details']['email_subject'] = array(
        '#type' => 'hidden',
        '#value' => $node->webform['email_subject'],
      );
      $form['details']['email_from_name'] = array(
        '#type' => 'hidden',
        '#value' => $node->webform['email_from_name'],
      );
      $form['details']['email_from_address'] = array(
        '#type' => 'hidden',
        '#value' => $node->webform['email_from_address'],
      );
      $form['details']['nid'] = array(
        '#type' => 'value',
        '#value' => $node->nid,
      );
      if (isset($submission->sid)) {
        $form['details']['sid'] = array(
          '#type' => 'hidden',
          '#value' => $submission->sid,
        );
      }
    }
  }
  return $form;
}
function _webform_client_form_add_component($cid, $component, &$parent_fieldset, &$form, $submission, $page_num, $enabled = FALSE) {

  // Load with submission information if necessary.
  if (!$enabled) {

    // This component is display only.
    $display_function = '_webform_submission_display_' . $component['type'];
    if (function_exists($display_function)) {
      $parent_fieldset[$component['form_key']] = $display_function(empty($submission->data[$cid]) ? NULL : $submission->data[$cid], $component, $enabled);
    }
  }
  elseif ($component['page_num'] == $page_num) {

    // Add this user-defined field to the form (with all the values that are always available).
    if (isset($submission->data)) {
      $display_function = '_webform_submission_display_' . $component['type'];
      if (function_exists($display_function)) {
        $parent_fieldset[$component['form_key']] = $display_function($submission->data[$cid], $component, $enabled);
      }
    }
    else {
      $render_function = '_webform_render_' . $component['type'];
      if (function_exists($render_function)) {
        $parent_fieldset[$component['form_key']] = $render_function($component);

        // Call the component render function.
      }
      else {
        drupal_set_message(t('The webform component @type is not able to be displayed', array(
          '@type' => $component['type'],
        )));
      }
    }
  }
  if (isset($component['children']) && is_array($component['children'])) {
    $microweight = 0.001;
    foreach ($component['children'] as $scid => $subcomponent) {
      _webform_client_form_add_component($scid, $subcomponent, $parent_fieldset[$component['form_key']], $form, $submission, $page_num, $enabled);
      $parent_fieldset[$component['form_key']][$subcomponent['form_key']]['#weight'] += $microweight;
      $microweight += 0.001;
    }
  }
}
function webform_client_form_validate($form_id, &$form_values) {
  $node = node_load(array(
    'nid' => $form_values['details']['nid'],
  ));
  $sid = $form_values['details']['sid'];

  // Check that the user has not exceeded the submission limit.
  // This usually will only apply to anonymous users when the page cache is
  // enabled, because they may submit the form even if they do not have access.
  if ($node->webform['submit_limit'] != -1) {

    // -1: Submissions are never throttled.
    include_once drupal_get_path('module', 'webform') . '/webform_submissions.inc';
    if (empty($sid) && ($limit_exceeded = _webform_submission_limit_check($node))) {
      $error = theme('webform_view_messages', $node, 0, 1, 0, $limit_exceeded, array_keys(user_roles()));
      form_set_error('', $error);
      return;
    }
  }

  // Flatten trees within the submission.
  $form_values['submitted_tree'] = $form_values['submitted'];
  $form_values['submitted'] = _webform_client_form_submit_flatten($node, $form_values['submitted']);
  if (trim($node->webform['additional_validate'])) {

    // We use eval here (rather than drupal_eval) because the user needs access to local variables.
    eval('?>' . $node->webform['additional_validate']);
  }
}
function webform_client_form_submit($form_id, &$form_values) {
  global $user, $base_url;
  include_once drupal_get_path('module', 'webform') . '/webform_submissions.inc';
  webform_load_components();
  $node = node_load(array(
    'nid' => $form_values['details']['nid'],
  ));
  $session_key = 'webform_form_' . $node->nid;

  // Check for a multi-page form that is not yet complete.
  if ($form_values['op'] != (empty($node->webform['submit_text']) ? t('Submit') : $node->webform['submit_text'])) {

    // Perform post processing by components.
    _webform_client_form_submit_process($node, $form_values['submitted'], array(
      'select',
      'grid',
    ));

    // This is a multi-page form that is not yet complete.
    // Save the order of the current post into a copied array.
    $original_post = is_array($_POST['submitted']) ? $_POST['submitted'] : array();
    $_POST['submitted'] = array();

    // Copy values stored during previous steps into $_POST because they are needed in form_builder() to repopulate the form.
    if (is_array($_SESSION[$session_key])) {
      foreach ($_SESSION[$session_key] as $key => $val) {
        $_POST['submitted'][$key] = $val;
      }
    }

    // Restore the newly added values either in the previous position.
    foreach ($original_post as $key => $val) {
      $_POST['submitted'][$key] = $val;
    }

    // Remove the variable so it doesn't show up in the additional processing.
    unset($original_post);

    // Store values from an intermediate stage of a multistep form in $_SESSION.
    if (is_array($form_values['submitted'])) {
      foreach ($form_values['submitted'] as $key => $val) {
        $_SESSION[$session_key][$key] = $val;
      }
    }
    return;
  }
  if (is_array($_SESSION[$session_key])) {

    // Merge any submission data stored in $_SESSION for multistep forms.
    $original_values = is_array($form_values['submitted']) ? $form_values['submitted'] : array();
    $form_values['submitted'] = array();
    foreach ($_SESSION[$session_key] as $key => $val) {
      $form_values['submitted'][$key] = $val;
    }
    foreach ($original_values as $key => $val) {
      $form_values['submitted'][$key] = $val;
    }

    // Remove the variable so it doesn't show up in the additional processing.
    unset($original_values);
    unset($_SESSION[$session_key]);
  }

  // Perform post processing by components.
  _webform_client_form_submit_process($node, $form_values['submitted']);

  // Flatten trees within the submission.
  $form_values['submitted_tree'] = $form_values['submitted'];
  $form_values['submitted'] = _webform_client_form_submit_flatten($node, $form_values['submitted']);

  // Convert additional email addresses into actual values.
  foreach ($node->webform['additional_emails'] as $cid => $value) {
    if (is_array($form_values['submitted'][$cid])) {
      $node->webform['additional_emails'][$cid] = array();
      foreach ($form_values['submitted'][$cid] as $submitted_value) {
        if ($submitted_value) {
          $node->webform['additional_emails'][$cid][] = $submitted_value;
        }
      }
    }
    else {
      $node->webform['additional_emails'][$cid] = $form_values['submitted'][$cid];
    }
    if (empty($node->webform['additional_emails'][$cid])) {
      unset($node->webform['additional_emails'][$cid]);
    }
  }

  // Perform additional submit processing.
  if (trim($node->webform['additional_submit'])) {

    // We use eval here (rather than drupal_eval) because the user needs access to local variables.
    eval('?>' . $node->webform['additional_submit']);
  }

  // Save the submission to the database.
  if (!$form_values['details']['sid']) {

    // No sid was found thus insert it in the datatabase.
    $form_values['details']['sid'] = webform_submission_insert($node, $form_values['submitted']);
    $form_values['details']['is_new'] = TRUE;

    // Set a cookie including the server's submission time.
    // The cookie expires in the length of the interval plus a day to compensate for different timezones.
    if (variable_get('webform_use_cookies', 0)) {
      $cookie_name = 'webform-' . $node->nid;
      $time = time();
      setcookie($cookie_name . '[' . $time . ']', $time, $time + $node->webform['submit_interval'] + 86400);
    }
  }
  else {

    // Sid was found thus update the existing sid in the datatbase.
    webform_submission_update($node, $form_values['details']['sid'], $form_values['submitted']);
    $form_values['details']['is_new'] = FALSE;
  }
  $sid = $form_values['details']['sid'];

  // Check if this form is sending an email.
  if ((!empty($node->webform['email']) || !empty($node->webform['additional_emails'])) && $form_values['details']['is_new']) {

    // Set values for the name, address, and subject for the email.
    $email_from_name = $node->webform['email_from_name'];
    $email_from_address = $node->webform['email_from_address'];
    $email_subject = $node->webform['email_subject'];
    foreach (array(
      'from_name',
      'from_address',
      'subject',
    ) as $field) {
      if ($node->webform['email_' . $field] == 'default') {
        ${'email_' . $field} = _webform_filter_values(webform_variable_get('webform_default_' . $field), $node, $form_values['submitted'], FALSE, TRUE);
      }
      elseif (is_numeric($node->webform['email_' . $field])) {
        if (is_array($form_values['submitted'][${'email_' . $field}])) {
          $values = array();
          foreach ($form_values['submitted'][${'email_' . $field}] as $key => $value) {
            $values[] = _webform_filter_values($value, $node, $form_values['submitted'], FALSE, TRUE);
          }
          ${'email_' . $field} = implode(', ', $values);
        }
        else {
          ${'email_' . $field} = _webform_filter_values($form_values['submitted'][${'email_' . $field}], $node, $form_values['submitted'], FALSE, TRUE);
        }
      }
      else {
        ${'email_' . $field} = _webform_filter_values(${'email_' . $field}, $node, $form_values['submitted'], FALSE, TRUE);
      }
    }

    // Create a themed message for mailing.
    // Check for a node-specific message:
    $emails = $node->webform['additional_emails'];
    if ($node->webform['email']) {
      $emails['default'] = $node->webform['email'];
    }
    $messages = array();
    $headers = array();
    $froms = array();
    $subjects = array();
    foreach ($emails as $cid => $email) {
      $messages[$cid] = theme('webform_mail_message_' . $node->nid, $form_values, $node, $sid, $cid);
      $headers[$cid] = theme('webform_mail_headers_' . $node->nid, $form_values, $node, $sid, $cid);

      // Otherwise use the generic message or headers:
      if (empty($messages[$cid])) {
        $messages[$cid] = theme('webform_mail_message', $form_values, $node, $sid, $cid);
      }
      if (empty($headers[$cid])) {
        $headers[$cid] = theme('webform_mail_headers', $form_values, $node, $sid, $cid);
      }

      // Assemble the FROM string.
      if (isset($headers[$cid]['From'])) {
        $froms[$cid] = $headers[$cid]['From'];
        unset($headers[$cid]['From']);
      }
      elseif (drupal_strlen($email_from_name) > 0) {
        $froms[$cid] = '"' . mime_header_encode($email_from_name) . '" <' . $email_from_address . '>';
      }
      else {
        $froms[$cid] = $email_from_address;
      }

      // Update the subject if set in the themed headers.
      if (isset($headers[$cid]['Subject'])) {
        $subjects[$cid] = $headers[$cid]['Subject'];
        unset($headers[$cid]['Subject']);
      }
      else {
        $subjects[$cid] = $email_subject;
      }

      // Update the to e-mail if set in the themed headers.
      if (isset($headers[$cid]['To'])) {
        $emails[$cid] = $headers[$cid]['To'];
        unset($headers[$cid]['To']);
      }
    }

    // Verify that this submission is not attempting to send any spam hacks.
    if (_webform_submission_spam_check($emails['default'], $subjects['default'], $froms['default'], $headers['default'])) {
      $ip_address = isset($_SERVER['HTTP_X_FORWARDED_FOR']) ? $_SERVER['HTTP_X_FORWARDED_FOR'] : $_SERVER['REMOTE_ADDR'];
      watchdog('webform', t('Possible spam attempt from @remote_addr', array(
        '@remote_add' => $ip_address,
      )) . "<br />\n" . nl2br(htmlentities($messages['default'])));
      drupal_set_message(t('Illegal information. Data not submitted.'), 'error');
      return FALSE;
    }

    // Mail the webform results.
    foreach ($emails as $cid => $address) {

      // In the case of checkboxes or multiple select, multiple e-mails may need
      // to be sent out.
      if (is_array($address)) {
        foreach ($address as $single_address) {
          drupal_mail('webform-submission', $single_address, $subjects[$cid], $messages[$cid], $froms[$cid], $headers[$cid]);

          // Debugging output for email.
          if (variable_get('webform_debug', 0) >= 2) {
            drupal_set_message('E-mail Headers: <pre>' . htmlentities(print_r($headers[$cid], TRUE)) . '</pre>To: ' . $single_address . '<br />From: ' . htmlentities($froms[$cid]) . '<br />Subject: ' . $subjects[$cid] . '<br />E-mail Body: <pre>' . $messages[$cid] . '</pre>');
          }
        }
      }
      else {
        drupal_mail('webform-submission', $address, $subjects[$cid], $messages[$cid], $froms[$cid], $headers[$cid]);

        // Debugging output for email.
        if (variable_get('webform_debug', 0) >= 2) {
          drupal_set_message('E-mail Headers: <pre>' . htmlentities(print_r($headers[$cid], TRUE)) . '</pre>To: ' . $address . '<br />From: ' . htmlentities($froms[$cid]) . '<br />Subject: ' . $subjects[$cid] . '<br />E-mail Body: <pre>' . $messages[$cid] . '</pre>');
        }
      }
    }
  }

  // More debugging output.
  if (variable_get('webform_debug', 0) >= 2) {
    drupal_set_message('$form_values are: <pre>' . htmlentities(print_r($form_values, true)) . '</pre>');
    drupal_set_message('$_SERVER is: <pre>' . htmlentities(print_r($_SERVER, true)) . '</pre>');
    drupal_set_message('$_POST is: <pre>' . htmlentities(print_r($_POST, true)) . '</pre>');
  }

  // Log to watchdog if normal debug is on.
  if (variable_get('webform_debug', 0) >= 1) {
    watchdog('webform', t('Submission posted to %title', array(
      '%title' => $node->title,
    )) . l(t('Results'), 'node/' . $node->nid . '/submission/' . $sid) . "<br />\n<pre>" . htmlentities(print_r($form_values, TRUE)) . "</pre>");
  }

  // Check confirmation field to see if redirect should be to another node or a message.
  if ($form_values['submission']) {
    drupal_set_message(t('Submission updated.'));
    $redirect = NULL;
  }
  elseif (valid_url(trim($node->webform['confirmation']), TRUE)) {
    $redirect = trim($node->webform['confirmation']);
  }
  elseif (preg_match('/^internal:/', trim(strip_tags($node->webform['confirmation'])))) {
    $path = preg_replace('/^internal:/', '', trim(strip_tags($node->webform['confirmation'])));
    $redirect = array(
      trim($path),
      'sid=' . $sid,
    );
  }
  elseif (preg_match('/^message:/', $node->webform['confirmation'])) {
    $message = preg_replace('/^message:/', '', $node->webform['confirmation']);
    drupal_set_message($message);
    $redirect = NULL;
  }
  else {
    $redirect = array(
      'node/' . $node->nid . '/done',
      'sid=' . $sid,
    );
  }
  return $redirect;
}

/**
 * Post processes the submission tree with any updates from components.
 *
 * @param $node
 *   The full webform node.
 * @param $form_values
 *   The form values for the form.
 * @param $types
 *   Optional. Specific types to perform processing.
 * @param $parent
 *   Internal use. The current parent CID whose children are being processed.
 */
function _webform_client_form_submit_process($node, &$form_values, $types = NULL, $parent = 0) {
  if (is_array($form_values)) {
    foreach ($form_values as $form_key => $value) {
      $cid = webform_get_cid($node, $form_key, $parent);
      if (is_array($value) && $node->webform['components'][$cid]['type'] == 'fieldset') {
        _webform_client_form_submit_process($node, $form_values[$form_key], $types, $cid);
      }
      $component = $node->webform['components'][$cid];
      $submit_function = '_webform_submit_' . $component['type'];
      if (function_exists($submit_function) && (!isset($types) || in_array($component['type'], $types))) {

        // Call the component process submission function.
        $submit_function($form_values[$component['form_key']], $component);
      }
    }
  }
}

/**
 * Flattens a submitted form back into a single array representation (rather than nested fields)
 */
function _webform_client_form_submit_flatten($node, $fieldset, $parent = 0) {
  $values = array();
  if (is_array($fieldset)) {
    foreach ($fieldset as $form_key => $value) {
      $cid = webform_get_cid($node, $form_key, $parent);
      if (is_array($value) && $node->webform['components'][$cid]['type'] == 'fieldset') {
        $values += _webform_client_form_submit_flatten($node, $value, $cid);
      }
      else {
        $values[$cid] = $value;
      }
    }
  }
  return $values;
}

/**
 * Prints the confirmation message after a successful submission.
 */
function _webform_confirmation($node) {
  drupal_set_title(check_plain($node->title));
  $output = theme('webform_confirmation_' . $node->nid, $node, $_GET['sid']);
  if (empty($output)) {
    $output = theme('webform_confirmation', $node, $_GET['sid']);
  }
  return $output;
}

/**
 * Themable function for webform submission confirmation.
 *
 * @param $node
 *   The webform node that has just been submitted.
 */
function theme_webform_confirmation($node, $sid) {
  if (empty($node->webform['confirmation'])) {
    drupal_set_message(t('Thank you, your submission has been received.'));
    drupal_goto('node/' . $node->nid);
  }
  $node->body = check_markup($node->webform['confirmation'], $node->format, FALSE);
  $node->links['webform_back'] = array(
    'href' => 'node/' . $node->nid,
    'title' => t('Go back to the form'),
  );
  return theme('node', $node, FALSE, TRUE);
}

/**
 * Theme the contents of e-mails sent by webform.
 *
 * @param $form_values
 *   An array of all form values submitted by the user. The array contains three
 *   keys containing the following:
 *   - submitted: All the submitted values in a single array keyed by webform
 *     component IDs. Useful for simply looping over the values.
 *   - submitted_tree: All the submitted values in a tree-structure array, keyed
 *     by the Form Key values defined by the user.
 * @param $node
 *   The complete node object for the webform.
 * @param $sid
 *   The submission ID of the new submission.
 * @param $cid
 *   If you desire to make different e-mails depending on the recipient, you can
 *   check this component ID to output different content. This will be the ID
 *   of the component that is a conditional e-mail recipient. For the normal
 *   e-mails, it will have the value of 'default'.
 */
function theme_webform_mail_message($form_values, $node, $sid, $cid) {
  global $user;
  $message = '';
  $message .= t('Submitted on') . ' ' . format_date(time(), 'small') . "\n";
  $ip_address = isset($_SERVER['HTTP_X_FORWARDED_FOR']) ? $_SERVER['HTTP_X_FORWARDED_FOR'] : $_SERVER['REMOTE_ADDR'];
  if ($user->uid) {
    $message .= t('Submitted by user') . ": {$user->name} [{$ip_address}]\n";
  }
  else {
    $message .= t('Submitted by anonymous user') . ": [{$ip_address}]\n";
  }
  $message .= "\n";
  $message .= t('Submitted values are:') . "\n\n";
  $message .= theme('webform_mail_fields', 0, $form_values['submitted_tree'], $node);
  $message .= "\n\n";
  $message .= t('The results of this submission may be viewed at:') . "\n";
  $message .= url('node/' . $node->nid . '/submission/' . $sid, NULL, NULL, TRUE);
  return $message;
}

/**
 * Theme the fields portion of the e-mails sent by webform.
 *
 * This function calls itself recursively to maintain the tree structure of
 * components in the webform. It is called intially by
 * theme_webform_create_mailmessage().
 *
 * @param $cid
 *   The parent component ID we're currently printing.
 * @param $value
 *   The value of the component to be printed. May be an array of other components.
 * @param $node
 *   The full node object.
 * @param $indent
 *   The current amount of indentation being applied to printed components.
 */
function theme_webform_mail_fields($cid, $value, $node, $indent = '') {
  $component = $node->webform['components'][$cid];

  // Check if this component needs to be included in the email at all.
  if ($cid && !$component['email'] && !in_array($component['type'], array(
    'markup',
    'fieldset',
    'pagebreak',
  ))) {
    return '';
  }

  // First check for component-level themes.
  $themed_output = theme('webform_mail_' . $component['type'], $value, $component);
  $message = '';
  if ($themed_output) {

    // Indent the output and add to message.
    $message .= $indent;
    $themed_output = rtrim($themed_output, "\n");
    $message .= str_replace("\n", "\n" . $indent, $themed_output);
    $message .= "\n";
  }
  elseif (!is_array($value)) {

    // Note that newlines cannot be preceeded by spaces to display properly in some clients.
    if ($component['name']) {

      // If text is more than 60 characters, put it on a new line with space after.
      $long = drupal_strlen($indent . $component['name'] . $value) > 60;
      $message .= $indent . $component['name'] . ':' . (empty($value) ? "\n" : ($long ? "\n{$value}\n\n" : " {$value}\n"));
    }
  }
  else {
    if ($cid != 0) {
      $message .= $indent . $component['name'] . ":\n";
    }
    foreach ($value as $k => $v) {
      foreach ($node->webform['components'] as $local_key => $local_value) {
        if ($local_value['form_key'] == $k && $local_value['pid'] == $cid) {
          $form_key = $local_key;
          break;
        }
      }
      $message .= theme('webform_mail_fields', $form_key, $v, $node, $indent . '  ');
    }
  }
  return $message;
}

/**
 * Theme the headers when sending an email from webform.
 *
 * @param $form_values
 *   An array of all form values submitted by the user. The array contains three
 *   keys containing the following:
 *   - submitted: All the submitted values in a single array keyed by webform
 *     component IDs. Useful for simply looping over the values.
 *   - submitted_tree: All the submitted values in a tree-structure array, keyed
 *     by the Form Key values defined by the user.
 * @param $node
 *   The complete node object for the webform.
 * @param $sid
 *   The submission ID of the new submission.
 * @param $cid
 *   If you desire to make different e-mail headers depending on the recipient,
 *   you can check this component ID to output different content. This will be
 *   the ID of the component that is a conditional e-mail recipient. For the
 *   normal e-mails, it will have the value of 'default'.
 * @return
 *   An array of headers to be used when sending a webform email. If headers
 *   for "From", "To", or "Subject" are set, they will take precendence over
 *   the values set in the webform configuration.
 */
function theme_webform_mail_headers($form_values, $node, $sid, $cid) {
  $headers = array(
    'X-Mailer' => 'Drupal Webform (PHP/' . phpversion() . ')',
  );
  return $headers;
}

/**
 * Filters all special tokens provided by webform, such as %post and %profile.
 *
 * @param $string
 *   The string to have its toknes replaced.
 * @param $node
 *   If replacing node-level tokens, the node for which tokens will be created.
 * @param $submission
 *   If replacing submission-level tokens, the submission for which tokens will
 *   be created.
 * @param $strict
 *   Boolean value indicating if the results should be run through check_plain.
 *   This is used any time the values will be output as HTML, but not in
 *   default values or e-mails.
 * @param $allow_anonymous
 *   Boolean value indicating if all tokens should be replaced for anonymous
 *   users, even if they contain sensitive user information such as %session or
 *   %ip_address. This is disabled by default to prevent user data from being
 *   preserved in the anonymous page cache and should only be used in
 *   non-cached situations, such as e-mails.
 */
function _webform_filter_values($string, $node = NULL, $submission = NULL, $strict = TRUE, $allow_anonymous = FALSE) {
  global $user;
  static $replacements;

  // Setup default token replacements.
  if (!isset($replacements)) {
    $replacements['unsafe'] = array();
    $replacements['safe']['%site'] = variable_get('site_name', 'drupal');
    $replacements['safe']['%date'] = format_date(time(), 'large');
  }

  // Node replacements.
  if (isset($node) && !array_key_exists('%title', $replacements)) {
    $replacements['safe']['%title'] = $node->title;
  }

  // Submission replacements.
  if (isset($submission) && !array_key_exists('%email_values', $replacements)) {
    foreach ($submission as $cid => $value) {
      $replacements['unsafe']['%cid[' . $cid . ']'] = $value;
    }
  }

  // Provide a list of candidates for token replacement.
  // Note these tokens are not cached as they may change frequently.
  $special_tokens = array(
    'safe' => array(
      '%get' => $_GET,
    ),
    'unsafe' => array(
      '%cookie' => $_COOKIE,
      '%session' => $_SESSION,
      '%post' => $_POST,
      '%request' => $_REQUEST,
    ),
  );

  // User replacements.
  if (!array_key_exists('%username', $replacements['unsafe'])) {
    $replacements['unsafe']['%username'] = isset($user->name) ? $user->name : '';
    $replacements['unsafe']['%useremail'] = isset($user->mail) ? $user->mail : '';
    $replacements['unsafe']['%ip_address'] = isset($_SERVER['HTTP_X_FORWARDED_FOR']) ? $_SERVER['HTTP_X_FORWARDED_FOR'] : $_SERVER['REMOTE_ADDR'];

    // Doesn't really belong here with user things, but works.
    $special_tokens['unsafe']['%server'] = $_SERVER;
  }

  // User profile replacements.
  if (!isset($replacements['unsafe']['%profile[uid]'])) {
    if ($user->uid && module_exists('profile')) {
      profile_load_profile($user);
    }
    $special_tokens['unsafe']['%profile'] = $user;
  }
  foreach ($special_tokens as $safe_state => $tokens) {
    foreach ($tokens as $token => $variable) {
      if (strpos($string, $token) !== FALSE) {
        foreach ($variable as $key => $value) {

          // This special case for profile module dates.
          if ($token == '%profile' && is_array($value) && isset($value['year'])) {
            $replacement = format_date(strtotime($value['month'] . '/' . $value['day'] . '/' . $value['year']), 'custom', 'F j, Y', '0');
          }
          else {
            $replacement = (string) $value;
          }
          $replacements[$safe_state][$token . '[' . $key . ']'] = $replacement;
        }
      }
    }
  }

  // Make a copy of the replacements so we don't affect the static version.
  $safe_replacements = $replacements['safe'];

  // Restrict replacements for anonymous users. Not all tokens can be used
  // because they may expose session or other private data to other users when
  // anonymous page caching is enabled.
  if ($user->uid || $allow_anonymous) {
    $safe_replacements += $replacements['unsafe'];
  }
  else {
    foreach ($replacements['unsafe'] as $key => $value) {
      $safe_replacements[$key] = '';
    }
  }
  $find = array_keys($safe_replacements);
  $replace = array_values($safe_replacements);
  $string = str_replace($find, $replace, $string);

  // Clean up any unused tokens.
  foreach ($special_tokens as $safe_state => $tokens) {
    foreach (array_keys($tokens) as $token) {
      $string = preg_replace('/\\' . $token . '\\[\\w+\\]/', '', $string);
    }
  }
  return $strict ? filter_xss($string) : $string;
}

/**
 * Filters all special tokens provided by webform, and allows basic layout in descriptions.
 */
function _webform_filter_descriptions($string, $node = NULL, $submission = NULL, $strict = TRUE) {
  return check_markup(_webform_filter_values($string, $node, $submission, $strict));
}

/**
 * Menu callback for admin/content/webform. Displays all webforms on the site.
 */
function webform_admin_content() {
  $result = db_query(db_rewrite_sql("SELECT n.* FROM {node} n WHERE n.type = 'webform'"));
  $nodes = array();
  while ($node = db_fetch_object($result)) {
    $nodes[] = $node;
  }
  return theme('webform_admin_content', $nodes);
}

/**
 * Generate a list of all webforms avaliable on this site.
 */
function theme_webform_admin_content($nodes) {
  $header = array(
    t('Title'),
    array(
      'data' => t('View'),
      'colspan' => '4',
    ),
    array(
      'data' => t('Operations'),
      'colspan' => '2',
    ),
  );
  $rows = array();
  foreach ($nodes as $node) {
    $rows[] = array(
      l($node->title, 'node/' . $node->nid),
      l(t('Submissions'), 'node/' . $node->nid . '/results'),
      l(t('Analysis'), 'node/' . $node->nid . '/results/analysis'),
      l(t('Table'), 'node/' . $node->nid . '/results/table'),
      l(t('Download'), 'node/' . $node->nid . '/results/download'),
      node_access('update', $node) ? l(t('Edit'), 'node/' . $node->nid . '/edit') : '',
      user_access('clear webform results') ? l(t('Clear'), 'node/' . $node->nid . '/results/clear') : '',
    );
  }
  return theme('table', $header, $rows);
}

/**
 * Menu callback for node/[nid]/components.
 */
function webform_components($node, $cid = NULL, $op = 'edit') {
  include_once drupal_get_path('module', 'webform') . '/webform_components.inc';
  $components = webform_load_components();
  if (isset($node->webform['components'][$cid]) || $cid == 'new') {
    if ($op == 'delete') {
      return drupal_get_form('webform_component_delete_form', $node, $node->webform['components'][$cid]);
    }
    elseif ($cid == 'new' && in_array($op, array_keys($components))) {
      $component = array(
        'type' => $op,
        'name' => $_GET['name'],
        'mandatory' => $_GET['mandatory'],
        'email' => $_GET['email'],
        'pid' => $_GET['pid'],
        'weight' => $_GET['weight'],
      );
      webform_component_defaults($component);
      return drupal_get_form('webform_component_edit_form', $node, $component);
    }
    elseif (isset($node->webform['components'][$cid]) && $op == 'clone') {
      $component = $node->webform['components'][$cid];
      webform_component_defaults($component);
      return drupal_get_form('webform_component_edit_form', $node, $component, TRUE);
    }
    elseif (isset($node->webform['components'][$cid])) {
      $component = $node->webform['components'][$cid];
      webform_component_defaults($component);
      return drupal_get_form('webform_component_edit_form', $node, $component);
    }
  }
  return drupal_get_form('webform_components_form', $node);
}

/**
 * Menu callback for all content under admin/content/webform.
 */
function webform_results($node, $op = 'submissions') {
  include_once drupal_get_path('module', 'webform') . '/webform_report.inc';
  include_once drupal_get_path('module', 'webform') . '/webform_submissions.inc';
  drupal_set_title(check_plain($node->title));
  switch ($op) {
    case 'analysis':
      return webform_results_analysis($node);
    case 'clear':
      return drupal_get_form('webform_results_clear_form', $node);
    case 'table':
      return webform_results_table($node, '50');
    case 'download':
      return drupal_get_form('webform_results_download_form', $node);
    case 'user_submissions':
      return webform_results_submissions($node, TRUE, '50');
    case 'submissions':
    default:
      return webform_results_submissions($node, FALSE, '50');
      break;
  }
}

/**
 * Given a form_key and a list of form_key parents, determine the cid.
 *
 * @param $node
 *   A fully loaded node object.
 * @param $form_key
 *   The form key for which we're finding a cid.
 * @param $parent
 *   The cid of the parent component.
 */
function webform_get_cid(&$node, $form_key, $pid) {
  foreach ($node->webform['components'] as $cid => $component) {
    if ($component['form_key'] == $form_key && $component['pid'] == $pid) {
      return $cid;
    }
  }
}

/**
 * Retreive a Drupal variable with the appropriate default value.
 */
function webform_variable_get($variable) {
  switch ($variable) {
    case 'webform_default_from_name':
      $result = variable_get('webform_default_from_name', variable_get('site_name', ''));
      break;
    case 'webform_default_from_address':
      $result = variable_get('webform_default_from_address', variable_get('site_mail', ini_get('sendmail_from')));
      break;
    case 'webform_default_subject':
      $result = variable_get('webform_default_subject', t('Form submission from: %title'));
      break;
  }
  return $result;
}
function theme_webform_token_help() {
  $tokens = array(
    '%username',
    '%useremail',
    '%session[' . t('key') . ']',
    '%post[' . t('key') . ']',
    '%request[' . t('key') . ']',
    '%cookie[' . t('key') . ']',
    '%server[' . t('key') . ']',
  );
  if (module_exists('profile')) {
    $tokens[] = '%profile[' . t('key') . ']';
  }
  $anonymous_tokens = array(
    '%site',
    '%date',
    '%get[' . t('key') . ']',
  );
  $output = '';
  $output .= t('You may use special tokens in this field that will be replaced with dynamic values.');
  $output .= theme('item_list', $anonymous_tokens, t('all users:'));
  $output .= theme('item_list', $tokens, t('authorized users only:'));
  $output .= t('You can use %server[key] to add any of the special PHP <a href="http://www.php.net/reserved.variables#reserved.variables.server">$_SERVER</a> variables, %session[key] to add any of the special PHP <a href="http://www.php.net/reserved.variables#reserved.variables.session">$_SESSION</a> variables and %get[key] to create prefilled forms from the <a href="http://www.php.net/reserved.variables#reserved.variables.get">URL</a>. %cookie, %request and %post also work with their respective PHP variables. For example %server[HTTP_USER_AGENT], %session[id], or %get[q].');
  if (module_exists('profile')) {
    $output .= ' ' . t('If you are using the profiles module, you can also access all profile data using the syntax %profile[form_name]. If you for example have a profile value named profile_city, add the variable %profile[profile_city].');
  }
  $fieldset = array(
    '#title' => t('Token values'),
    '#type' => 'fieldset',
    '#collapsible' => TRUE,
    '#collapsed' => TRUE,
    '#children' => '<div>' . $output . '</div>',
  );
  return theme('fieldset', $fieldset);
}
function _webform_safe_name($name) {
  $new = trim($name);

  // If transliteration is available, use it to convert names to ASCII.
  if (function_exists('transliteration_get')) {
    $new = transliteration_get($new, '');
    $new = str_replace(array(
      ' ',
      '-',
    ), array(
      '_',
      '_',
    ), $new);
  }
  else {
    $new = str_replace(array(
      ' ',
      '-',
      '€',
      'ƒ',
      'Š',
      'Ž',
      'š',
      'ž',
      'Ÿ',
      '¢',
      '¥',
      'µ',
      'À',
      'Á',
      'Â',
      'Ã',
      'Ä',
      'Å',
      'Ç',
      'È',
      'É',
      'Ê',
      'Ë',
      'Ì',
      'Í',
      'Î',
      'Ï',
      'Ñ',
      'Ò',
      'Ó',
      'Ô',
      'Õ',
      'Ö',
      'Ø',
      'Ù',
      'Ú',
      'Û',
      'Ü',
      'Ý',
      'à',
      'á',
      'â',
      'ã',
      'ä',
      'å',
      'ç',
      'è',
      'é',
      'ê',
      'ë',
      'ì',
      'í',
      'î',
      'ï',
      'ñ',
      'ò',
      'ó',
      'ô',
      'õ',
      'ö',
      'ø',
      'ù',
      'ú',
      'û',
      'ü',
      'ý',
      'ÿ',
      'Œ',
      'œ',
      'Æ',
      'Ð',
      'Þ',
      'ß',
      'æ',
      'ð',
      'þ',
    ), array(
      '_',
      '_',
      'E',
      'f',
      'S',
      'Z',
      's',
      'z',
      'Y',
      'c',
      'Y',
      'u',
      'A',
      'A',
      'A',
      'A',
      'A',
      'A',
      'C',
      'E',
      'E',
      'E',
      'E',
      'I',
      'I',
      'I',
      'I',
      'N',
      'O',
      'O',
      'O',
      'O',
      'O',
      'O',
      'U',
      'U',
      'U',
      'U',
      'Y',
      'a',
      'a',
      'a',
      'a',
      'a',
      'a',
      'c',
      'e',
      'e',
      'e',
      'e',
      'i',
      'i',
      'i',
      'i',
      'n',
      'o',
      'o',
      'o',
      'o',
      'o',
      'o',
      'u',
      'u',
      'u',
      'u',
      'y',
      'y',
      'OE',
      'oe',
      'AE',
      'DH',
      'TH',
      'ss',
      'ae',
      'dh',
      'th',
    ), $new);
  }
  $new = drupal_strtolower($new);
  $new = preg_replace('/[^a-z0-9_]/', '', $new);
  return $new;
}

/**
 * Given a set of components, determine which one are appropriate for a
 * particular use, such as an email address or subject.
 *
 * @param $components
 *   An array of components.
 * @param $type
 *   Either 'email' or 'string' (for use as subject or from value).
 *
 */
function _webform_component_options($components, $type) {
  $acceptable_types = $type == 'email' ? array(
    'email',
    'select',
    'hidden',
  ) : array(
    'textfield',
    'select',
    'hidden',
  );
  $options = array();
  foreach ((array) $components as $cid => $component) {
    if (in_array($component['type'], $acceptable_types)) {
      $options[$cid] = $component['name'];
    }
  }
  return $options;
}

/**
 * Convert an array of components into a tree
 */
function _webform_components_tree_build($src, &$tree, $parent, &$page_count) {
  foreach ($src as $cid => $component) {
    if ($component['pid'] == $parent) {
      _webform_components_tree_build($src, $component, $cid, $page_count);
      $tree['children'][$cid] = $component;
      $tree['children'][$cid]['page_num'] = $page_count;
      if ($component['type'] == 'pagebreak') {
        $page_count++;
      }
    }
  }
  return $tree;
}

/**
 * Flatten a component tree into a flat list.
 */
function _webform_components_tree_flatten($tree) {
  $components = array();
  foreach ($tree as $cid => $component) {
    if (isset($component['children'])) {
      unset($component['children']);
      $components[$cid] = $component;

      // array_merge() can't be used here because the keys are numeric.
      $children = _webform_components_tree_flatten($tree[$cid]['children']);
      foreach ($children as $ccid => $ccomponent) {
        $components[$ccid] = $ccomponent;
      }
    }
    else {
      $components[$cid] = $component;
    }
  }
  return $components;
}

/**
 * Helper for the uasort in webform_tree_sort()
 */
function _webform_components_sort($a, $b) {
  if ($a['weight'] == $b['weight']) {
    return strcasecmp($a['name'], $b['name']);
  }
  return $a['weight'] < $b['weight'] ? -1 : 1;
}

/**
 * Sort each level of a component tree by weight and name
 */
function _webform_components_tree_sort($tree) {
  if (isset($tree['children']) && is_array($tree['children'])) {
    $children = array();
    uasort($tree['children'], '_webform_components_sort');
    foreach ($tree['children'] as $cid => $component) {
      $children[$cid] = _webform_components_tree_sort($component);
    }
    $tree['children'] = $children;
  }
  return $tree;
}

/**
 * Load all necessary component.inc files into memory.
 */
function webform_load_components($return_all = FALSE, $reset = FALSE) {
  static $component_list, $enabled_list;
  if (!isset($component_list) || $reset) {
    $component_list = array();
    $enabled_list = array();
    $path = drupal_get_path('module', 'webform') . '/components';
    $files = file_scan_directory($path, '^.*\\.inc$');
    foreach ($files as $filename => $file) {
      $enabled = variable_get('webform_enable_' . $file->name, 1);
      if ($return_all || $enabled) {
        include_once $filename;
        $component_list[$file->name] = t($file->name);
      }
      if ($enabled) {
        $enabled_list[$file->name] = t($file->name);
      }
    }
  }

  // Ensure only wanted components are returned, even all are loaded.
  return $return_all ? $component_list : array_intersect_assoc($component_list, $enabled_list);
}

Functions

Namesort descending Description
theme_webform_admin_content Generate a list of all webforms avaliable on this site.
theme_webform_admin_settings
theme_webform_advanced_submit_limit_form Theme the submit limit fieldset on the webform node form.
theme_webform_confirmation Themable function for webform submission confirmation.
theme_webform_mail_components_form Theme the component options for sending e-mails.
theme_webform_mail_fields Theme the fields portion of the e-mails sent by webform.
theme_webform_mail_headers Theme the headers when sending an email from webform.
theme_webform_mail_message Theme the contents of e-mails sent by webform.
theme_webform_mail_settings_form Theme the Webform mail settings section of the node form.
theme_webform_token_help
theme_webform_view Output the Webform into the node content.
theme_webform_view_messages Display a message to a user if they are not allowed to fill out a form.
webform_access Implementation of hook_access().
webform_admin_content Menu callback for admin/content/webform. Displays all webforms on the site.
webform_admin_settings Menu callback for admin/webform/settings.
webform_client_form Client form generation function. If this is displaying an existing submission, pass in the $submission variable with the contents of the submission to be displayed.
webform_client_form_submit
webform_client_form_validate
webform_components Menu callback for node/[nid]/components.
webform_delete Implementation of hook_delete().
webform_file_download Implementation of hook_file_download().
webform_form Implementation of hook_form(). Creates the standard form for editing or creating a webform.
webform_forms Implementation of hook_forms(). All webform_client_form forms share the same form handler
webform_form_alter Implementation of hook_form_alter().
webform_form_submit Submit handler for the webform node form.
webform_get_cid Given a form_key and a list of form_key parents, determine the cid.
webform_help Implementation of hook_help().
webform_insert Implementation of hook_insert().
webform_link Implementation of hook_link(). Always add a "view form" link.
webform_load Implementation of hook_load().
webform_load_components Load all necessary component.inc files into memory.
webform_menu Implementation of hook_menu().
webform_node_info Implementation of hook_node_info().
webform_perm Implementation of hook_perm().
webform_results Menu callback for all content under admin/content/webform.
webform_results_access Menu access callback. Ensure a user both access and node 'view' permission.
webform_submit Implementation of hook_submit().
webform_update Implementation of hook_update().
webform_validate Implementation of hook_validate().
webform_variable_get Retreive a Drupal variable with the appropriate default value.
webform_view Implementation of hook_view().
_webform_client_form_add_component
_webform_client_form_submit_flatten Flattens a submitted form back into a single array representation (rather than nested fields)
_webform_client_form_submit_process Post processes the submission tree with any updates from components.
_webform_components_sort Helper for the uasort in webform_tree_sort()
_webform_components_tree_build Convert an array of components into a tree
_webform_components_tree_flatten Flatten a component tree into a flat list.
_webform_components_tree_sort Sort each level of a component tree by weight and name
_webform_component_options Given a set of components, determine which one are appropriate for a particular use, such as an email address or subject.
_webform_confirmation Prints the confirmation message after a successful submission.
_webform_filter_descriptions Filters all special tokens provided by webform, and allows basic layout in descriptions.
_webform_filter_values Filters all special tokens provided by webform, such as %post and %profile.
_webform_safe_name