Index: INSTALL.txt
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/signup/INSTALL.txt,v
retrieving revision 1.15
diff -u -p -r1.15 INSTALL.txt
--- INSTALL.txt	22 Jan 2009 18:59:28 -0000	1.15
+++ INSTALL.txt	18 Oct 2010 21:38:19 -0000
@@ -14,11 +14,8 @@ the Drupal package and read the online d
 
 2. (Optional) Customize the form presented to users when signing up
    for content on your site.  Unfortuantely, there is not yet a way to
-   do this from within your site, you must modify a theme function to
-   change this form.  See the comment above "theme_signup_user_form()"
-   in the theme/signup_form.inc file.  If you want to change the form,
-   you should define your own version of theme_signup_user_form() in
-   your site's theme (e.g. as phptemplate_signup_user_form()).
+   do this from within your site, you must implement hook_signup_pane_info in
+   a module to register a callback that returns form elements.
 
 
 3. Enable the signup module by navigating to:
Index: README.txt
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/signup/README.txt,v
retrieving revision 1.15
diff -u -p -r1.15 README.txt
--- README.txt	24 Jul 2009 17:47:47 -0000	1.15
+++ README.txt	18 Oct 2010 21:38:19 -0000
@@ -12,6 +12,13 @@ setting), and automatically close signup
 (general setting).  There are settings for restricting signups to
 selected roles and content types.
 
+Further information can be collected when users sign up to a node: several
+included modules provide extra panes that can be added to a signup form. 
+These allow the collection of the user's name and phone number, fields 
+from Drupal core's Profile module, and fields from a form created with the
+Webform module (http://druapl.org/project/webform). An API allows the creation 
+of customized modules to collect any kind of data in the signup form.
+
 There is support for both registered and anonymous users to sign up.
 Both can receive confirmation and reminder emails, and registered
 users are optionally able to cancel or edit their own signups and view
Index: signup.api.php
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/signup/signup.api.php,v
retrieving revision 1.5
diff -u -p -r1.5 signup.api.php
--- signup.api.php	19 Sep 2009 01:42:52 -0000	1.5
+++ signup.api.php	18 Oct 2010 21:38:19 -0000
@@ -8,11 +8,64 @@
  */
 
 /**
+ * Hook to define 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 hook_signup_pane_info($node = NULL) {
+  return array(
+    'basic' => array(
+      'label' => 'Basic form',
+      'description' => 'Collects name and phone number.',
+      'callback' => 'signup_basic_form_form',
+    ),  
+  );
+}
+
+/**
  * Hook to alter signup data before a signup is inserted or updated.
  *
  * @param $signup
  *   Reference to the fully-loaded signup object representing the signup.
- * @param $form_values
+ *   Modules implementing this hook may prevent a signup from going ahead by
+ *   setting $signup->block_signup = TRUE.
+ *   It is up to the implementing module to provide a message to the user to
+ *   explain why their attempt to sign up has failed.
+* @param $form_values
  *   Array of form values (if any) from the signup being inserted or updated.
  */
 function hook_signup_data_alter(&$signup, $form_values) {
@@ -80,7 +133,7 @@ function hook_signup_update($signup) {
  *
  * This hook allows other modules to inject information into the custom signup
  * data for each signup.  The array is merged with the values of any custom
- * fields from theme_signup_user_form(), serialized, and stored in the
+ * fields from hook_signup_pane_info(), serialized, and stored in the
  * {signup_log} database table.
  *
  * @param $node
@@ -94,7 +147,7 @@ function hook_signup_update($signup) {
  *   should be human-readable (and wrapped in t() to allow translation).
  *
  * @see signup_sign_up_user()
- * @see theme_signup_user_form()
+ * @see hook_signup_pane_info()
  */
 function hook_signup_sign_up($node, $account) {
   return array(
@@ -207,3 +260,33 @@ function hook_signup_menu_access($node, 
   // powers over events in their group.
 }
 
+/**
+ * Implementation of hook_signup_form_data_display_alter().
+ *
+ * Allow modules to alter signup form data prior to displaying signup records
+ * in, for example, a node's list of signups at node/N/signups/admin.
+ *
+ * This allows modules that implement signup panes for format or even inject 
+ * their own data.
+ *
+ * @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.
+ * @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 hook_signup_form_data_display_alter(&$form_data, $nid, $sid, $uid, $type = 'list') {
+  foreach ($form_data as $pane_id => $pane_data) {
+    // If this is one of your panes, do stuff to it.
+  }
+}
Index: signup.install
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/signup/signup.install,v
retrieving revision 1.30
diff -u -p -r1.30 signup.install
--- signup.install	19 Sep 2009 01:42:52 -0000	1.30
+++ signup.install	18 Oct 2010 21:38:20 -0000
@@ -117,7 +117,7 @@ function signup_schema() {
         'default' => 0,
       ),
       'form_data' => array(
-        'description' => t('Serialized string of additional signup form values.  See theme_signup_user_form() from theme/signup.theme for more information.'),
+        'description' => t('Serialized string of additional signup form values.  See hook_signup_pane_info() for more information.'),
         'type' => 'text',
         'size' => 'big',
         'not null' => TRUE,
@@ -141,6 +141,46 @@ function signup_schema() {
     ),
   );
 
+  $schema['signup_panes'] = array(
+    'description' => t('Signup panes for each signup node.'),
+    'fields' => array(
+      'nid' => array(
+        'description' => t('Node ID'),
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'pane_id' => array(
+        'description' => t('Pane ID as defined by hook_signup_pane_info().'),
+        'type' => 'varchar',
+        'length' => 32,
+        'not null' => TRUE,
+        'default' => '',
+      ),
+      'callback' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => "The name of the function that renders the pane.",
+      ),
+      'weight' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'size' => 'tiny',
+        'description' => 'The weight of this pane in relation to other panes for the node.',
+      ),
+    ),
+    'indexes' => array(
+      'nid'        => array('nid'),
+      ),
+    'unique keys' => array(
+      'nid_pane_id' => array('nid', 'pane_id'),
+    ),
+  );
+
   return $schema;
 }
 
@@ -482,3 +522,22 @@ function signup_update_6003() {
   return $ret;
 }
 
+/**
+ * Create the {signup_panes} table, enable the signup_basic_form module,
+ * and add the basic form to all existing nodes.
+ */
+function signup_update_6200() {
+  $ret = array();
+  
+  $schema = signup_schema();
+  db_create_table($ret, 'signup_panes', $schema['signup_panes']);
+  
+  module_enable('signup_basic_form');
+  
+  $ret[] = db_query("INSERT INTO {signup_panes} (nid, pane_id, callback, weight) SELECT nid, 'basic', 'signup_basic_form_form', 0 FROM {signup}");
+
+  $ret[] = array('success' => TRUE, 'query' => t('The Signup basic form module has been enabled and the basic "Name and Phone number" pane has been added to your existing signup-enabled content.'));
+  
+  return $ret;
+}
+
Index: signup.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/signup/signup.module,v
retrieving revision 1.242
diff -u -p -r1.242 signup.module
--- signup.module	8 Oct 2009 00:01:24 -0000	1.242
+++ signup.module	18 Oct 2010 21:38:20 -0000
@@ -151,13 +151,6 @@ function signup_theme() {
         'node' => NULL,
       ),
     ),
-    'signup_user_form' => array(
-      'file' => 'signup_form.inc',
-      'path' => $path,
-      'arguments' => array(
-        'node' => NULL,
-      ),
-    ),
     'signup_anonymous_username' => array(
       'file' => 'signup_form.inc',
       'path' => $path,
@@ -173,6 +166,11 @@ function signup_theme() {
         'view' => NULL,
       ),
     ),
+    'signup_node_settings_form_panes' => array(
+      'file' => 'node_settings.inc',
+      'path' => drupal_get_path('module', 'signup') . '/includes',
+      'arguments' => array('form'),
+    ),
   );
 }
 
@@ -744,6 +742,7 @@ function signup_nodeapi(&$node, $op, $te
     case 'delete':
         // Clean up the signup tables for the deleted node.
         db_query("DELETE FROM {signup} WHERE nid = %d", $node->nid);
+        db_query("DELETE FROM {signup_panes} WHERE nid = %d", $node->nid);
         db_query("DELETE FROM {signup_log} WHERE nid = %d", $node->nid);
       break;
 
@@ -768,6 +767,15 @@ function signup_nodeapi(&$node, $op, $te
           $node->signup_total = db_result(db_query("SELECT COUNT(*) FROM {signup_log} WHERE nid = %d", $node->nid));
           $node->signup_effective_total = db_result(db_query("SELECT SUM(count_towards_limit) FROM {signup_log} WHERE nid = %d", $node->nid));
         }
+        // Load pane data
+        $result = db_query("SELECT * FROM {signup_panes} WHERE nid = %d ORDER BY weight", $node->nid);
+        $node->signup_form_panes = array();
+        while ($pane_data = db_fetch_array($result)) {
+          $node->signup_form_panes[$pane_data['pane_id']] = array(
+            'weight' => $pane_data['weight'],
+            'callback' => $pane_data['callback'],
+          );
+        }
       }
       else {
         $node->signup = 0;
@@ -1177,8 +1185,9 @@ function signup_content_types() {
  *   highly recommended to call the signup_validate_anon_email
  *   function in the external module's validation cycle or perform
  *   that function's validation logic prior to passing in this element!
- * $signup_form['signup_form_data'] : an array of key/value pairs --
- *   key is the data category, value is the user input
+ * $signup_form['signup_form_data'] : an associative array of extra data, 
+ *   keyed by signup form pane id. The value is itself an array in the same
+ *   format as a $form_values array, that is, form_element => value.
  *
  * @param $notify_user
  *   When set to TRUE, confirmation messages are displayed via
@@ -1261,11 +1270,23 @@ function signup_sign_up_user($signup_for
     // Invoke hook_signup_data_alter() to let other modules change this.
     drupal_alter('signup_data', $signup, $signup_form);
 
+    // Altering modules may set $signup->block_signup = TRUE to prevent signup.
+    if ($signup->block_signup == TRUE) {
+      return FALSE;
+    }
+
     // Insert the signup into the {signup_log} table.
     signup_save_signup($signup);
 
     // See if we should generate any notifications from this signup.
     if ($notify_user) {
+      // Invoke hook_signup_form_data_display_alter() to let other modules
+      // (in particular pane modules) alter the data for output.
+      // This may involve internal data being removed.
+      $form_data = $signup->form_data;
+      drupal_alter('signup_form_data_display', $form_data, $node->nid, $signup->sid, $signup->uid, 'mail');
+      $signup->form_data = $form_data;
+
       // Confirmation e-mail to the user who signed up.
       if ($node->signup_send_confirmation) {
         signup_send_confirmation_mail($signup, $node);
@@ -1604,6 +1625,8 @@ function _signup_get_username($signup, $
       $signup_data = unserialize($signup->form_data);
     }
   }
+  // Allow other modules to add their data.
+  drupal_alter('signup_form_data_display', $signup_data, $signup->nid, $signup->sid, $signup->uid);
   // Determine if this is an anon signup or not, and get the right info.
   if (!empty($signup->anon_mail)) {
     $user_name = theme('signup_anonymous_username', $signup_data, $signup->anon_mail);
Index: includes/admin.settings.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/signup/includes/admin.settings.inc,v
retrieving revision 1.7
diff -u -p -r1.7 admin.settings.inc
--- includes/admin.settings.inc	24 Jan 2009 08:52:39 -0000	1.7
+++ includes/admin.settings.inc	18 Oct 2010 21:38:20 -0000
@@ -210,6 +210,8 @@ function signup_settings_form_submit($fo
     $values[] = isset($form_state['values']['signup_reminder_email']) ? $form_state['values']['signup_reminder_email'] : '';
     $values[] = 0;  // "nid" of the row in {signup} for the global settings.
     db_query("UPDATE {signup} SET forwarding_email = '%s', send_confirmation = %d, confirmation_email = '%s', close_signup_limit = %d, send_reminder = %d, reminder_days_before = %d, reminder_email = '%s' WHERE nid = %d", $values);
+    // Handle the pane settings.
+    _signup_node_settings_panes_save($form_state['values']['signup_form_panes'], 0);
   }
   else {
     module_load_include('install', 'signup', 'signup');
Index: includes/node_admin.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/signup/includes/node_admin.inc,v
retrieving revision 1.7
diff -u -p -r1.7 node_admin.inc
--- includes/node_admin.inc	14 Jan 2009 18:11:18 -0000	1.7
+++ includes/node_admin.inc	18 Oct 2010 21:38:20 -0000
@@ -84,7 +84,13 @@ function signup_node_admin_details_form(
     $users[$key] = '';
     $form['username'][$key] = array('#value' => $username);
     $form['signup_date'][$key] = array('#value' => format_date($signed_up_user->signup_time, variable_get('signup_date_format', 'small')));
-    $form['signup_form_data'][$key] = array('#value' => theme('signup_custom_data', unserialize($signed_up_user->form_data)));
+    // Unpack the stored signup form data.
+    $signup_form_data = unserialize($signed_up_user->form_data);
+    // Invoke hook_signup_form_data_display_alter() to let other modules
+    // (in particular pane modules) alter the data for output.
+    // This may involve internal data being removed.
+    drupal_alter('signup_form_data_display', $signup_form_data, $node->nid, $key, $signed_up_user->uid, 'list');
+    $form['signup_form_data'][$key] = array('#value' => theme('signup_custom_data', $signup_form_data));
     $form['attended'][$key] = array('#value' => theme('signup_attended_text', $signed_up_user->attended));
   }
   if (empty($users)) {
Index: includes/node_form.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/signup/includes/node_form.inc,v
retrieving revision 1.6
diff -u -p -r1.6 node_form.inc
--- includes/node_form.inc	16 Sep 2009 00:42:50 -0000	1.6
+++ includes/node_form.inc	18 Oct 2010 21:38:20 -0000
@@ -68,6 +68,8 @@ function signup_save_node($node, $op) {
     $values = db_fetch_array(db_query("SELECT forwarding_email, send_confirmation, confirmation_email, close_signup_limit, send_reminder, reminder_days_before, reminder_email FROM {signup} WHERE nid = 0"));
     $values[] = $node->nid;
     db_query("INSERT INTO {signup} (forwarding_email, send_confirmation, confirmation_email, close_signup_limit, send_reminder, reminder_days_before, reminder_email, nid) VALUES ('%s', %d, '%s', %d, %d, %d, '%s', %d)", $values);
+    // Copy the signup panes defaults to the new node.
+    db_query("INSERT INTO {signup_panes} (nid, pane_id, callback, weight) SELECT %d, pane_id, callback, weight FROM {signup_panes} WHERE nid = 0", $node->nid);
   }
 
   $node = node_load($node->nid);
Index: includes/node_settings.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/signup/includes/node_settings.inc,v
retrieving revision 1.5
diff -u -p -r1.5 node_settings.inc
--- includes/node_settings.inc	21 Sep 2009 05:23:02 -0000	1.5
+++ includes/node_settings.inc	18 Oct 2010 21:38:21 -0000
@@ -46,6 +46,96 @@ function signup_node_settings_form($form
     $node->signup_reminder_days_before = $result->reminder_days_before;
     $node->signup_reminder_email = $result->reminder_email;
     $node->signup_close_signup_limit = $result->close_signup_limit;
+    // Load pane data
+    $result = db_query("SELECT * FROM {signup_panes} WHERE nid = 0 ORDER BY weight");
+    $node->signup_form_panes = array();
+    while ($pane_data = db_fetch_array($result)) {
+      $node->signup_form_panes[$pane_data['pane_id']] = array(
+        'weight' => $pane_data['weight'],
+        'callback' => $pane_data['callback'],
+      );
+    }
+  }
+  
+  // Get information about all potential form panes from modules that 
+  // implement hook_signup_pane_info.
+  $signup_form_pane_info = module_invoke_all('signup_pane_info', $node);
+  
+  // Put in weights from either saved settings or for panes not enabled on this 
+  // node a default heavy weight of 10.
+  foreach ($signup_form_pane_info as $id => $signup_form_pane) {
+    $signup_form_pane_info[$id]['weight'] = isset($node->signup_form_panes[$id]) ? $node->signup_form_panes[$id]['weight'] : 10;
+  }
+  // Sort the list by weight so it appears correctly in the UI.
+  uasort($signup_form_pane_info, '_signup_cmp_weight'); 
+  
+  // The label and the theme function for the form panes table.
+  $form['signup_form_panes'] = array(
+    // This gets a label on the whole table; not sure this is the best approach though.
+    // Improvements welcome.
+    '#type' => 'item',
+    '#title' => t('Signup form panes'),
+    '#tree' => TRUE,
+    '#description' => t('Enable the signup form panes you want to use on this content. You may also drag enabled panes into any order. The position of disabled panes in the list is ignored.'),
+    '#theme' => 'signup_node_settings_form_panes',
+  );
+  // Add a warning if there are already existing signups.
+  if ($node->signup_effective_total > 0) {
+     $form['signup_form_panes']['#description'] .= t(' <span class="warning">There are existing signups recorded. If you disable panes their data will remain in your records; if you enable panes then past signups will not have any data for them.</span>');
+  }
+  
+  // The draggable table of signup panes.
+  // @see hook_signup_pane_info() for information on the array structure here.
+  $href_replace_nid = isset($node->nid) ? $node->nid : 0;  
+  foreach ($signup_form_pane_info as $id => $signup_form_pane) {
+    // Stash the callback as a hidden value for the submit handler to find.
+    $form['signup_form_panes'][$id]['callback'] = array(
+      '#type' => 'value', 
+      '#value' => $signup_form_pane['callback'],
+    );
+    $form['signup_form_panes'][$id]['enabled'] = array(
+      '#type' => 'checkbox',
+      '#default_value' => isset($node->signup_form_panes[$id]),
+      // Don't set the #title: we want it in a separate column. 
+    );
+    $form['signup_form_panes'][$id]['label'] = array(
+      '#value' => $signup_form_pane['label'],
+    );
+    $form['signup_form_panes'][$id]['description'] = array(
+      '#value' => $signup_form_pane['description'],
+    );
+    // Signup panes may define links to operations for individual configuration.
+    if (isset($signup_form_pane['operations'])) {
+      foreach ($signup_form_pane['operations'] as $op_key => $operation) {
+        // Operations get the token %nid in their href replaced with the nid of 
+        // the current node or 0 for defaults.
+        $signup_form_pane['operations'][$op_key]['href'] = str_replace('%nid', $href_replace_nid, $signup_form_pane['operations'][$op_key]['href']);
+        // Operations may request to have the return destination appended to their link.
+        if (isset($operation['destination'])) {
+          $signup_form_pane['operations'][$op_key]['query'] .= drupal_get_destination();
+        }
+        // Operations with 'not_defaults' are skipped when there is no node.
+        if (!isset($node->nid) && isset($operation['not_defaults'])) {
+          unset($signup_form_pane['operations'][$op_key]);
+        }
+         $form['signup_form_panes'][$id]['operations'] = array(
+          // This array is rendered by theme_links() in the form's theme function.
+          '#value' => $signup_form_pane['operations'],
+        );
+      }
+    }
+    $form['signup_form_panes'][$id]['weight'] = array(
+      '#type' => 'weight',
+      '#delta' => 10,      
+      '#default_value' => $signup_form_pane['weight'],
+      '#attributes' => array(
+        'class' => "signup-pane-weight",
+      ),
+    );
+  }
+  // Unset weight item if only 1 element
+  if (count($signup_form_pane_info) == 1) {
+    unset($form['signup_form_panes'][$id]['weight']);
   }
 
   $form['signup_forwarding_email'] = array(
@@ -145,6 +235,73 @@ function signup_node_settings_page($node
 }
 
 /**
+ * Helper function to sort signup panes by their weight key.
+ */
+function _signup_cmp_weight($a, $b) {
+  return $a['weight'] - $b['weight'];
+}
+
+/**
+ * Theme function for the panes table in the node settings form.
+ */
+function theme_signup_node_settings_form_panes($form) {
+  // Based on taxonomy.admin.inc
+  foreach (element_children($form) as $key) {
+    $row = array();
+    $row[] = drupal_render($form[$key]['enabled']);
+    if (isset($form[$key]['weight'])) {
+      $form[$key]['weight']['#attributes']['class'] = 'signup-pane-weight';
+      $row[] = drupal_render($form[$key]['weight']);
+    }
+    $row[] = drupal_render($form[$key]['label']);
+    $row[] = drupal_render($form[$key]['description']);
+    // Render all the operation links as a themed list of links.
+    // Changing what's in #value here seems a bit hackish, but AFAIK it's the only
+    // way to do the theming here rather than in the form builder.
+    // These all go in a single table cell because calculating colspan for a variable
+    // number of operations is not pretty and not worth it.
+    if (isset($form[$key]['operations'])) {
+      $form[$key]['operations']['#value'] = theme('links', $form[$key]['operations']['#value']);
+      $row[] = drupal_render($form[$key]['operations']);
+    }
+    else {
+      $row[] = '';
+    }
+        
+    $rows[] = array(
+      'data' => $row,
+      'class' => 'draggable',
+    );
+  }
+  // A message if there are no rows.
+  // @todo: figure out why the form label doesn't show in this case.
+  if (!count($rows)) {
+    $rows[] = array(
+      array(
+        'data' => t('No form panes are available.'),
+        'colspan' => 3,
+      )
+    );
+  }
+  
+  $header[] = t('Enabled');
+  if (isset($form[$key]['weight'])) {
+    $header[] = t('Weight');
+  }
+  $header[] = t('Name');
+  $header[] = t('Description');
+  $header[] = t('Operations');
+  
+  if (isset($form[$key]['weight'])) {  
+    drupal_add_tabledrag('signup-node-settings-panes', 'order', 'sibling', 'signup-pane-weight');
+  }
+    
+  $output = drupal_render($form); // ???
+  $output .=  theme('table', $header, $rows, array('id' => 'signup-node-settings-panes'));
+  return $output;
+}
+
+/**
  * Submit handler for the per-node signup settings form.
  *
  * @param $form_id
@@ -153,16 +310,23 @@ function signup_node_settings_page($node
  *   The constructed form values array of the submitted form.
  */
 function signup_node_settings_form_submit($form, &$form_state) {
+  $node = $form['#node'];
   $op = isset($form_state['values']['op']) ? $form_state['values']['op'] : '';
   if ($op == t('Reset to defaults')) {
     // If we're resetting, we just want to grab the site-wide defaults.
     $values = db_fetch_array(db_query("SELECT forwarding_email, send_confirmation, confirmation_email, close_signup_limit, send_reminder, reminder_days_before, reminder_email FROM {signup} WHERE nid = 0"));
+    // Clear all the panes for the node.
+    db_query("DELETE FROM {signup_panes} WHERE nid = %d", $node->nid);
   }
   else {
+    // Handle the pane settings.
+    _signup_node_settings_panes_save($form_state['values']['signup_form_panes'], $node->nid);
+    
     // Populate $values from $form_state.
     foreach (array('forwarding_email', 'send_confirmation', 'confirmation_email', 'close_signup_limit') as $setting) {
       $values[$setting] = $form_state['values']["signup_$setting"];
     }
+   
     // If we're dealing with a node that doesn't have a start time, these
     // fields are missing from the signup settings form, so we can't assume
     // they're defined.
@@ -173,7 +337,6 @@ function signup_node_settings_form_submi
 
   // Either way, we want to make sure we're updating the values for the
   // current node, not nid 0...
-  $node = $form['#node'];
   $values[] = $node->nid;
   db_query("UPDATE {signup} SET forwarding_email = '%s', send_confirmation = %d, confirmation_email = '%s', close_signup_limit = %d, send_reminder = %d, reminder_days_before = %d, reminder_email = '%s' WHERE nid = %d", $values);
 
@@ -192,3 +355,49 @@ function signup_node_settings_form_submi
   }
 }
 
+/**
+ * Helper function to save pane settings.
+ *
+ * Called from the form submit handlers for node settings and sitewide defaults.
+ *
+ * @param $form_state_values
+ *  The $form_state['values']['signup_form_panes'] array from the calling
+ *  form submit handler.
+ * @param $nid
+ *  The nid of the node this signup is attached to. May also be 0 for the case
+ *  that the defaults are being saved. This value should be used regardless.
+ */
+function _signup_node_settings_panes_save($form_state_values, $nid) {
+  // WTF new nodes come here with ALL WEIGHTS set at 10???
+  // @todo: new nodes probably need arbitrary weights setting?
+  if (isset($form_state_values)) {
+    foreach ($form_state_values as $pane_id => $form_state_values_panes) {
+      if ($form_state_values_panes['enabled']) {
+        $signup_panes[$pane_id] = $form_state_values_panes['weight'];
+      }
+    }
+  }
+  // Clear all the panes for the node.
+  db_query("DELETE FROM {signup_panes} WHERE nid = %d", $nid);
+
+  // If there are enabled panes, save them.
+  if (count($signup_panes)) {
+    // Give a singleton pane a weight value, arbitrarily set to 0.
+    if (count($signup_panes) == 1) {
+      // Convoluted way of getting the first (and only!) key to set the value to 0
+      // @todo Suggestions for improvement welcome!
+      reset($signup_panes);
+      $pane_id = key($signup_panes);
+      $signup_panes[$pane_id] = 0;
+    }
+
+    // Sort by weight, preserve keys.
+    // obsolete asort($signup_panes);
+    foreach ($signup_panes as $pane_id => $weight) {
+      // Store the signup pane information. Note the callback is retrieve from
+      // form values where we stashed it earlier.
+      db_query("INSERT INTO {signup_panes} (nid, pane_id, callback, weight) VALUES (%d, '%s', '%s', %d)", 
+        $nid, $pane_id, $form_state_values[$pane_id]['callback'], $weight);
+    }
+  }
+}
Index: includes/signup_edit_form.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/signup/includes/signup_edit_form.inc,v
retrieving revision 1.7
diff -u -p -r1.7 signup_edit_form.inc
--- includes/signup_edit_form.inc	19 Sep 2009 00:31:56 -0000	1.7
+++ includes/signup_edit_form.inc	18 Oct 2010 21:38:21 -0000
@@ -85,24 +85,43 @@ function signup_edit_form($form_state, $
     );
   }
 
-  // Build the themed signup form for this site and include that.
-  $site_form = theme('signup_user_form', $node);
-  $form_data = unserialize($signup->form_data);
-
-  // This is sort of a hack, but we don't support nested arrays for the custom
-  // signup form anyway, so it works for now.  Obviously all this will change
-  // with signup_fields and friends, but for now it works.
-  foreach ($form_data as $key => $value) {
-    if (!empty($site_form['signup_form_data'][$key])) {
-      $site_form['signup_form_data'][$key]['#default_value'] = $value;
-      if (!$can_edit) {
-        // If they can't edit, mark all the fields as disabled.
-        $site_form['signup_form_data'][$key]['#disabled'] = TRUE;
+  // Build all the signup panes for this node.
+  if (isset($node->signup_form_panes)) {
+    $form['elements']['signup_form_data'] = array(
+      '#tree' => TRUE,
+    );
+    foreach (array_keys($node->signup_form_panes) as $pane_id) {
+      $callback = $node->signup_form_panes[$pane_id]['callback'];
+      if (function_exists($callback)) {
+        // Each pane goes into its own form element with #tree set
+        // to protect form values from clashing.
+        $form['elements']['signup_form_data'][$pane_id] = array(
+          '#tree' => TRUE,
+        );
+        // Add a validation function for each pane.
+        $form['elements']['signup_form_data']['#element_validate'][] = $callback . '_validate';
+        $form['elements']['signup_form_data'][$pane_id] += $callback($form, $form_state, $node, $signup, $pane_id, $signup_type);
       }
     }
   }
-  $form['elements'] += $site_form;
 
+  $form_data = unserialize($signup->form_data);
+  // This is *still* sort of a hack, and means that nested arrays in signup 
+  // form panes won't work. 
+  // Does Drupal FormAPI provide a means to re-fill a $form array with values
+  // from a $form_values array? It really should.
+  foreach ($form_data as $pane_id => $pane_data) {
+    foreach ($pane_data as $key => $value) {
+      if (!empty($form['elements']['signup_form_data'][$pane_id][$key])) {
+        // The pane callback may have provided its own default values: if so
+        // then don't clobber them.
+        if (!isset($form['elements']['signup_form_data'][$pane_id][$key]['#default_value'])) {
+          $form['elements']['signup_form_data'][$pane_id][$key]['#default_value'] = $value;
+        }
+      }
+    }
+  }
+  
   // Add the appropriate buttons based on permissions.
   if ($can_edit) {
     $form['elements']['save'] = array(
@@ -111,6 +130,11 @@ function signup_edit_form($form_state, $
       '#submit' => array('signup_edit_form_save_submit'),
     );
   }
+  else {
+    // If they can't edit, mark all the fields as disabled.
+    _signup_form_element_disable_recursive($form['elements']['signup_form_data']);
+  }
+  
   if ($can_cancel) {
     if (isset($_REQUEST['destination'])) {
       $destination = drupal_get_destination();
@@ -130,6 +154,20 @@ function signup_edit_form($form_state, $
 }
 
 /**
+ * Helper function to recursively disable all elements of a form.
+ */
+function _signup_form_element_disable_recursive(&$form) {
+  if (isset($form['#type'])) {
+    $form['#disabled'] = TRUE;    
+  }
+  foreach (element_children($form) as $key) {
+    if (is_array($form[$key])) {
+      _signup_form_element_disable_recursive($form[$key]);
+    }
+  }
+}
+
+/**
  * Validation callback when editing the anonymous email for an existing signup.
  */
 function signup_validate_anon_mail($form, &$form_state) {
Index: includes/signup_form.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/signup/includes/signup_form.inc,v
retrieving revision 1.2
diff -u -p -r1.2 signup_form.inc
--- includes/signup_form.inc	20 Dec 2008 06:31:22 -0000	1.2
+++ includes/signup_form.inc	18 Oct 2010 21:38:21 -0000
@@ -75,17 +75,27 @@ function signup_form(&$form_state, $node
     $signup_form += $admin_form;
   }
 
-  // Build the themed signup form for this site and include that.
-  $signup_themed_form = theme('signup_user_form', $node);
-
-  if ($signup_type == 'admin') {
-    // Special case hack for the default signup form, where the current
-    // username is being filled in as the default for the 'Name' field.
-    if (!empty($signup_themed_form['signup_form_data']['Name']['#default_value'])) {
-      unset($signup_themed_form['signup_form_data']['Name']['#default_value']);
+  // Insert the form components provided by other modules.
+  if (isset($node->signup_form_panes)) {
+    $signup_form['signup_form_data'] = array(
+      '#tree' => TRUE,
+    );
+    // Add each pane to the form. The node settings are stored in the weighted
+    // order so there is no need for further sorting here.
+    foreach ($node->signup_form_panes as $pane_id => $pane_data) {
+      $callback = $pane_data['callback'];
+      if (function_exists($callback)) {
+        // Each pane goes into its own form element with #tree set
+        // to protect form values from clashing.
+        $signup_form['signup_form_data'][$pane_id] = array(
+          '#tree' => TRUE,
+        );
+        // Add a validation function for each pane.
+        $signup_form['signup_form_data']['#element_validate'][] = $callback . '_validate';
+        $signup_form['signup_form_data'][$pane_id] += $callback($signup_form, $form_state, $node, NULL, $pane_id, $signup_type);
+      }
     }
   }
-  $signup_form += $signup_themed_form;
 
   $form['collapse']['signup_user_form'] = $signup_form;
   $form['collapse']['submit'] = array(
Index: modules/signup_basic_form/signup_basic_form.info
===================================================================
RCS file: modules/signup_basic_form/signup_basic_form.info
diff -N modules/signup_basic_form/signup_basic_form.info
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ modules/signup_basic_form/signup_basic_form.info	18 Oct 2010 21:38:21 -0000
@@ -0,0 +1,6 @@
+; $Id$
+name = Signup basic form
+description = Provides a signup form pane with name and phone fields.
+package = Signup
+dependencies[] = signup
+core = 6.x
Index: modules/signup_basic_form/signup_basic_form.module
===================================================================
RCS file: modules/signup_basic_form/signup_basic_form.module
diff -N modules/signup_basic_form/signup_basic_form.module
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ modules/signup_basic_form/signup_basic_form.module	18 Oct 2010 21:38:21 -0000
@@ -0,0 +1,98 @@
+<?php
+// $Id$
+/**
+ * @file signup_basic_form.module
+ * Provides a basic signup form pane to collect participant name and phone.
+ *
+ * Comments a-plenty as this is also the example of how to use the API.
+ */
+
+/**
+ * Implementation of hook_signup_pane_info().
+ *
+ * Defines panes available to signup forms.
+ * 
+ * @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 associative array of data about signup form panes. 
+ *  The key is the pane ID. The value is itself an associative array of the
+ *  following form:
+ *    - label: A label for the admin UI.
+ *    - description: A longer description for the admin UI.
+ *    - callback: A callback function to generate the form.
+ *  The callback function should have the following signature:
+ *   function my_callback(&$signup_form, &$form_state, $node, $signup, $pane_id, $signup_type = 'auth')
+ *  where the parameters are:
+ *  - $signup_form: Incoming form array, for information only.
+ *  - $form_state: Incoming array from the form builder function.
+ *  - $node: The fully loaded node object.
+ *  - $signup: The fully loaded signup object; or NULL if this is a new signup.
+ *  - $pane_id: The id of the pane currently being invoked. This allows a module
+ *    to use one callback for several panes.
+ *  - $signup_type: The type of signup, which may influence what form is returned:
+ *    - 'auth' -- regular authenticated user signup form
+ *    - 'anon' -- anonymous user signup form (main form includes required email field).
+ *    - 'admin' -- admin form to signup another user (main form includes user selector).
+ */
+function signup_basic_form_signup_pane_info($node = NULL) {
+  return array(
+    'basic' => array(
+      'label' => t('Name and phone number'),
+      'description' => t('Collects name and phone number.'),
+      'callback' => 'signup_basic_form_form',
+    ),
+  );
+}
+
+/**
+ * Signup form pane callback for the basic name and phone form.
+ *
+ * @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. You might need this, but you probably don't.
+ * @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_basic_form_form(&$signup_form, &$form_state, $node, $signup, $pane_id, $signup_type = 'auth') {
+  global $user;
+  $form = array();
+
+  $form['Name'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Name'),
+    '#size' => 40, '#maxlength' => 64,
+    '#required' => TRUE,
+  );
+  $form['Phone'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Phone'),
+    '#size' => 40, '#maxlength' => 64,
+  );
+
+  // If the user is logged in, fill in their name by default,
+  // except in the special case where the admin user is adding other users.
+  if ($user->uid && $signup_type != 'admin') {
+    $form['Name']['#default_value'] = $user->name;
+  }
+
+  return $form;
+}
Index: modules/signup_pane_example/signup_pane_example.info
===================================================================
RCS file: modules/signup_pane_example/signup_pane_example.info
diff -N modules/signup_pane_example/signup_pane_example.info
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ modules/signup_pane_example/signup_pane_example.info	18 Oct 2010 21:38:21 -0000
@@ -0,0 +1,6 @@
+; $Id$
+name = Signup pane example
+description = Example implementation of a signup pane.
+package = Signup
+dependencies[] = signup
+core = 6.x
Index: modules/signup_pane_example/signup_pane_example.module
===================================================================
RCS file: modules/signup_pane_example/signup_pane_example.module
diff -N modules/signup_pane_example/signup_pane_example.module
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ modules/signup_pane_example/signup_pane_example.module	18 Oct 2010 21:38:21 -0000
@@ -0,0 +1,202 @@
+<?php
+// $Id$
+/**
+ * @file signup_pane_example.module
+ * A very simple example signup pane module.
+ */
+
+/**
+ * Implementation of hook_signup_pane_info().
+ *
+ * Defines panes available to signup forms.
+ * 
+ * @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 associative array of data about signup form panes. 
+ *  The key is the pane ID. The value is itself an associative array of the
+ *  following form:
+ *    - label: A label for the admin UI.
+ *    - description: A longer description for the admin UI.
+ *    - callback: A callback function to generate the form.
+ *  The callback function should have the following signature:
+ *   function my_callback(&$signup_form, &$form_state, $node, $signup, $pane_id, $signup_type = 'auth')
+ *  where the parameters are:
+ *  - $signup_form: Incoming form array, for information only.
+ *  - $form_state: Incoming array from the form builder function.
+ *  - $node: The fully loaded node object.
+ *  - $signup: The fully loaded signup object; or NULL if this is a new signup.
+ *  - $pane_id: The id of the pane currently being invoked. This allows a module
+ *    to use one callback for several panes.
+ *  - $signup_type: The type of signup, which may influence what form is returned:
+ *    - 'auth' -- regular authenticated user signup form
+ *    - 'anon' -- anonymous user signup form (main form includes required email field).
+ *    - 'admin' -- admin form to signup another user (main form includes user selector).
+ */
+function signup_pane_example_signup_pane_info($node = NULL) {
+  return array(
+    'example' => array(
+      'label' => t('Favourite colour'),
+      'description' => t('Should probably be blue.'),
+      'callback' => 'signup_pane_example_form',
+    ),
+  );
+}
+
+/**
+ * Signup form pane callback.
+ *
+ * If you are handling your own data storage for this form, you should load any
+ * existing data here and load it into the '#default_value' attribute for each
+ * element of the returned array.
+ *
+ * @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 node 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_pane_example_form(&$signup_form, &$form_state, $node, $signup, $pane_id, $signup_type = 'auth') {
+  $form['favorite_color'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Favourite colour'),
+    '#size' => 40, '#maxlength' => 64,
+    '#required' => TRUE,
+  );
+  return $form;
+}
+
+/**
+ * Form validation function for our signup pane.
+ *
+ * This function should be of the form PANECALLBACK_validate.
+ */
+function signup_pane_example_form_validate($element, &$form_state) {
+  /*
+  // Example validation code:
+  if ($form_state['values']['signup_form_data']['example']['favorite_color'] != 'blue') {
+    form_set_error('signup_form_data][example][favorite_color', t('Favourite colour must be blue.'));
+  }
+  */
+}
+
+/**
+ * Implementation of hook_signup_data_alter().
+ *
+ * If we want to handle our own data storage rather than let signup module do it,
+ * we can intercept a signup here before it is saved, extract our data (and
+ * unset it in the incoming array), and save it to our own database tables.
+ *
+ * The advantage of doing this is that we can use it in Views as a field in its
+ * own right, which allows sorting and filtering.
+ *
+ * Be aware that at this point the signup may not be assigned an sid if it is
+ * a new submission. You may use signup_pane_example_signup_insert() to save
+ * data after the signup has been stored in the database.
+ */
+function signup_pane_example_signup_data_alter(&$signup, $form_values) {
+  /*
+  $our_value = $signup->form_data['favorite_color'];
+  // Now save our value ourselves.
+  // This gets us an ID: $our_id.
+  
+  // Unset our key so it is not saved in the signup data.
+  unset($signup->form_data['favorite_color']);
+  
+  // However, we have no signup sid here. 
+  // In order to set a connection to our own data, we put in our own data's ID 
+  // here, so we can retrieve it in hook_signup_insert().
+  $our_id = 'coconuts';
+  $signup->form_data[$pane_id] = array(
+    'signup_pane_example_id' => $our_id,
+  );
+  */
+}
+
+/**
+ * Implementation of hook_signup_insert().
+ *
+ * Carries on from signup_pane_example_signup_data_alter(). The signup is now
+ * saved to the database and is assigned an sid.
+ */
+function signup_pane_example_signup_insert($signup) {
+  /*
+  // Retrieve the id for our own data from where we put it in the form data
+  // in hook_signup_data_alter().
+  $our_id = $signup->form_data[$pane_id]['signup_pane_example_id'];
+ 
+  // We should now revisit our own data to save the connection between signup sid
+  // and our own record for this signup.
+  // Save a connection between $signup->sid and $our_id.
+  */
+}
+
+/**
+ * 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.
+ *
+ * Alternatively, if we are storing data ourselves, we may wish to load it 
+ * and insert it into the form_data array.
+ *
+ * @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 probably 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_pane_example_signup_form_data_display_alter(&$form_data, $nid, $sid, $uid, $type = 'list') {
+  if (isset($form_data['example'])) {
+    // Change our data array key from the form element key to something nicely readable.
+    $form_data['example']['Favourite colour'] = $form_data['example']['favorite_color'];
+    unset($form_data['example']['favorite_color']);
+  }
+}
+
+/**
+ * Implementation of hook_signup_cancel().
+ *
+ * A signup is being cancelled. 
+ * If we are storing our own data for this signup, we should delete it here.
+ */
+//function signup_pane_example_signup_cancel($signup, $node) { }
+
+/**
+ * Implementation of hook_nodeapi().
+ *
+ * A whole signup-enabled node is being deleted.
+ * If we are storing our own data, we should delete all of it that relates to 
+ * this node.
+ */
+//function signup_pane_example_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
+//  if ($op == 'delete') {}
+//}
Index: modules/signup_profile/signup_profile.info
===================================================================
RCS file: modules/signup_profile/signup_profile.info
diff -N modules/signup_profile/signup_profile.info
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ modules/signup_profile/signup_profile.info	18 Oct 2010 21:38:21 -0000
@@ -0,0 +1,7 @@
+; $Id$
+name = Signup profile
+description = Provides a signup form pane containing profile fields for each profile category.
+package = Signup
+dependencies[] = signup
+dependencies[] = profile
+core = 6.x
Index: modules/signup_profile/signup_profile.module
===================================================================
RCS file: modules/signup_profile/signup_profile.module
diff -N modules/signup_profile/signup_profile.module
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ modules/signup_profile/signup_profile.module	18 Oct 2010 21:38:21 -0000
@@ -0,0 +1,243 @@
+<?php
+// $Id$
+/**
+ * @file signup_profile.module
+ * Provides integration with profile module.
+ *
+ * Comments a-plenty as this is also an example of how to use the API.
+ * This shows an example of how a signup pane can elect to do its own storage
+ * of its signup data rather than letting signup handle storage.
+ *
+ * @todo: The big question about this module is what to do with anonymous signups.
+ * One possibility would be to add a module-wide admin setting to either:
+ * a) create a user account based on the entered anon email and so store the 
+ *    profile data there
+ * b) just don't show this pane and don't collect data
+ * c) give the user the choice of creating an account as they signup
+ * d) (crazyland) Store the data for anon users ourselves.
+ */
+
+/**
+ * Implementation of hook_signup_pane_info().
+ *
+ * @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.
+ *
+ * This should return an associative array of data about signup form panes.
+ */
+function signup_profile_signup_pane_info($node = NULL) {
+  // Get the categories. This may return NULL if none are defined yet.
+  $categories = _signup_profile_available_categories();
+  if (!is_null($categories)) {
+    foreach ($categories as $category) {
+      // Make a pane for each category.
+      $panes['profile_' . $category['name']] = array(
+        'label' => 'Profile: ' . $category['name'],
+        'description' => t('Collects @category profile fields and saves to the user profile.', array('@category' => $category['name'])),
+        'callback' => 'signup_profile_form',
+      );
+    }
+  }
+  
+  return $panes;
+}
+
+/**
+ * Helper function to get profile categories that users have access to.
+ *
+ * This result is cached as we may need it several times.
+ */
+function _signup_profile_available_categories() {
+  static $available_categories;
+  
+  if (isset($available_categories)) {
+    return $available_categories;
+  }
+  
+  // Get all the categories.
+  $categories = profile_categories();
+  // Get the number of fields in each category according to access. This will
+  // let us determine if a category actually applies.
+  $result = db_query("SELECT category, COUNT(*) AS count FROM {profile_fields} WHERE visibility <> 4 GROUP BY category", PROFILE_HIDDEN);
+  while ($row = db_fetch_array($result)) {
+    $categories_count[$row['category']] = $row['count'];
+  }
+  foreach ($categories as $category) {
+    if (isset($categories_count[$category['name']])) {
+      $available_categories[] = $category;
+    }
+  }
+  
+  return $available_categories;
+}
+
+/**
+ * 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_profile_form(&$signup_form, &$form_state, $node, $signup, $pane_id, $signup_type = 'auth') {
+  $form = array();
+  
+  // Get the real category name from the prefixed pane ID.
+  $category = substr($pane_id, 8);
+  
+  // Call profile.module's implementation of hook_user's form op.
+  // This is a smidgen hacky, but safe, as:
+  // - $edit is just checked for default values, passing nothing won't hurt. 
+  //   @todo: make this bit work for bonus points!
+  // - $user is not looked at at all.
+  $form = profile_form_profile(array(), NULL, $category, FALSE);
+  
+  // Our data is not in the signup data, so we prepopulate this form
+  // with our own default values where applicable.
+  if (isset($signup) && $signup_type != 'anon') {
+    // Existing signup: get the account.
+    $account = user_load($signup->uid);
+  }
+  elseif ($signup_type == 'auth') {
+    // An authenticated user is signup themselves up. 
+    // Prefill their profile values so they can either leave them or know what 
+    // they are changing.
+    global $user;
+    // Need to do a user_load to get profile fields.
+    $account = user_load($user->uid);
+  }
+  
+  if (isset($account)) {
+    // Set the form's defaults to the profile values. 
+    foreach (element_children($form[$category]) as $key) {
+      $form[$category][$key]['#default_value'] = $account->$key;
+    }
+    $form[$category]['#description'] = t('This information is from your @site-name user profile; changes you make here will be saved into it.', array(
+      '@site-name' => variable_get('site_name', 'Drupal'),
+    ));
+  }
+    
+  return $form;
+}
+
+/**
+ * Implementation of hook_signup_data_alter().
+ *
+ * This allows us to alter the signup data prior to it being saved, do our own
+ * saving of profile data, and remove the profile data from the signup so it is
+ * not saved in the {signup_log} table's serialized array.
+ */
+function signup_profile_signup_data_alter(&$signup, $form_values) { 
+  // Check we have incoming data that concerns us.
+  foreach($signup->form_data as $pane_id => $form_data_pane) {
+    if (substr($pane_id, 0, 7) == 'profile') {
+      $profile_pane_ids[] = $pane_id;
+    }
+  }
+  
+  if (isset($profile_pane_ids)) {
+    // Get the user account.
+    $account = user_load($signup->uid);
+    
+    foreach ($profile_pane_ids as $pane_id) {
+      // Get the real category name from the prefixed pane ID.
+      $category_name = substr($pane_id, 8);
+      $profile_data = $signup->form_data['profile_' . $category_name][$category_name];
+      
+      // Again, a bit hackish, but probably safe to call:
+      // function profile_save_profile(&$edit, &$user, $category, $register = FALSE)
+      // $edit is an array of form element ID => value which we have too.
+      // $user we pass the account for the signup.
+      profile_save_profile($profile_data, $account, $category_name, FALSE);
+      
+      // Unset our data from the signup so it's not saved redundantly.
+      unset($signup->form_data[$pane_id]);
+    }    
+  }
+}
+
+/**
+ * Implementation of hook_signup_form_data_display_alter().
+ *
+ * Load the signup user's profile data and add it to the data being output.
+ *
+ * @todo: consider skipping this in the case $type == 'view', as really
+ * the profile fields should be added to the view itself.
+ *
+ * @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.
+ * @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 probably 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_profile_signup_form_data_display_alter(&$form_data, $nid, $sid, $uid, $type = 'list') {
+  // Check this is a signup that concerns us. We don't store anything of 
+  // our own in $form_data so we have to investigate the node.
+  static $nodes = array();
+  if (!isset($nodes[$nid])) {
+    $nodes[$nid] = node_load($nid);
+  }
+  foreach($nodes[$nid]->signup_form_panes as $pane_id => $form_data_pane) {
+    if (substr($pane_id, 0, 7) == 'profile') {
+      // We actually only need to know there is at least one signup pane that interests us.
+      $has_profile_signup_panes = TRUE;
+      break;
+    }
+  }
+  
+  // If there are signup panes and this is not an anonymous user, load the
+  // profile data and display it.
+  if ($has_profile_signup_panes && $uid != 0) {
+    // Get all the profile fields and cache them.
+    static $profile_fields;
+    if (!isset($profile_fields)) {
+      $result = db_query('SELECT fid, name, title, type, category, visibility, weight FROM {profile_fields} WHERE visibility IN (%d, %d) ORDER BY weight', PROFILE_PUBLIC, PROFILE_PUBLIC_LISTINGS);
+      while ($field = db_fetch_object($result)) {
+        $profile_fields[$field->fid] = $field;
+      }
+    }
+    
+    // It's probably quicker to get all the values for this one user rather 
+    // than join to {profile_fields} for each one to see if it's in a category
+    // we care about.
+    $result = db_query("SELECT fid, value FROM {profile_values} WHERE uid = %d", $uid);
+    while ($record = db_fetch_array($result)) {
+      //dsm($record);
+      $signup_profile_data[$record['fid']] = $record['value'];
+    }
+    
+    foreach ($profile_fields as $fid => $field) {
+      if (isset($signup_profile_data[$fid])) {
+        $form_data[$field->category][$field->title] = $signup_profile_data[$fid];
+      }
+    }
+  }
+}
Index: modules/signup_webform/README.txt
===================================================================
RCS file: modules/signup_webform/README.txt
diff -N modules/signup_webform/README.txt
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ modules/signup_webform/README.txt	18 Oct 2010 21:38:21 -0000
@@ -0,0 +1,14 @@
+$Id$
+
+The signup webform module provides a signup pane for each webform node. This allows  users to quickly create a form to collect data specific to a signup event.
+
+INSTRUCTIONS
+------------
+
+1. Create a signup-enabled node as normal.
+2. Create a webform node.
+3. Add some components to the webform.
+4. Go to the signup node's settings. The webform you created appears as one of the possible signup panes. Enable this pane and drag it into the order you wish it to appear.
+5. The webform components you added in step 3 now appear on the signup node. When users sign up to this node, webform submissions are created automatically. Webform submission data appears both in the webform submissions listing and in the signup listing.
+6. You can repeat steps 2-6 to add more webforms to the same signup node; in general you can have several webforms per signup, and use one webform several signup nodes.
+ 
\ No newline at end of file
Index: modules/signup_webform/signup_webform.info
===================================================================
RCS file: modules/signup_webform/signup_webform.info
diff -N modules/signup_webform/signup_webform.info
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ modules/signup_webform/signup_webform.info	18 Oct 2010 21:38:21 -0000
@@ -0,0 +1,7 @@
+; $Id$
+name = Signup webform
+description = Provides a signup form panes for each webform.
+dependencies[] = signup
+dependencies[] = webform
+package = Signup
+core = 6.x
Index: modules/signup_webform/signup_webform.install
===================================================================
RCS file: modules/signup_webform/signup_webform.install
diff -N modules/signup_webform/signup_webform.install
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ modules/signup_webform/signup_webform.install	18 Oct 2010 21:38:21 -0000
@@ -0,0 +1,60 @@
+<?php
+// $Id$
+/**
+ * @file signup_webform.install
+ */
+
+/**
+ * Implementation of hook_schema().
+ */
+function signup_webform_schema() {
+  // The relationship between signup and webform submissions is saved in its
+  // own table rather than in the signup form_data array to allow eventual
+  // access from Views.
+  $schema['signup_webform_submission'] = array(
+    'description' => 'Relates a {signup_log}.sid (as signup_sid) to one or more {webform_submissions}.sid (as submission_sid), giving access to the webform data saved for a given signup.',
+    'fields' => array(
+      'signup_sid' => array(
+        'description' => 'The signup ID: {signup_log}.sid.',
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'webform_nid' => array(
+        'description' => 'The {node}.id for the webform.',
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'submission_sid' => array(
+        'description' => 'The submission ID: {webform_submissions}.sid.',
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+    ),
+    'primary key' => array('signup_sid', 'webform_nid'),
+  );
+  
+  return $schema;
+}
+
+/**
+ * Implementation of hook_install().
+ */
+function signup_webform_install() {
+  // Create tables.
+  drupal_install_schema('signup_webform');
+}
+
+/**
+ * Implementation of hook_uninstall().
+ */
+function signup_webform_uninstall() {
+  // Remove tables.
+  drupal_uninstall_schema('signup_webform');
+}
+
Index: modules/signup_webform/signup_webform.module
===================================================================
RCS file: modules/signup_webform/signup_webform.module
diff -N modules/signup_webform/signup_webform.module
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ modules/signup_webform/signup_webform.module	18 Oct 2010 21:38:21 -0000
@@ -0,0 +1,479 @@
+<?php
+// $Id$
+/**
+ * @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;
+}
Index: theme/email.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/signup/theme/email.inc,v
retrieving revision 1.2
diff -u -p -r1.2 email.inc
--- theme/email.inc	7 Jan 2009 00:55:00 -0000	1.2
+++ theme/email.inc	18 Oct 2010 21:38:21 -0000
@@ -56,6 +56,8 @@ function theme_signup_custom_data_email(
  *
  * @param $key
  *   Name of the custom signup field (the array key).
+ *   If this is numeric, it is assumed that the $value contains its own label,
+ *   in which case the $key is not output.
  * @param $value
  *   Value of the custom signup field (the array value).
  *
@@ -71,7 +73,14 @@ function theme_signup_custom_data_field_
   // warnings when extracting strings, "hide" the call to t() by using a
   // variable to hold the function name.
   $tr = 't';
-  return $tr($key) .': '. $value;
+  // If a key is numeric, we assume the module that set it knows what it is
+  // doing and provides its own label.
+  if (is_numeric($key)) {
+    return $value;
+  }
+  else {
+    return $tr($key) .': '. $value;
+  }
 }
 
 /**
Index: theme/node.admin.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/signup/theme/node.admin.inc,v
retrieving revision 1.8
diff -u -p -r1.8 node.admin.inc
--- theme/node.admin.inc	21 Sep 2009 05:23:02 -0000	1.8
+++ theme/node.admin.inc	18 Oct 2010 21:38:21 -0000
@@ -138,7 +138,14 @@ function theme_signup_custom_data($data)
       $output .= call_user_func(__FUNCTION__, $value);
     }
     else {
-      $output .= $tr($key) .': '. check_plain($value);
+      // If a key is numeric, we assume the module that set it knows what it is
+      // doing and provides its own label.
+      if (is_numeric($key)) {
+        $output .= check_plain($value);
+      }
+      else {
+        $output .= $tr($key) .': '. check_plain($value);
+      }
     }
     $output .= "</div>\n";
   }
Index: theme/signup_form.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/signup/theme/signup_form.inc,v
retrieving revision 1.5
diff -u -p -r1.5 signup_form.inc
--- theme/signup_form.inc	22 Jan 2009 18:59:29 -0000	1.5
+++ theme/signup_form.inc	18 Oct 2010 21:38:21 -0000
@@ -8,76 +8,11 @@
  */
 
 /**
- * Return the site-specific custom fields for the signup user form.
- *
- * To customize this for your site, copy this entire function into
- * your theme's template.php file, rename the function to
- * phptemplate_signup_user_form(), and modify to taste.  Feel free to
- * alter any elements in this section, remove them, or add any others.
- *
- * WARNING: If your site allows anonymous signups and you alter the
- * 'Name' field in this function, you will probably have to implement a
- * version of theme_signup_anonymous_username() for your site.
- *
- * In order for the form to be rendered properly and for the custom
- * fields to be fully translatable when printed in other parts of the
- * Signup module (displayed in signup lists, emails, etc), the name of
- * the form element must be $form['signup_form_data']['NameOfDataField'],
- * where NameOfDataField is replaced with the actual name of the data
- * field.  For translation to work, the displayed name of the field
- * (the '#title' property) be the same as the name of the data field,
- * but wrapped in t().  See below for examples.
- *
- * Fieldsets are not currently supported in this form.  Any
- * '#default_value' will be filled in by default when the form is
- * presented to the user.  Any field marked '#required' must be filled
- * in before the user can sign up.
- *
- * If you do not want any additional fields, the function can simply
- * return an empty array: "return array();"
- *
- * @param $node
- *   The fully loaded node object where this signup form is appearing.
- *
- * @return
- *   Array defining the form to present to the user to signup for a node.
- *
- * @see theme_signup_anonymous_username()
- */
-function theme_signup_user_form($node) {
-  global $user;
-  $form = array();
-
-  // If this function is providing any extra fields at all, the following
-  // line is required for form form to work -- DO NOT EDIT OR REMOVE.
-  $form['signup_form_data']['#tree'] = TRUE;
-
-  $form['signup_form_data']['Name'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Name'),
-    '#size' => 40, '#maxlength' => 64,
-    '#required' => TRUE,
-  );
-  $form['signup_form_data']['Phone'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Phone'),
-    '#size' => 40, '#maxlength' => 64,
-  );
-
-  // If the user is logged in, fill in their name by default.
-  if ($user->uid) {
-    $form['signup_form_data']['Name']['#default_value'] = $user->name;
-  }
-
-  return $form;
-}
-
-/**
  * Returns the value to use for the user name for anonymous signups.
  *
- * WARNING: If you implemented your own version of theme_signup_form_data()
- * that changed or removed the custom 'Name' field and your site
- * allows anonymous signups, you will need to modify this, too.
+ * WARNING: If you are using a custom signup form other than the basic one
+ * supplied with this module, you will need to alter this function to work
+ * correctly.
  *
  * This value is used for the %user_name email token for anonymous users, and
  * also to identify a particular anonymous signup in various places in the UI.
@@ -96,8 +31,8 @@ function theme_signup_anonymous_username
   // supplied email address, in which case, you should uncomment this:
   //return $email;
 
-  // WARNING: This line is only valid if you left the 'Name' field in
-  // your site's version of theme_signup_user_form().
+  // WARNING: This line is only valid if you are using the basic custom form
+  // or if your own custom form has a 'Name' field.
   return $form_data['Name'];
 }
 
Index: views/signup.views.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/signup/views/signup.views.inc,v
retrieving revision 1.11
diff -u -p -r1.11 signup.views.inc
--- views/signup.views.inc	22 Jul 2009 21:47:01 -0000	1.11
+++ views/signup.views.inc	18 Oct 2010 21:38:21 -0000
@@ -252,6 +252,20 @@ function signup_views_data() {
     'field' => array(
       'handler' => 'signup_handler_field_signup_user_form_data',
       'option' => 'string',
+      'additional fields' => array(
+        'sid' => array(
+          'table' => 'signup_log',
+          'field' => 'sid',
+        ),
+        'nid' => array(
+          'table' => 'signup_log',
+          'field' => 'nid',
+        ),
+        'uid' => array(
+          'table' => 'signup_log',
+          'field' => 'uid',
+        ),
+      ),
     ),
   );
 
Index: views/handlers/signup_handler_field_signup_user_form_data.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/signup/views/handlers/signup_handler_field_signup_user_form_data.inc,v
retrieving revision 1.2
diff -u -p -r1.2 signup_handler_field_signup_user_form_data.inc
--- views/handlers/signup_handler_field_signup_user_form_data.inc	7 Jan 2009 00:55:00 -0000	1.2
+++ views/handlers/signup_handler_field_signup_user_form_data.inc	18 Oct 2010 21:38:21 -0000
@@ -14,6 +14,17 @@ class signup_handler_field_signup_user_f
       '#default_value' => isset($this->options['form_data_fieldname']) ? $this->options['form_data_fieldname'] : '',
     );
   }
+  
+  /**
+   * Ensure we have the node nid and signup sid values to pass to 
+   * hook_signup_form_data_display_alter().
+   */
+  function query() {
+    $this->ensure_my_table();
+    $this->add_additional_fields();
+
+    parent::query();
+  }
 
   /**
    * Set each field value to "$key: $value" when rendering all fields.
@@ -24,9 +35,18 @@ class signup_handler_field_signup_user_f
     if (!$this->options['form_data_fieldname']) {
       foreach ($values as $key => $result) {
         $form_data = unserialize($result->{$this->field_alias});
-        if ($form_data) {
-          foreach ($form_data as $key => $value) {
-            $this->items[$result->{$this->field_alias}][$key] = check_plain(theme('signup_custom_data_field_text', $key, $value));
+        $sid = $result->{$this->aliases['sid']};
+        $nid = $result->{$this->aliases['nid']};
+        $uid = $result->{$this->aliases['uid']};
+        // Invoke hook_signup_form_data_display_alter() to let other modules
+        // (in particular pane modules) alter the data for output.
+        // This may involve internal data being removed.
+        drupal_alter('signup_form_data_display', $form_data, $nid, $sid, $uid, 'view');
+        if (is_array($form_data)) {
+          foreach ($form_data as $pane_id => $pane_data) {
+            foreach ((array)$pane_data as $key => $value) {
+              $this->items[$result->{$this->field_alias}][$key] = check_plain(theme('signup_custom_data_field_text', $key, $value));
+            }
           }
         }
       }
