You are here

signup_webform.module in Signup 6.2

signup_webform.module

Signup integration with webform module: provides a signup pane for each webform node.

The intention is that if a webform is used for signup, it is *not* also used as a standalone webform; rather, we make use of webform for its convenience in allowing users to use a UI to build a form.

Also note that parenting, fieldsets, and pagebreaks are untested, unsupported, and probably won't work! @todo: add some checking for this.

@todo: use form_alter or nodeapi to disable the webform from the node view so it cant be submitted there (but visible for checking) @todo: figure out why an unpublished webform doesn't render its components and spoof this so admins can unpublish a signup webform to hide it. @todo: hook_nodeapi: delete: delete ALL stuff for this node! @todo: should we consider the 'access own webform submissions' permission when showing signup data that includes webform submissions?

File

modules/signup_webform/signup_webform.module
View source
<?php

/**
 * @file signup_webform.module
 * 
 * Signup integration with webform module: provides a signup pane for each 
 * webform node.
 *
 * The intention is that if a webform is used for signup, it is *not* also 
 * used as a standalone webform; rather, we make use of webform for its
 * convenience in allowing users to use a UI to build a form.
 *
 * Also note that parenting, fieldsets, and pagebreaks are untested, unsupported,
 * and probably won't work! @todo: add some checking for this.
 *
 * @todo: use form_alter or nodeapi to disable the webform from the node view
 *   so it cant be submitted there (but visible for checking)
 * @todo: figure out why an unpublished webform doesn't render its components
 *   and spoof this so admins can unpublish a signup webform to hide it.
 * @todo: hook_nodeapi: delete: delete ALL stuff for this node!
 * @todo: should we consider the 'access own webform submissions' permission
 *   when showing signup data that includes webform submissions?
 */

/**
 * Implementation of hook_signup_pane_info().
 *
 * Define our panes available to insert into the signup form.
 *
 * Panes should be provided by callback functions as a FormAPI array.
 * The callback should have the following signature:
 *   function my_callback(&$signup_form, &$form_state, $node, $signup, $pane_id, $signup_type = 'auth')
 * See signup_basic_form_form for an example.
 * The values submitted to the form elements defined by this form will be 
 * serialized and stored in the {signup_log} table as 'form_data'.
 *
 * @param $node
 *  (optional) The node being considered for panes.
 *  Most modules won't need to look at this, but you may need to only return
 *  panes if the node satisfies certain properties.
 *
 * @return
 *   An array of possible forms, keyed by a unique ID. Each value is itself an 
 *   array of data, with the following key-value pairs:
 *     - 'label': (required) The human-readable name of the form.
 *     - 'description': (required) Extra information about the form.
 *     - 'callback': (required) The name of a function.
 *     - 'operations': (optional) A list of links for the user to perform 
 *        administrative tasks on this pane, either global or per-node settings.
 *        The format is an unkeyed array of link arrays (suitable for passing
 *        to theme_links).
 *        You may use %nid as a token in your link's href property to
 *        insert the node id of the current signup node. 
 *        The following optional keys are also available:
 *        - destination: if set to TRUE, the return destination will be appended
 *          to the link as a query string with drupal_get_destination, allowing
 *          the user to return to this page directly. Do not use if your link
 *          sends the user to a complex series of forms or pages.
 *        - not_defaults: if set to TRUE, this link will not be shown when 
 *          default signup settings are being edited at admin/settings/signup.
 *          Use this when your settings link would be meaningless in this 
 *          context because it is dependent on the current node.
 *
 * @see signup_basic_form_form.
 */
function signup_webform_signup_pane_info($node = NULL) {

  // Get the webform nodes.
  $result = db_query('SELECT n.title, n.nid FROM {node} n WHERE n.type = "webform" AND n.status = 1');
  while ($webform = db_fetch_object($result)) {
    $panes['webform_' . $webform->nid] = array(
      'label' => 'Webform: ' . $webform->title,
      'description' => t('Collects data for the webform.'),
      'callback' => 'signup_webform_form',
      'operations' => array(
        array(
          'href' => 'node/' . $webform->nid . '/edit',
          'title' => 'Edit webform',
          'destination' => TRUE,
        ),
        array(
          'href' => 'node/' . $webform->nid . '/edit/components',
          'title' => 'Edit components',
          'destination' => TRUE,
        ),
      ),
    );
  }
  return $panes;
}

/**
 * Signup form pane callback.
 *
 * @param &$signup_form
 *   The form array for the whole signup. You should not alter this, but it
 *   contains useful data depending on circumstances.
 * @param &$form_state
 *   Likewise.
 * @param $node
 *   The fully loaded node object.
 * @param $signup
 *   If this is an existing signup, the fully loaded signup object. If this is a 
 *   new signup, this is just NULL.
 * @param $pane_id
 *   The pane ID being invoked. This allows a module to implement multiple panes
 *   with one callback.
 * @param $signup_type
 *   Determines what kind of signup to generate a form for. Possible values:
 *    'auth' -- regular authenticated user signup form
 *    'anon' -- anonymous user signup form (includes required email field).
 *    'admin' -- admin form to signup another user (includes user selector).
 * @return
 *   A form API array for insertion into the signup form. 
 */
function signup_webform_form(&$signup_form, &$form_state, $node, $signup, $pane_id, $signup_type = 'auth') {
  $form = array();

  // Get the real nid from the prefixed pane ID and thence the node.
  $webform_nid = substr($pane_id, 8);
  $webform_node = node_load($webform_nid);
  module_load_include('inc', 'webform', 'webform_components');
  webform_load_components();

  // Load the webform submission for existing signups, so the webform API
  // takes care of putting in existing data.
  if (isset($signup) && $signup_type != 'anon') {
    $submission = signup_webform_get_signup_submission($signup, $webform_nid);
  }
  $component_tree = array();
  $page_count = 1;
  $page_num = 1;
  _webform_components_tree_build($webform_node->webform['components'], $component_tree, 0, $page_count);

  //dsm($component_tree);

  // Recursively add components to the form. Microweights keep things in webform order.
  // No idea what most of these do; _webform_client_form_add_component() is an undocumented black box!
  $microweight = 0.001;
  $enabled = TRUE;
  foreach ($component_tree['children'] as $cid => $component) {

    // we have no existing values here ever.
    $component_value = NULL;
    _webform_client_form_add_component($cid, $component, $component_value, $form, $form, $submission, $page_num, $enabled);
  }
  return $form;
}

/**
 * Implementation of hook_signup_data_alter().
 */
function signup_webform_signup_data_alter(&$signup, $form_values) {

  // Act on each pane that is a webform pane.
  foreach (_signup_webform_pane_ids($signup->form_data) as $pane_id) {

    // Load the webform API.
    // Do this inside the loop so signups that don't use webform don't load this.
    module_load_include('inc', 'webform', 'webform_submissions');

    // Get the real nid from the prefixed pane ID and thence the node.
    $webform_nid = substr($pane_id, 8);
    $webform_node = node_load($webform_nid);

    /*
    So... some missing documentation!
    webform_submission_insert($node, $submitted)
    expects this for $submitted, where keys are cids:
    *   Array
    *  (
    *      [1] => Array // Single checkbox TRUE/FALSE
    *          (
    *              [1] => 1
    *          )
    *
    *      [2] => Abbey Road // Textfield
    *      [3] => Array // Multiple select: checkboxes
    *          (
    *              [John] => John
    *              [Paul] => Paul
    *          )
    *
    *  )
    *
    */
    $component_lookup = _signup_webform_translate_cid_form_keys($webform_node);
    $submitted = array();
    $webform_data = $signup->form_data[$pane_id];
    foreach ($webform_data as $form_key => $component_data) {
      $cid = $component_lookup[$form_key];

      // Remove 0 values for unselected checkbox items
      if (is_array($component_data)) {
        $component_data = array_filter($component_data);
      }
      $submitted[$cid] = $component_data;
    }
    if (isset($signup->sid)) {

      // existing signup: update the webform submission with the new data.
      $submission_sid = db_result(db_query('SELECT submission_sid FROM {signup_webform_submission} WHERE signup_sid = %d AND webform_nid = %d', $signup->sid, $webform_nid));
      webform_submission_update($webform_node, $submission_sid, $submitted);

      // We store the sid so hook_signup_form_data_display() has something to work on.
      $signup->form_data[$pane_id] = array(
        'sid' => $submission_sid,
      );
    }
    else {

      // new signup: insert webform submission.
      $sid = webform_submission_insert($webform_node, $submitted);

      // Replace our data with just the submission ID; this is retrieved
      // by signup_webform_signup_insert() once the signup is saved and has
      // an ID.
      $signup->form_data[$pane_id] = array(
        'sid' => $sid,
      );
    }
  }
}

/**
 * Implementation of hook_signup_insert().
 *
 * Carries on from signup_webform_signup_data_alter(). Saves the relationship
 * between the signup and the webform submission into {signup_webform_submission},
 * which we couldn't do earlier in signup_webform_signup_data_alter() because
 * at that point there is no signup id.
 */
function signup_webform_signup_insert($signup) {
  foreach (_signup_webform_pane_ids($signup->form_data) as $pane_id) {

    // Retrieve the Submission id where we put it in the form data.
    $submission_sid = $signup->form_data[$pane_id]['sid'];
    $webform_nid = substr($pane_id, 8);
    db_query("INSERT INTO {signup_webform_submission} (signup_sid, webform_nid, submission_sid) VALUES (%d, %d, %d)", $signup->sid, $webform_nid, $submission_sid);
  }
}

/**
 * Implementation of hook_signup_form_data_display_alter().
 *
 * Alter signup form data prior to displaying signup records in, for example,
 * a node's list of signups.
 *
 * We remove the submission ID that is internal data.
 * Since webform does not yet have view support, we add in the webform 
 * submission data.
 * @todo: Change all this when webform gets views support!
 *
 * @param $form_data
 *  The user's signup data to alter.
 * @param $nid
 *  The node id for the signup-enabled node.
 * @param $sid
 *  The signup record id. WARNING: NOT the submission sid!
 * @param $uid
 *  The user id whose signup this is; 0 if this is an anonymous signup.
 * @param $type
 *  The type of output being prepared. Possible values are:
 *    - 'list': The hardcoded admin lists of signups, eg at node/X/signups/admin
 *    - 'view': The form data field in Views.
 *    - 'mail': Email output. This is likely the only one that needs special 
 *      handling; in this case, modules should be more generous about supplying
 *      data since there's no other place to see it.
 */
function signup_webform_signup_form_data_display_alter(&$form_data, $nid, $sid, $uid, $type = 'list') {

  //dsm($form_data);

  // Cache any webform components we load, as we are probably coming here
  // multiple times to show a set of signups that all involve the same webform.
  static $components = array();
  static $webform_loaded;
  foreach (_signup_webform_pane_ids($form_data) as $pane_id) {

    // Load the webform API.
    if (!isset($webform_loaded)) {
      module_load_include('inc', 'webform', 'webform_submissions');
      module_load_include('inc', 'webform', 'webform_components');
      webform_load_components();
      $webform_loaded = TRUE;
    }

    // Get the real nid from the prefixed pane ID.
    $webform_nid = substr($pane_id, 8);

    // Get the submission ID from the signup data and thence the submission.
    // We could get this from {signup_webform_submission}, but might as well
    // get it here.
    // @todo: when signup switches entirely to Views-based output, we could use
    // hook_views_data_alter() to add {signup_webform_submission}.submission_sid
    // as an additional field for the ['signup_log']['form_data'] field.
    $submission_sid = $form_data[$pane_id]['sid'];
    $submission = webform_get_submission($webform_nid, $submission_sid);

    // Check there is an actual submission: if no webform fields are compulsory,
    // a user can sign up and not create a corresponding webform submission.
    if (isset($submission)) {
      if (!isset($components[$webform_nid])) {
        $webform_node = node_load($webform_nid);

        // Cache the components for this webform node.
        $components[$webform_nid] = $webform_node->webform['components'];
      }
      foreach ($submission->data as $cid => $component_data) {
        $component = $components[$webform_nid][$cid];
        $label = $components[$webform_nid][$cid]['name'];

        // Hijack webform's email output theming: this, as far as I can tell,
        // is the only way there is of getting a component's data flattened to
        // a human-readable string.
        // @todo: textfields do not render properly here; I am assuming that
        // all single-values fields need their arrays exploding. Figure out
        // what is going on with webform and do this the correct way!
        if (count($component_data['value']) == 1) {
          $component_data['value'] = array_shift($component_data['value']);
        }
        $themed_output = theme('webform_mail_' . $component['type'], $component_data['value'], $component);

        // Use a numeric key rather than $label so the key is not output by
        // theme functions -- webform's output already includes the label.
        $form_data[$pane_id][] = $themed_output;
      }
    }

    // Remove the sid from display: it is internal data.
    unset($form_data[$pane_id]['sid']);
  }
}

/**
 * Implementation of hook_signup_cancel().
 *
 * When a signup is canceled, delete the corresponding webform submission.
 */
function signup_webform_signup_cancel($signup, $node) {
  $form_data = unserialize($signup->form_data);
  foreach (_signup_webform_pane_ids($form_data) as $pane_id) {

    // Load the webform API.
    module_load_include('inc', 'webform', 'webform_submissions');

    // Get the real nid from the prefixed pane ID and thence the node.
    $webform_nid = substr($pane_id, 8);
    $webform_node = node_load($webform_nid);

    // Get the submission ID from the signup data and thence the submission.
    $sid = $form_data[$pane_id]['sid'];
    $submission = webform_get_submission($webform_nid, $sid);

    // Delete the webform submission.
    webform_submission_delete($webform_node, $submission);

    // Delete the signup-webform submission relationship.
    db_query("DELETE FROM {signup_webform_submission} WHERE signup_sid = %d", $signup->sid);
  }
}

/**
 * Implementation of hook_nodeapi().
 *
 * When a whole signup node is deleted, delete data related to it.
 */

/* 
// needs work.
function signup_webform_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
  if ($op == 'delete' && isset($node->signup_form_panes)) {
    $signup_nid = $node->nid;
    // Act on records pertaining to each webform that this signup is linked to.
    foreach (_signup_webform_pane_ids($node->signup_form_panes) as $pane_id) {
      // Load the webform API.
      module_load_include('inc', 'webform', 'webform_submissions');
      
      $webform_nid = substr($pane_id, 8);
      $webform_node = node_load($webform_nid);
      
      // One webform may be used in several signup nodes. So we need to get 
      // *only* the webform submissions that came from the signup node being deleted.
      // @todo BUG!!! this doesn't seem to work at all!
      // do we even have {signup_log} any more at this point?? 
      // may be module weight issues here -- urgh!
      $result = db_query('SELECT sws.submission_sid FROM {signup_log} sl INNER JOIN {signup_webform_submission} sws ON sl.sid = sws.signup_sid WHERE sl.nid = %d', $signup_nid);
      // Act on each submission.
      while ($submission_data = db_fetch_array($result)) {
        // Load the submission in order to delete it... 
        $submission = webform_get_submission($webform_nid, $submission_data['sid']);
        webform_submission_delete($webform_node, $submission);
        // Delete the signup-submission link for this submission.
        // @todo: replace this with a DELETE SELECT query outside this loop, joining
        // to {signup_log} if possible.
        db_query('DELETE FROM {signup_webform_submission} WHERE submission_sid = %d', $submission_data['sid']);
      }
    }
  }
}
*/

/**
 * Implementation of hook_form_FORM_ID_alter().
 *
 * If a webform node is about to be deleted, and it is being used as a pane
 * in one or more signup-enabled nodes, prevent deletion and explain why.
 */
function signup_webform_form_node_delete_confirm_alter(&$form, $form_state) {
  $node = $form['#parameters'][2];
  if ($node->type == 'webform') {
    $webform_nid = $node->nid;
    $pane_id = 'webform_' . $node->nid;

    // Get the signup nodes the pane based on this webform is used in.
    // A left join gets us the special case where nid is 0 which represents the
    // signup defaults.
    $result = db_query("SELECT n.nid, n.title FROM {signup_panes} sp LEFT JOIN {node} n ON sp.nid = n.nid WHERE sp.pane_id = '%s'", $pane_id);
    while ($signup_node = db_fetch_object($result)) {
      if ($signup_node->nid == 0) {
        $text = t('Signup default settings');
        $items[] = user_access('administer all signups') ? l($text, 'admin/settings/signup') : $text;
      }
      else {
        $items[] = l($signup_node->title, 'node/' . $signup_node->nid);
      }
    }
    if (count($items)) {

      // Overwrite the description text and append the list of signup nodes.
      $form['description']['#value'] = t('This webform can not be deleted because it is currently used in the following signup-enabled content:');
      $form['description']['#suffix'] = theme('item_list', $items);
      drupal_set_title(t('Unable to delete %title', array(
        '%title' => $node->title,
      )));
      unset($form['actions']['submit']);
    }
  }
}

/**
 * Get the webform submission record for a given signup record.
 *
 * @param $signup
 *  The signup record object, as generally swilled around the place. 
 * @param $webform_nid
 *  The node ID of the webform node in question.
 * @return
 *  A webform submission object, as returned by webform_get_submission().
 */
function signup_webform_get_signup_submission($signup, $webform_nid) {

  // Beware: both pieces of data we care about here are called sid!
  $submission_sid = db_result(db_query('SELECT submission_sid FROM {signup_webform_submission} WHERE signup_sid = %d AND webform_nid = %d', $signup->sid, $webform_nid));

  // Load the webform API.
  module_load_include('inc', 'webform', 'webform_submissions');
  $submission = webform_get_submission($webform_nid, $submission_sid);
  return $submission;
}

/**
 * Helper function to make a lookup array of form keys => cid.
 * 
 * @param $webform_node
 *  Just feed this the entire webform $node object. 
 */
function _signup_webform_translate_cid_form_keys($webform_node) {
  $components = $webform_node->webform['components'];
  foreach ($components as $component) {
    $data[$component['form_key']] = $component['cid'];
  }
  return $data;
}

/**
 * Helper function to get the webform panes from an array on pane IDs.
 * 
 * Same pattern as element_children().
 *
 * @param $signup_data
 *  An array whose keys are pane IDs. Probably $signup->form_data.
 * @return
 *  An array of the keys that are IDs for webform panes.
 */
function _signup_webform_pane_ids($signup_data) {
  $return = array();
  foreach ($signup_data as $pane_id => $data) {
    if (substr($pane_id, 0, 7) == 'webform') {
      $return[] = $pane_id;
    }
  }
  return $return;
}

Functions

Namesort descending Description
signup_webform_form Signup form pane callback.
signup_webform_form_node_delete_confirm_alter Implementation of hook_form_FORM_ID_alter().
signup_webform_get_signup_submission Get the webform submission record for a given signup record.
signup_webform_signup_cancel Implementation of hook_signup_cancel().
signup_webform_signup_data_alter Implementation of hook_signup_data_alter().
signup_webform_signup_form_data_display_alter Implementation of hook_signup_form_data_display_alter().
signup_webform_signup_insert Implementation of hook_signup_insert().
signup_webform_signup_pane_info Implementation of hook_signup_pane_info().
_signup_webform_pane_ids Helper function to get the webform panes from an array on pane IDs.
_signup_webform_translate_cid_form_keys Helper function to make a lookup array of form keys => cid.