You are here

signup.module in Signup 5

Same filename and directory in other branches
  1. 5.2 signup.module
  2. 6.2 signup.module
  3. 6 signup.module
  4. 7 signup.module

File

signup.module
View source
<?php

/**
 * @defgroup signup_core Core drupal hooks
 */

/**
 * Implementation of hook_block().
 * @ingroup signup_core
 *
 * @param $op
 *   The operation that is being requested. This defaults to 'list', which
 *   indicates that the method should return which blocks are available.
 * @param $delta
 *   The specific block to display (the offset into an array).
 *
 * @return
 *   One of two possibilities. The first is an array of available blocks.
 *   The other is an array containing a block.
 */
function signup_block($op = 'list', $delta = 0) {
  global $user;
  switch ($op) {
    case 'list':
      $blocks[0]['info'] = t('List of users current signups.');
      return $blocks;
    case 'view':
      if (user_access('access content')) {
        switch ($delta) {
          case 0:
            $titles = signup_list_user_signups($user->uid);
            if (count($titles)) {
              $block['subject'] = t('Current signups');
              $block['content'] = theme_item_list($titles) . l(t('View signup schedule'), "user/{$user->uid}/signups");
            }
            return $block;
        }
      }
      break;
  }
}

/**
 * Implementation of hook_cron().
 * @ingroup signup_core
 */
function signup_cron() {

  // Only run this function if the event module is enabled.
  if (module_exists('event')) {

    // We must manually include this here as the event module doesn't
    // include timezone support on all page requests.
    require_once drupal_get_path('module', 'event') . '/event_timezones.inc';

    // Get the current time, and pull all of the nodes for which the
    // current time + the reminder_days_before time is greater than
    // the event's start date.  These are the events for which a
    // reminder email needs to be sent.
    $curtime = time();
    $result = db_query("SELECT n.title, n.nid, e.event_start, e.timezone, s.reminder_email, s.forwarding_email FROM {signup} s\n      INNER JOIN {node} n ON n.nid = s.nid INNER JOIN {event} e ON e.nid = s.nid WHERE\n      s.send_reminder = 1 AND (%d + ((s.reminder_days_before) * 86400)) > e.event_start", $curtime);

    // Grab each event, construct the email header and subject, and query
    // the signup log to pull all users who are signed up for this event.
    $from = variable_get('site_mail', ini_get('sendmail_from'));
    while ($event = db_fetch_object($result)) {
      $subject = t('Event reminder: !event', array(
        '!event' => $event->title,
      ));
      $signups = db_query("SELECT u.name, u.mail, s_l.anon_mail, s_l.form_data FROM {signup_log} s_l INNER JOIN {users} u ON u.uid = s_l.uid WHERE s_l.nid = %d", $event->nid);

      // Get timezone offset.
      $offset = event_get_offset($event->timezone, $event->event_start);

      // Loop through the users, composing their customized message
      // and sending the email.
      while ($signup = db_fetch_object($signups)) {
        $mail_address = $signup->anon_mail ? $signup->anon_mail : $signup->mail;
        $signup_data = unserialize($signup->form_data);
        if (!empty($signup_data)) {
          $signup_info = theme('signup_email_token_custom_data', $signup_data);
        }
        $trans = array(
          '%event' => $event->title,
          '%time' => _event_date(variable_get('signup_date_string', 'D, M jS, g:i A'), $event->event_start, $offset),
          '%username' => $signup->name,
          '%useremail' => $mail_address,
          '%info' => $signup_info,
        );
        $message = strtr($event->reminder_email, $trans);
        drupal_mail('signup_reminder_mail', $mail_address, $subject, $message, $from);
        watchdog('signup', t('Reminder for %event sent to %useremail.', array(
          '%event' => $event->title,
          '%useremail' => $mail_address,
        )), WATCHDOG_NOTICE, l(t('view'), 'node/' . $event->nid));
      }

      // Reminders for this event are all sent, so mark it in the
      // database so they're not sent again.
      db_query("UPDATE {signup} SET send_reminder = 0 WHERE nid = %d", $event->nid);
    }

    // Calculate the closing time for the event, which is the current
    // time + the number of hours before the event start when closing is
    // preferred.  Query the database for all signup events which have a
    // start time less than this.
    $closing_time = $curtime + variable_get('signup_close_early', 1) * 3600;
    $result = db_query("SELECT s.nid FROM {signup} s INNER JOIN {event} e ON e.nid = s.nid WHERE s.completed = 0 AND e.event_start < %d", $closing_time);

    // Loop through the results, calling the event closing function.
    while ($signup = db_fetch_object($result)) {
      signup_close_signup($signup->nid, $cron = 'yes');
      $node = node_load($signup->nid);
      foreach (module_implements('signup_close') as $module) {
        $function = $module . '_signup_close';
        $function($node);
      }
      watchdog('signup', t('Signups closed for %event by cron.', array(
        '%event' => $node->title,
      )), WATCHDOG_NOTICE, l(t('view'), 'node/' . $node->nid));
    }
  }
}

/**
 * Implementation of hook_help().
 *
 * @ingroup signup_core
 */
function signup_help($section) {
  switch ($section) {
    case 'admin/help#signup':
      return '<p>' . t('Signup allows users to sign up (in other words, register) for content of any type. The most common use is for events, where users indicate they are planning to attend. This module includes options for sending a notification email to a selected email address upon a new user signup (good for notifying event coordinators, etc.) and a confirmation email to users who sign up. Each of these options are controlled per node. When used on event nodes (with event.module installed and regular cron runs), it can also send out reminder emails to all signups a configurable number of days before the start of the event (also controlled per node) and to automatically close event signups 1 hour before their start (general setting). Settings exist for resticting signups to selected roles and content types.') . '</p><p>' . t('To use signup, you must enable which content types should allow signups in administer->settings->content types, and you must also grant the %sign_up_for_content permission to any user role that should be able to sign up in administer->access control. Each signup-enabled node will now have a place for users to sign up.', array(
        '%sign_up_for_content' => 'sign up for content',
      )) . '</p><p>' . t('There are two ways for a user to have administrative access to the signup information about a given node: either the user has the %administer_signups_for_own_content permission and they are viewing a node they created, or the user has the global %administer_all_signups permission. Administrative access allows a user to view all the users who have signed up for the node, along with whatever information they included when they signed up.  Signup administrators can also cancel other user\'s signups for the node, and can close signups on the node entirely (meaning no one else is allowed to sign up).', array(
        '%administer_signups_for_own_content' => 'administer signups for own content',
        '%administer all signups' => 'administer all signups',
      )) . '</p><p>' . t('Default settings for notification email address, reminder emails and confirmation emails are located in administer->settings->signup. These will be the default values used for a signup node unless otherwise specified (to configure these options per node, visit \'edit\' for that node and make the adjustments in the \'Sign up settings\' section).') . '</p><p>' . t('Signups can be manually closed for any node at the %signup_overview page, or on the \'signups\' tab on each node.', array(
        '%signup_overview' => t('Signup overview'),
      )) . '</p><p>' . t('The user signup form is fully themable -- form fields may be added or deleted. For more details see the instructions in signup.theme, where a sample user form is included.') . '</p>';
  }
}

/**
 * Implementation of hook_menu().
 *
 * @ingroup signup_core
 */
function signup_menu($may_cache) {
  global $user;
  $items = array();
  $access = user_access('administer all signups');
  if ($may_cache) {
    $items[] = array(
      'path' => 'admin/settings/signup',
      'description' => t('Configure settings for signups.'),
      'access' => $access,
      'callback' => 'drupal_get_form',
      'callback arguments' => array(
        'signup_settings_page',
      ),
      'title' => user_access('access administration pages') ? t('Signup') : t('Signup settings'),
    );
    $items[] = array(
      'path' => 'admin/content/signup',
      'description' => t('View all signup-enabled posts, and open or close signups on them.'),
      'access' => $access,
      'callback' => 'signup_admin_page',
      'title' => t('Signup administration'),
    );

    // Callbacks to open and close signups on a specific node.
    $items[] = array(
      'path' => 'closesignup',
      'access' => $access,
      'type' => MENU_CALLBACK,
      'callback' => 'signup_close_signup_admin',
    );
    $items[] = array(
      'path' => 'opensignup',
      'access' => $access,
      'type' => MENU_CALLBACK,
      'callback' => 'signup_open_signup_admin',
    );
  }
  else {

    // !$may_cache: dynamic menu items
    // Include other php code that we need when not serving cached pages:
    require_once drupal_get_path('module', 'signup') . '/signup.theme';

    // User signup schedule callback
    $items[] = array(
      'path' => 'user/' . arg(1) . '/signups',
      'access' => $access || $user->uid == arg(1),
      'type' => MENU_CALLBACK,
      'callback' => 'signup_user_schedule',
      'callback arguments' => array(
        $uid => arg(1),
      ),
    );

    // If it's a signup-enabled node, then put in a signup tab for admins.
    if (arg(0) == 'node' && is_numeric(arg(1)) && db_num_rows(db_query("SELECT nid FROM {signup} WHERE nid = %d", arg(1)))) {
      $node = node_load(array(
        'nid' => arg(1),
      ));
      $access_own = user_access('administer signups for own content') && $user->uid == $node->uid;
      $items[] = array(
        'path' => 'node/' . arg(1) . '/signups',
        'title' => t('Signups'),
        'callback' => 'signup_user_signups_form',
        'callback arguments' => array(
          $node,
        ),
        'access' => $access || $access_own,
        'type' => MENU_LOCAL_TASK,
        'weight' => 20,
      );
    }
  }
  return $items;
}

/**
 * Implementation of hook_perm().
 *
 * @ingroup signup_core
 */
function signup_perm() {
  return array(
    'sign up for content',
    'view all signups',
    'administer all signups',
    'administer signups for own content',
  );
}

/**
 * Implementation of hook_user().
 *
 * When viewing a user profile page, display their current signup schedule.
 *
 * When a user is deleted, cancel all of that user's signups to remove all
 * instances of that user from the {signup_log} table, free up space in events
 * with signup limits, etc.
 *
 * @ingroup signup_core
 */
function signup_user($op, &$edit, &$user, $category = NULL) {
  switch ($op) {
    case 'view':

      // grab list of events the user signed up for.
      $signups = signup_list_user_signups($user->uid);
      if (count($signups)) {
        $output = '<h4>' . t('Current signups') . ' -- ' . l(t('view signup schedule'), "user/{$user->uid}/signups") . '</h4>' . theme_item_list($signups);
      }
      if (isset($output)) {
        return array(
          t('Signup information') => array(
            array(
              'value' => $output,
              'class' => 'user',
            ),
          ),
        );
      }
      break;
    case 'delete':
      $uids = array();
      if (is_array($edit['accounts'])) {

        // A multi-user delete from Admin > User management > Users.
        $uids = $edit['accounts'];
      }
      else {

        // A single-user delete from the edit tab on the user's profile.
        $uids[] = $edit['uid'];
      }
      foreach ($uids as $uid) {
        $nids = db_query("SELECT nid FROM {signup_log} WHERE uid = %d", $uid);
        while ($data = db_fetch_object($nids)) {
          signup_cancel_signup($uid, $data->nid);
        }
      }
      break;
  }
}

/**
 * Implementation of hook_form_alter().
 *
 * @ingroup signup_core
 */
function signup_form_alter($form_id, &$form) {
  switch ($form_id) {
    case 'node_type_form':
      signup_alter_node_type_form($form_id, $form);
      break;
    case $form['type']['#value'] . '_node_form':
      signup_alter_node_form($form_id, $form);
      break;
  }
}

/**
 * Alters the form for administrator settings per node type.
 * (admin/content/types)
 */
function signup_alter_node_type_form($form_id, &$form) {
  $type = $form['old_type']['#value'];
  $form['workflow']['signup_form'] = array(
    '#type' => 'checkbox',
    '#title' => t('Allow signups by default'),
    '#default_value' => variable_get('signup_form_' . $type, FALSE) == 1,
    '#description' => t('If selected, users will be allowed to signup for this node type by default. Users with %admin_all_signups permission will be able to toggle this setting on a per-node basis.', array(
      '%admin_all_signups' => t('administer all signups'),
    )),
  );
}

/**
 * Alters the node form to inject the appropriate per-node signup settings.
 */
function signup_alter_node_form($form_id, &$form) {
  global $user;

  // Load the node if it already exists.
  if (!empty($form['nid']['#value'])) {
    $node = node_load($form['nid']['#value']);
  }
  else {
    $node = NULL;
  }
  $signup_enabled = variable_get('signup_form_' . $form['type']['#value'], 0);

  // If the current user has global signup administration permissions,
  // or if this node-type is signup-enabled and the user has permission
  // to administer signups for their own content, add a fieldset for
  // signup-related settings.
  if (user_access('administer all signups') || !empty($node) && $signup_enabled && $node->uid == $user->uid && user_access('administer signups for own content')) {
    $form['signup'] = array(
      '#type' => 'fieldset',
      '#title' => t('Signup settings'),
      '#collapsible' => TRUE,
      '#collapsed' => TRUE,
      '#weight' => 30,
    );

    // Figure out what the options should be.  If there are already
    // people signed-up for this node, we need a 3rd choice: disable
    // signups and remove all signup data.
    $has_signups = !empty($node) && db_result(db_query("SELECT COUNT(*) from {signup_log} WHERE nid = %d", $node->nid));
    $radio_options[1] = t('Enabled');
    if ($has_signups) {
      $radio_options[0] = t('Disabled, but save existing signup information');
      $radio_options[2] = t('Disabled, and remove all signup information') . ' <strong>(' . t('This can not be undone, use with extreme caution!') . ')</strong>';
    }
    else {
      $radio_options[0] = t('Disabled');
    }

    // Figure out what the default selection for signups should be.
    if (isset($node->signup)) {
      $default_option = $node->signup;
    }
    else {
      $default_option = $signup_enabled;
    }
    if ($default_option == 1) {
      $hint = t('If enabled, you can control whether users may sign up by visiting the !signups tab and toggling if signups are %open or %closed for this @node_type.', array(
        '!signups' => !empty($node) ? l(t('Signups'), 'node/' . $node->nid . '/signups') : theme('placeholder', t('Signups')),
        '%open' => t('open'),
        '%closed' => t('closed'),
        '@node_type' => drupal_strtolower(node_get_types('name', $form['type']['#value'])),
      ));
    }
    else {
      $hint = '';
    }

    // Add the form element to toggle if signups are allowed.
    $form['signup']['signup_enabled'] = array(
      '#type' => 'radios',
      '#options' => $radio_options,
      '#default_value' => $default_option,
      '#description' => $hint . '<div class="js-hide">' . t('If disabled, all of the other signup settings will be ignored.') . '</div>',
      '#prefix' => '<div class="signup-allow-radios">',
      '#suffix' => '</div>',
    );

    // If JS is enabled, signup.css will hide all the settings on page
    // load if signups aren't enabled on this node.
    $settings_class = "signup-node-settings";
    if ($default_option != 1) {
      $settings_class .= " js-hide";
    }

    // Add the actual settings.  We wrap this in a div to make it easy
    // to use jQuery to hide these settings when signups are disabled.
    drupal_add_js(drupal_get_path('module', 'signup') . '/signup.js');
    drupal_add_css(drupal_get_path('module', 'signup') . '/signup.css');
    $form['signup']['node_settings'] = array(
      '#prefix' => '<div class="' . $settings_class . '">',
      '#suffix' => '</div>',
    );
    $form['signup']['node_settings']['settings'] = _signup_admin_form($node);
  }
}

/**
 * Submits the cancel signup form
 *
 * @ingroup signup_core
 *
 * @param $form_id The ID of the form being submitted.
 * @param $form_values The constructed form values array of the submitted form.
 */
function signup_form_cancel_submit($form_id, $form_values) {
  signup_cancel_signup($form_values['uid'], $form_values['nid'], $form_values['signup_anon_mail']);
}

/**
 * Executes the user signup form
 *
 * @ingroup signup_core
 *
 * @param $form_id The ID of the form being submitted.
 * @param $form_values The constructed form values array of the submitted form.
 */
function signup_form_submit($form_id, $form_values) {
  signup_sign_up_user($form_values);
}

/**
 * Validates the user signup form
 *
 * @ingroup signup_core
 *
 * @param $form_id The ID of the form being submitted.
 * @param $form_values The constructed form values array of the submitted form.
 */
function signup_form_validate($form_id, $form_values) {

  // If it's an anonymous signup...
  $anon_mail = $form_values['signup_anon_mail'] ? trim($form_values['signup_anon_mail']) : '';
  if ($anon_mail) {
    signup_validate_anon_email($form_values['nid'], $anon_mail, 'signup_anon_mail');
  }
}

/**
 * @defgroup signup_nodeapi Functions for nodeapi integration
 */

/**
 * hook_nodeapi implementation
 *
 * @ingroup signup_nodeapi
 */
function signup_nodeapi(&$node, $op, $teaser = NULL, $page = NULL) {
  global $form_values;
  switch ($op) {
    case 'insert':
      if (isset($form_values['signup_enabled'])) {
        if ($form_values['signup_enabled'] == 1) {
          $values = array(
            $node->nid,
            $form_values['signup_forwarding_email'],
            $form_values['signup_send_confirmation'],
            $form_values['signup_confirmation_email'],
            $form_values['signup_send_reminder'],
            $form_values['signup_reminder_days_before'],
            $form_values['signup_reminder_email'],
          );
        }
      }
      elseif (variable_get('signup_form_' . $node->type, 0)) {

        // The form doesn't include any information about signups, but
        // the node type is signup-enabled. This would happen if a
        // user without any signup admin permissions creates a node
        // that has been signup-enabled based on the node type. In
        // this case, we use the site-wide default signup settings.
        $defaults = db_fetch_array(db_query("SELECT * from {signup} WHERE nid = 0"));
        $values = array(
          $node->nid,
          $defaults['forwarding_email'],
          $defaults['send_confirmation'],
          $defaults['confirmation_email'],
          $defaults['send_reminder'],
          $defaults['reminder_days_before'],
          $defaults['reminder_email'],
        );
      }
      if (isset($values)) {
        db_query("INSERT INTO {signup} (nid, forwarding_email, send_confirmation, confirmation_email, send_reminder, reminder_days_before, reminder_email) VALUES (%d, '%s', %d, '%s', %d, %d, '%s')", $values);
      }
      break;
    case 'update':
      if (isset($form_values['signup_enabled'])) {
        $has_signup_record = db_result(db_query('SELECT COUNT(*) FROM {signup} WHERE nid = %d', $node->nid));
        switch ($form_values['signup_enabled']) {
          case 1:

            // Enabled
            if ($has_signup_record) {
              db_query("UPDATE {signup} SET forwarding_email  = '%s', send_confirmation = %d, confirmation_email  = '%s', send_reminder = %d, reminder_days_before = %d, reminder_email = '%s' WHERE nid = %d", $node->signup_forwarding_email, $node->signup_send_confirmation, $node->signup_confirmation_email, $node->signup_send_reminder, $node->signup_reminder_days_before, $node->signup_reminder_email, $node->nid);
            }
            else {
              db_query("INSERT INTO {signup} (nid, forwarding_email, send_confirmation, confirmation_email, send_reminder, reminder_days_before, reminder_email) VALUES (%d, '%s', %d, '%s', %d, %d, '%s')", $node->nid, $node->signup_forwarding_email, $node->signup_send_confirmation, $node->signup_confirmation_email, $node->signup_send_reminder, $node->signup_reminder_days_before, $node->signup_reminder_email);
            }
            break;
          case 2:

            // Disabled, and delete {signup_log}, too
            db_query("DELETE FROM {signup_log} WHERE nid = %d", $node->nid);

          // No break, fall through and remove from {signup} too.
          case 0:

            // Disabled, but leave {signup_log} alone
            if ($has_signup_record) {
              db_query("DELETE FROM {signup} WHERE nid = %d", $node->nid);
            }
            break;
        }
      }
      break;
    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_log} WHERE nid = %d", $node->nid);
      break;
    case 'load':

      // Check for a signup for this node.
      // If it's a new node, load the defaults.
      $result = db_query("SELECT * FROM {signup} WHERE nid = %d", $node->nid ? $node->nid : 0);

      // Load signup data for both new nodes w/ enabled node types,
      // and any existing nodes that are already signup enabled.
      if (!$node->nid && variable_get('signup_form_' . $node->type, 0) || $node->nid && db_num_rows($result)) {
        $signup = db_fetch_object($result);
        $node->signup = 1;
        $node->signup_forwarding_email = $signup->forwarding_email;
        $node->signup_send_confirmation = $signup->send_confirmation;
        $node->signup_confirmation_email = $signup->confirmation_email;
        $node->signup_send_reminder = $signup->send_reminder;
        $node->signup_reminder_days_before = $signup->reminder_days_before;
        $node->signup_reminder_email = $signup->reminder_email;
        $node->signup_completed = $signup->completed;
      }
      else {
        $node->signup = 0;
      }
      break;
    case 'view':
      $suppress = module_invoke_all('signup_suppress', $node);

      // If this is a signup node, start checks for what's to be printed.
      // Only include any of this if we're trying to view the node as
      // a page, not during the view from comment validation, etc.
      if ($node->signup && $page && !in_array(TRUE, $suppress)) {
        global $user;
        $anon_signup_form = array();

        // The node has been closed for signups, and the user has
        // signup permissions.  Let them know it's closed.
        if ($node->signup_completed) {
          if (user_access('sign up for content')) {
            $output = '<h3>' . t('Signups closed for this event') . '</h3>';
          }
        }
        else {
          if ($user->uid == 0) {

            // This is an anonymous user. If they have signup permissions,
            // then build the anon portion of the sigup form.  If not, then
            // display the login link.
            $login_array = array(
              '!login' => l(t('login'), 'user/login', array(), drupal_get_destination()),
              '!register' => l(t('register'), 'user/register', array(), drupal_get_destination()),
            );
            if (user_access('sign up for content')) {
              $needs_signup_form = TRUE;
              $anon_signup_form['signup_anon_mail'] = array(
                '#type' => 'textfield',
                '#title' => t('Email'),
                '#description' => t('An e-mail address is required for users who are not registered at this site. If you are a registered user at this site, please !login to sign up for this event.', $login_array),
                '#size' => 40,
                '#maxlength' => 255,
                '#required' => TRUE,
              );
            }
            else {
              $needs_signup_form = FALSE;

              // If not, then display the appropriate login/register
              // link if the default authenticated user role can signup.
              $signup_roles = user_roles(FALSE, 'sign up for content');
              if (!empty($signup_roles[DRUPAL_AUTHENTICATED_RID])) {
                if (variable_get('user_register', 1) == 0) {
                  $anon_login_text = t('Please !login to sign up for this event.', $login_array);
                }
                else {
                  $anon_login_text = t('Please !login or !register to sign up for this event.', $login_array);
                }
                $output .= '<div class="signup_anonymous_login">' . $anon_login_text . '</div>';
              }
            }
          }
          else {

            // See if the user is already signed up for this node.
            $result = db_query("SELECT signup_time, form_data FROM {signup_log} WHERE uid = %d AND nid = %d", $user->uid, $node->nid);
            $needs_signup_form = db_num_rows($result) == 0;
          }
          if ($needs_signup_form) {

            // User isn't signed up, so check to make sure they have signup
            // permissions, and if so, print the themed signup form.
            if (user_access('sign up for content')) {
              $output = drupal_get_form('signup_form', $node, $anon_signup_form);
            }
          }
          elseif ($user->uid !== 0 && isset($result)) {

            // The authenticated user is already signed up, so print a table
            // of their signup data, and give them the option to cancel.
            $result = db_fetch_object($result);
            $form_data = unserialize($result->form_data);
            $output .= theme('signup_custom_data_table', $form_data);
            $output .= drupal_get_form('signup_form_cancel', $node);
          }
        }

        // If the user has the view signups perm, display the current signups.
        // Pull all users signed up for this event, and start table creation.
        if (user_access('view all signups')) {
          $registered_signups = db_query("SELECT u.uid, u.name, s.signup_time, s.form_data FROM {signup_log} s INNER JOIN {users} u ON u.uid = s.uid WHERE s.nid = %d AND u.uid <> 0", $node->nid);
          $anon_signups = db_num_rows(db_query("SELECT anon_mail FROM {signup_log} WHERE nid = %d AND uid = 0", $node->nid));
          $header = array(
            array(
              'data' => t('!users signed up', array(
                '!users' => format_plural(db_num_rows($registered_signups) + $anon_signups, '1 individual', '@count individuals'),
              )),
            ),
          );
          $rows = array();
          while ($signed_up_user = db_fetch_object($registered_signups)) {
            $rows[] = array(
              theme('username', $signed_up_user),
            );
          }
          if ($anon_signups) {
            $rows[] = array(
              t('!count anonymous', array(
                '!count' => $anon_signups,
              )),
            );
          }
          $output .= theme('table', $header, $rows);
        }

        // Save output into a node property for retrieval from the theme layer.
        $node->signup_view = $output;

        // Store the data directly into the content array, for default display.
        $node->content['signup'] = array(
          '#value' => $output,
          '#weight' => 10,
        );
      }
      break;
  }
}

/**
 * @defgroup signup_callback
 *   Functions which are the menu callbacks for this module.
 */

/**
 * Builder function for the signup form
 * @ingroup signup_callback
 */
function signup_form($node, $anon_signup_form = array()) {
  global $user;
  $form['nid'] = array(
    '#type' => 'value',
    '#value' => $node->nid,
  );
  $form['uid'] = array(
    '#type' => 'value',
    '#value' => $user->uid,
  );
  $form['collapse'] = array(
    '#type' => 'fieldset',
    '#title' => t('Sign up for @title', array(
      '@title' => $node->title,
    )),
    '#collapsible' => TRUE,
    '#collapsed' => TRUE,
  );

  // Build the themed signup form.  If the anon signup form is
  // present, merge it in at the end of the form.
  $signup_themed_form = theme('signup_user_form');
  if (!empty($anon_signup_form)) {
    $signup_themed_form = array_merge($signup_themed_form, $anon_signup_form);
  }
  $form['collapse']['signup_user_form'] = $signup_themed_form;
  $form['collapse']['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Sign up'),
  );
  return $form;
}

/**
 * Builder function for the cancel signup form
 * @ingroup signup_callback
 */
function signup_form_cancel($node) {
  global $user;
  $form['nid'] = array(
    '#type' => 'value',
    '#value' => $node->nid,
  );
  $form['uid'] = array(
    '#type' => 'value',
    '#value' => $user->uid,
  );
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Cancel signup'),
  );
  return $form;
}

/**
 * Prints the admin signup overview page located at admin/content/signup
 * @ingroup signup_callback
 */
function signup_admin_page() {
  drupal_add_css(drupal_get_path('module', 'signup') . '/signup.css');
  $output = drupal_get_form('signup_filter_status_form');
  $type = $_SESSION['signup_status_filter'];

  // Add optional SQL to the query if the event module is enabled.
  $has_event = module_exists('event');
  $event_select = $has_event ? ', e.event_start, e.timezone' : '';
  $event_join = $has_event ? ' LEFT JOIN {event} e ON e.nid = n.nid' : '';
  if ($type == 'open') {
    $where = ' WHERE s.completed = 0';
  }
  elseif ($type == 'closed') {
    $where = ' WHERE s.completed = 1';
  }
  else {
    $where = '';
  }
  $header = array(
    array(
      'data' => t('Event'),
      'field' => 'n.title',
      'sort' => 'asc',
    ),
    array(
      'data' => t('Signups'),
      'field' => 'count',
    ),
    array(
      'data' => t('Operations'),
    ),
  );

  // If event.module enabled, add event start time to table
  if ($has_event) {
    $header = array_merge(array(
      array(
        'data' => t('Start'),
        'field' => 'e.event_start',
      ),
    ), $header);
  }

  // Pull all open signup nodes, and start the creation of the table.
  $sql = "SELECT n.nid, n.title, s.completed{$event_select},\n          COUNT(s_l.nid) AS count\n          FROM {signup} s INNER JOIN {node} n ON n.nid = s.nid\n          LEFT JOIN {signup_log} s_l ON s.nid = s_l.nid {$event_join}";
  $sql .= $where;
  $sql .= " GROUP BY n.nid, n.title, s.completed{$event_select}";
  $sql .= tablesort_sql($header);
  $sql = db_rewrite_sql($sql);
  $sql_count = "SELECT COUNT(s.nid) FROM {signup} s";
  $sql_count .= $where;
  $sql_count = db_rewrite_sql($sql_count, 's');
  $result = pager_query($sql, 25, 0, $sql_count);

  // Loop through the signup nodes, pull the number of signups for
  // each, and create the summary table.
  while ($signup_event = db_fetch_object($result)) {
    $row = array();
    if ($has_event) {

      // Must include this here as event module doesn't include timezone
      // support on all page requests.
      require_once drupal_get_path('module', 'event') . '/event_timezones.inc';
      $offset = $signup_event->event_start ? event_get_offset($signup_event->timezone, $signup_event->event_start) : '';
      $row[] = $signup_event->event_start ? _event_date(variable_get('signup_date_string', 'D, M jS, g:i A'), $signup_event->event_start, $offset) : '';
    }
    $row[] = l($signup_event->title, "node/{$signup_event->nid}");
    $row[] = $signup_event->count;
    $op_links = l(t('View Signups'), "node/{$signup_event->nid}/signups");
    $op_links .= '<br />';
    if ($signup_event->completed) {
      if (!arg(2)) {
        $op_links .= '<em>' . t('Closed: ') . '</em>';
      }
      $op_links .= l(t('Open Event'), "opensignup/{$signup_event->nid}/" . arg(2));
    }
    else {
      if (!arg(2)) {
        $op_links .= '<em>' . t('Open: ') . '</em>';
      }
      $op_links .= l(t('Close Event'), "closesignup/{$signup_event->nid}/" . arg(2));
    }
    $row[] = $op_links;
    $rows[] = $row;
  }
  $output .= theme('table', $header, $rows, array(
    'style' => 'width:100%',
  ));
  $pager = theme('pager', NULL, 25, 0);
  if (!empty($pager)) {
    $output .= $pager;
  }
  return $output;
}
function signup_filter_status_form() {
  $options = array(
    'all' => t('All'),
    'open' => t('Open'),
    'closed' => t('Closed'),
  );
  if (empty($_SESSION['signup_status_filter'])) {
    $_SESSION['signup_status_filter'] = 'all';
  }
  $form['filter'] = array(
    '#type' => 'select',
    '#title' => t('Filter by signup status'),
    '#options' => $options,
    '#default_value' => $_SESSION['signup_status_filter'],
  );
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Filter'),
  );
  $form['#redirect'] = FALSE;
  return $form;
}
function theme_signup_filter_status_form($form) {
  return '<div class="container-inline">' . drupal_render($form) . '</div>';
}
function signup_filter_status_form_submit($form_id, $form_values) {
  $_SESSION['signup_status_filter'] = $form_values['filter'];
}

/**
 * Callback function for canceling signups
 * @ingroup signup_callback
 */
function signup_cancel_signup($uid, $nid, $anon_mail = NULL) {

  // Delete the selected user from the log table.
  if ($anon_mail) {
    db_query("DELETE FROM {signup_log} WHERE anon_mail = '%s' AND nid = %d", $anon_mail, $nid);
  }
  else {
    db_query('DELETE FROM {signup_log} WHERE uid = %d AND nid = %d', $uid, $nid);
  }
  $node = node_load($nid);
  module_invoke_all('signup_cancel', $node, $uid);
  drupal_set_message(t('Signup to !title cancelled.', array(
    '!title' => l($node->title, "node/{$node->nid}"),
  )));
}

/**
 * Callback function for closing signups
 * @ingroup signup_callback
 */
function signup_close_signup($nid, $cron = 'no') {
  db_query("UPDATE {signup} SET completed = 1 WHERE nid = %d", $nid);
  if ($cron == 'no') {
    $node = node_load($nid);
    foreach (module_implements('signup_close') as $module) {
      $function = $module . '_signup_close';
      $function($node);
    }
    watchdog('signup', t('Signups closed for %title.', array(
      '%title' => $node->title,
    )), WATCHDOG_NOTICE, l(t('view'), 'node/' . $nid));
  }
}

/**
 * Callback function for opening signups via a link in the admin tables
 * @ingroup signup_callback
 */
function signup_close_signup_admin($nid, $tab = NULL) {
  signup_close_signup($nid, $cron = 'no');
  drupal_goto("admin/content/signup/{$tab}");
}

/**
 * Callback function for reopening signups
 * @ingroup signup_callback
 */
function signup_open_signup($nid, $cron = 'no') {
  db_query("UPDATE {signup} SET completed = 0 WHERE nid = %d", $nid);
  if ($cron == 'no') {
    $node = node_load(array(
      'nid' => $nid,
    ));
    foreach (module_implements('signup_open') as $module) {
      $function = $module . '_signup_open';
      $function($node);
    }
    watchdog('signup', t('Signups reopened for %title.', array(
      '%title' => $node->title,
    )), WATCHDOG_NOTICE, l(t('view'), 'node/' . $nid));
  }
}

/**
 * Callback function for opening signups via a link in the admin tables
 * @ingroup signup_callback
 */
function signup_open_signup_admin($nid, $tab = NULL) {
  signup_open_signup($nid, $cron = 'no');
  drupal_goto("admin/content/signup/{$tab}");
}

/**
 * Form builder for the settings page under admin/setttings/signup
 */
function signup_settings_page() {
  $form['signup_close_early'] = array(
    '#title' => t('Close x hours before'),
    '#type' => 'textfield',
    '#default_value' => variable_get('signup_close_early', 1),
    '#size' => 5,
    '#maxlength' => 10,
    '#description' => t('The number of hours before the event which signups will no longer be allowed. Use negative numbers to close signups after the event start (example: -12).'),
  );
  $form['node_defaults'] = array(
    '#type' => 'fieldset',
    '#title' => t('Default signup information'),
    '#description' => t('New signup-enabled nodes will start with these settings.'),
    '#collapsible' => TRUE,
  );
  $form['node_defaults']['_signup_admin_form'] = _signup_admin_form(NULL);

  // Use our own submit handler, so we can do some processing before
  // we hand control to system_settings_form_submit.
  $form['#submit']['signup_settings_page_submit'] = array();
  return system_settings_form($form);
}

/**
 * Submits the signup settings form
 *
 * @param $form_id The ID of the form being submitted.
 * @param $form_values The constructed form values array of the submitted form.
 */
function signup_settings_page_submit($form_id, $form_values) {
  $op = isset($form_values['op']) ? $form_values['op'] : '';
  if ($op == t('Save configuration') && db_num_rows(db_query('SELECT nid FROM {signup} WHERE nid = 0'))) {
    db_query("UPDATE {signup} SET forwarding_email = '%s', send_confirmation = %d, confirmation_email = '%s', send_reminder = %d, reminder_days_before = %d, reminder_email = '%s' WHERE nid = 0", $form_values['signup_forwarding_email'], $form_values['signup_send_confirmation'], $form_values['signup_confirmation_email'], $form_values['signup_send_reminder'], $form_values['signup_reminder_days_before'], $form_values['signup_reminder_email']);
  }
  else {
    require_once 'signup.install';
    db_query("DELETE FROM {signup} WHERE nid = 0");
    signup_insert_default_signup_info();
  }

  // Now, remove all the settings we just processed from our copy of
  // $form_values, so system_settings_form_submit() doesn't see them.
  $settings = array(
    'signup_forwarding_email',
    'signup_send_confirmation',
    'signup_confirmation_email',
    'signup_send_reminder',
    'signup_reminder_days_before',
    'signup_reminder_email',
  );
  foreach ($settings as $setting) {
    unset($form_values[$setting]);
  }

  // Remove the hidden element from _signup_admin_form(), too.
  unset($form_values['signup']);

  // Finally, let system_settings_form_submit() do its magic with the
  // rest of the settings.
  system_settings_form_submit($form_id, $form_values);
}

/**
 * Returns an array of node titles with links for all events the
 * specified user has signed up for.
 */
function signup_list_user_signups($uid) {
  $titles = array();

  // We don't want to return anything for anon users.
  if ($uid != 0) {
    $has_event = module_exists('event');
    $event_join = $has_event ? ' LEFT JOIN {event} e ON e.nid = n.nid' : '';
    $event_where = $has_event ? ' AND (e.event_start >= ' . time() . ' OR e.event_start IS NULL)' : '';
    $order_by = $has_event ? 'e.event_start' : 'n.title';
    $event_col = $has_event ? ', e.event_start' : '';

    // Pull all open signup nodes for this user.
    $result = db_query(db_rewrite_sql("SELECT n.nid, n.title{$event_col} FROM {node} n INNER JOIN {signup_log} s_l ON n.nid = s_l.nid {$event_join} WHERE s_l.uid = '%s' {$event_where} ORDER BY {$order_by}"), $uid);
    while ($node = db_fetch_array($result)) {
      $titles[$node['nid']] = l($node['title'], 'node/' . $node['nid']);
    }
  }
  return $titles;
}

/**
 * Signs up a user to a node.
 *
 * NOTE: other modules can call this function. To do so, $signup_form
 * must be as follows:
 *
 * $signup_form['nid'] : nid of the node to which the user will be signed up
 * $signup_form['uid'] : uid of the user to sign up
 * $signup_form['signup_anon_mail'] : Optional. An email address of an
 *   anonymous user to sign up. Only include this if the user is not
 *   already registered with the site.  $signup_form['uid'] will
 *   automatically be set to 0 if this element is passed in. NOTE: It's
 *   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
 */
function signup_sign_up_user($signup_form) {
  $node = node_load($signup_form['nid']);

  // Since this is an API call, we need to validate that there are no
  // duplicate signups being generated, even though through the usual
  // UI, there's no way to reach this function if it's a duplicate.
  // How to find duplicates is different for anonymous and
  // authenticated signups.
  if (!empty($signup_form['signup_anon_mail'])) {

    // Ensure the uid is 0 for anonymous signups, even if it's not duplicate.
    $signup_form['uid'] = 0;

    // Now, see if this email is already signed-up.
    if (db_num_rows(db_query("SELECT anon_mail FROM {signup_log} WHERE anon_mail = '%s' AND nid = %d", $signup_form['signup_anon_mail'], $node->nid))) {
      drupal_set_message(t('Anonymous user %email is already signed up for %title', array(
        '%email' => $signup_form['signup_anon_mail'],
        '%title' => $node->title,
      ), 'error'));
      return FALSE;
    }
  }
  else {

    // This query does the JOIN on {users} so we can avoid a full
    // user_load() just so theme('username') can have the data it
    // needs for the error message we might print out.
    $query = db_query("SELECT sl.uid, u.name FROM {signup_log} sl INNER JOIN {users} u ON sl.uid = u.uid WHERE sl.uid = %d AND sl.nid = %d", $signup_form['uid'], $signup_form['nid']);
    if (db_num_rows($query)) {
      $user = db_fetch_object($query);
      drupal_set_message(t('User !user is already signed up for %title', array(
        '!user' => theme('username', $user),
        '%title' => $node->title,
      )), 'error');
      return FALSE;
    }
  }

  // If we made it this far, we're going to need the full $user object.
  $user = user_load(array(
    'uid' => $signup_form['uid'],
  ));
  if (user_access('sign up for content') && !$node->signup_completed) {

    // Grab the current time once, since we need it in a few places.
    $curtime = time();

    // Allow other modules to inject data into the user's signup data.
    $extra = module_invoke_all('signup_sign_up', $node, $user);
    $signup_info = array();
    if (!empty($signup_form['signup_form_data'])) {
      $signup_info = $signup_form['signup_form_data'];
    }
    if (!empty($extra)) {
      $signup_info = array_merge($signup_info, $extra);
    }
    $signup_form_data = serialize($signup_info);

    // Figure out if confirmation or reminder emails will be sent and
    // inform the user.
    $confirmation_email = $node->signup_send_confirmation ? '  ' . t('You will receive a confirmation email shortly which contains further event information.') : '';
    $reminder_email = $node->signup_send_reminder ? '  ' . t('You will receive a reminder email !number !days before the event.', array(
      '!number' => $node->signup_reminder_days_before,
      '!days' => format_plural($node->signup_reminder_days_before, 'day', 'days'),
    )) : '';

    // Insert the user into the signup_log.
    db_query("INSERT INTO {signup_log} (uid, nid, anon_mail, signup_time, form_data) VALUES (%d, %d, '%s', %d, '%s')", $signup_form['uid'], $signup_form['nid'], $signup_form['signup_anon_mail'], $curtime, $signup_form_data);

    // Must include this here as event module doesn't include timezone
    // support on all page requests.
    if (module_exists('event')) {
      require_once drupal_get_path('module', 'event') . '/event_timezones.inc';
    }

    // Format the start time, and compose the user's signup data for
    // later use in the emails.
    $offset = $node->event_start ? event_get_offset($node->timezone, $node->event_start) : '';
    $starttime = $node->event_start ? _event_date(variable_get('signup_date_string', 'D, M jS, g:i A'), $node->event_start, $offset) : t('[Untimed]');
    $signup_data = '';
    if (isset($signup_form['signup_form_data'])) {
      $signup_data = theme('signup_email_token_custom_data', $signup_form['signup_form_data']);
    }

    // Determine if this is an anon signup or not, and get the
    // appropriate email address to use.
    $user_mail = $signup_form['signup_anon_mail'] ? $signup_form['signup_anon_mail'] : $user->mail;

    // This is not used for web display, only email, so the output is
    // not being sanitized and linked as we would for web output.
    $trans = array(
      "%event" => $node->title,
      "%time" => $starttime,
      "%username" => $user->name,
      "%useremail" => $user_mail,
      "%info" => $signup_data,
    );
    $from = variable_get('site_mail', ini_get('sendmail_from'));

    // If a confirmation is to be sent, compose the mail message,
    // translate the string substitutions, and send it.
    if ($node->signup_send_confirmation && $user_mail) {
      $subject = t('Signup confirmation for event: !event', array(
        '!event' => $node->title,
      ));
      $message = strtr($node->signup_confirmation_email, $trans);
      drupal_mail('signup_confirmation_mail', $user_mail, $subject, $message, $from);
    }

    // If a forwarding email is to be sent, compose the mail message,
    // translate the string substitutions, and send it.
    if ($node->signup_forwarding_email) {
      $header = array(
        'From' => t('New Event Signup') . "<{$from}>",
      );
      $subject = t('Signup confirmation for event: !title', array(
        '!title' => $node->title,
      ));
      $message = t('The following information was submitted as a signup for !title', array(
        '!title' => $node->title,
      )) . "\n\r" . t('Date/Time: !time', array(
        '!time' => $starttime,
      )) . ":\n\r\n\r" . "\n\r" . t('Username:') . $user->name;
      if (!empty($user->uid)) {

        // For authenticated users, just include a link to their profile page.
        $message .= "\n\r" . t('Profile page:') . url('user/' . $user->uid, NULL, NULL, TRUE);
      }
      else {

        // For anonymous users, their email is all we've got, so disclose it.
        $message .= "\n\r" . t('E-mail:') . $user_mail;
      }
      $message .= "\n\r\n\r" . $signup_data;
      drupal_mail('signup_forwarding_mail', $node->signup_forwarding_email, $subject, $message, $from, $header);
    }
    drupal_set_message(t('Signup to !title confirmed.', array(
      '!title' => l($node->title, "node/{$node->nid}"),
    )) . $confirmation_email . $reminder_email);
  }
  else {
    drupal_access_denied();
  }
}

/**
 * Prints a schedule of the given user's signups.
 * @ingroup signup_callback
 */
function signup_user_schedule($uid) {
  $output = '';
  $user = user_load(array(
    'uid' => $uid,
  ));
  if (!$user) {
    drupal_not_found();
    return;
  }
  drupal_set_title(t('Signups for @user', array(
    '@user' => $user->name,
  )));
  $titles = signup_list_user_signups($user->uid);
  foreach ($titles as $nid => $title) {
    $node = node_load(array(
      'nid' => $nid,
    ));
    $output .= theme('signup_user_schedule', $node);
  }
  return $output;
}

/**
 * Prints the signup details for a single node when the signups tab is clicked
 * @ingroup signup_callback
 */
function signup_user_signups_form($node) {
  drupal_set_title(check_plain($node->title));

  // Display if signups are open/closed, and print a button to toggle
  $ctrl_row = array();
  if ($node->signup_completed) {
    $ctrl_row[] = array(
      t('Signups <strong>closed</strong> for this event'),
      drupal_get_form('signup_open_signups_form', $node->nid),
    );
  }
  else {
    $ctrl_row[] = array(
      t('Signups <strong>open</strong> for this event'),
      drupal_get_form('signup_close_signups_form', $node->nid),
    );
  }
  $output .= '<div class="signup-admin-row">';
  $output .= theme('table', NULL, $ctrl_row);
  $output .= '</div><br />';

  // Pull all user signed up for this event, and start table creation.
  $result = db_query("SELECT u.uid, u.name, s.anon_mail, s.signup_time, s.form_data FROM {signup_log} s INNER JOIN {users} u ON u.uid = s.uid WHERE s.nid =%d", $node->nid);
  $header = array(
    array(
      'data' => t('!users signed up', array(
        '!users' => format_plural(db_num_rows($result), '1 individual', '@count individuals'),
      )),
      'colspan' => 3,
    ),
  );
  $rows = array();

  // Get default timezone offset for the site.
  $offset = intval(variable_get('date_default_timezone', 0));

  // Loop through the users, unserializing their user data.
  while ($signed_up_user = db_fetch_object($result)) {
    $table_data = array();
    $form_data = unserialize($signed_up_user->form_data);

    // Compose the user data.
    $signup_form_data = theme('signup_custom_data', $form_data);

    // The username and the unique form identifier are different for
    // anon signups and registered user signups.  For registered users,
    // provide a link to the user profile, and use the uid as the
    // identifier.  For anon, use the user's email address as the
    // identifier and name.
    if ($signed_up_user->uid == 0) {
      $username = check_plain($signed_up_user->anon_mail);
      $id = $signed_up_user->anon_mail;
    }
    else {
      $username = theme('username', $signed_up_user);
      $id = $signed_up_user->uid;
    }

    // Build the row for this user.
    $rows[] = array(
      $username . '<br />' . gmdate(variable_get('signup_date_string', 'M jS, g:i A'), $signed_up_user->signup_time + $offset),
      $signup_form_data,
      drupal_get_form('signup_user_cancel_form_' . $id, $id, $node->nid, $signed_up_user->uid, $signed_up_user->anon_mail),
    );
  }
  $output .= theme('table', $header, $rows);
  return $output;
}
function signup_open_signups_form($nid) {
  $form['nid'] = array(
    '#type' => 'value',
    '#value' => $nid,
  );
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Open Signups'),
  );
  return $form;
}
function signup_open_signups_form_submit($form_id, $form_values) {
  signup_open_signup($form_values['nid']);
}
function signup_close_signups_form($nid) {
  $form['nid'] = array(
    '#type' => 'value',
    '#value' => $nid,
  );
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Close Signups'),
  );
  return $form;
}
function signup_close_signups_form_submit($form_id, $form_values) {
  signup_close_signup($form_values['nid']);
}

/**
 * Implementation of hook_forms().
 */
function signup_forms() {
  $args = func_get_args();
  $args = $args[0];
  $form_id = array_shift($args);
  if (strpos($form_id, 'signup_user_cancel_form') !== FALSE) {
    if ($form_id == 'signup_user_cancel_form_' . $args[0]) {
      array_shift($args);

      // Get rid of the extra uid arg.
      $forms[$form_id] = array(
        'callback' => 'signup_user_cancel_form',
        'callback arguments' => $args,
      );
      return $forms;
    }
  }
}
function signup_user_cancel_form($nid, $uid, $anon_mail) {
  $form['#base'] = 'signup_form_cancel';
  $form['nid'] = array(
    '#type' => 'value',
    '#value' => $nid,
  );
  $form['uid'] = array(
    '#type' => 'value',
    '#value' => $uid,
  );
  $form['signup_anon_mail'] = array(
    '#type' => 'value',
    '#value' => $anon_mail,
  );
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Cancel signup'),
  );
  return $form;
}

/**
 * Validates an anonymous signup email.
 *
 * @param $nid The node the user is signing up for.
 * @param $anon_mail The anonymous email address to validate.
 * @param $name Optional. The form element being validated.
 *
 * @return Boolean.  TRUE if the address validates, FALSE otherwise.
 */
function signup_validate_anon_email($nid, $anon_mail, $name = FALSE) {
  if (!valid_email_address($anon_mail)) {
    $message = t('Invalid email address entered for signup.');
  }
  elseif (db_num_rows(db_query("SELECT mail FROM {users} WHERE mail = '%s'", $anon_mail))) {
    $message = t('The email address entered belongs to a registered user.');
  }
  elseif (db_num_rows(db_query("SELECT anon_mail FROM {signup_log} WHERE anon_mail = '%s' AND nid = %d", $anon_mail, $nid))) {
    $message = t('The email address entered has already been used to sign up for this event.');
  }

  // If there's no message, it's a valid email, so return success.
  if (!isset($message)) {
    return TRUE;
  }

  // Depending on how we were called, propagate the error accordinly.
  if ($name) {
    form_set_error($name, $message);
  }
  else {
    drupal_set_message($message, 'error');
  }
  return FALSE;
}

/**
 * @defgroup signup_internal Internal module functions
 */

/**
 * Returns the form for the per-node signup settings. This is shared
 * by the settings page and the node edit page.
 * @ingroup signup_internal
 */
function _signup_admin_form($node = NULL) {

  // Load the default admin form data for new nodes.
  if (!$node || !$node->signup) {
    $result = db_fetch_object(db_query("SELECT * FROM {signup} WHERE nid = 0"));
    $node->signup_forwarding_email = $result->forwarding_email;
    $node->signup_send_confirmation = $result->send_confirmation;
    $node->signup_confirmation_email = $result->confirmation_email;
    $node->signup_send_reminder = $result->send_reminder;
    $node->signup_reminder_days_before = $result->reminder_days_before;
    $node->signup_reminder_email = $result->reminder_email;
  }
  $signup_token_description = t('Supported string substitutions: %event, %time, %username, %useremail, %info (user signup information).');
  $form['signup_forwarding_email'] = array(
    '#type' => 'textfield',
    '#title' => t('Send signups to'),
    '#default_value' => $node->signup_forwarding_email,
    '#size' => 40,
    '#maxlength' => 64,
    '#description' => t('Email address where notification of new signups will be sent. Leave blank for no notifications.'),
  );
  $form['signup_send_confirmation'] = array(
    '#type' => 'checkbox',
    '#title' => t('Send confirmation'),
    '#default_value' => $node->signup_send_confirmation,
  );
  $form['signup_confirmation_email'] = array(
    '#type' => 'textarea',
    '#title' => t('Confirmation email'),
    '#default_value' => $node->signup_confirmation_email,
    '#cols' => 40,
    '#rows' => 6,
    '#description' => t('Email sent to user upon signup.') . ' ' . $signup_token_description,
  );

  // Define a sub-tree to wrap the next 2 form elements together in an
  // inline div for better display.
  $form['signup_reminder'] = array(
    '#prefix' => '<div class="container-inline">',
    '#suffix' => '</div>',
  );
  $form['signup_reminder']['signup_send_reminder'] = array(
    '#type' => 'checkbox',
    '#title' => t('Send reminder'),
    '#default_value' => $node->signup_send_reminder,
  );
  $options = array();
  for ($i = 1; $i <= 60; $i++) {
    $options[$i] = $i;
  }
  $form['signup_reminder']['signup_reminder_days_before'] = array(
    '#type' => 'select',
    '#default_value' => $node->signup_reminder_days_before,
    '#options' => $options,
    '#suffix' => t('day(s) before event'),
  );
  $form['signup_reminder_email'] = array(
    '#type' => 'textarea',
    '#title' => t('Reminder email'),
    '#default_value' => $node->signup_reminder_email,
    '#cols' => 40,
    '#rows' => 6,
    '#description' => t('Email sent to user as an event reminder.') . ' ' . $signup_token_description,
  );
  $form['signup'] = array(
    '#type' => 'hidden',
    '#value' => 1,
  );
  return $form;
}

/**
 * Deprecated function to render signup data in a human-readable form.
 *
 * @see theme_signup_custom_data()
 * @see theme_signup_custom_data_email()
 * @see theme_signup_custom_data_rows()
 */
function signup_build_signup_data($data, $type = 'output') {
  switch ($type) {
    case 'email':
      return theme('signup_custom_data_email', $data);
    case 'table':

      // This function used to take $type == 'table' when it wanted rows. :(
      return theme('signup_custom_data_rows', $data);
    default:
      return theme('signup_custom_data', $data);
  }
}

/**
 * Returns the value to use for the %info email token for custom signup data.
 *
 * @param $signup_data
 *   Array of custom data for a particular signup.
 *
 * @see theme_signup_user_form()
 * @see theme_signup_custom_data_email()
 */
function theme_signup_email_token_custom_data($signup_data) {
  return t('SIGNUP INFORMATION') . "\n\r" . theme('signup_custom_data_email', $signup_data);
}

/**
 * Renders custom signup data into unfiltered output for use in email.
 *
 * WARNING: This theme function is recursive (it calls itself for
 * nested data), so if you override it, be sure not to change the part
 * where it does "call_user_func(__FUNCTION__)".
 *
 * @param $data
 *   Array of custom user signup data.
 *
 * @return
 *   Plain text output with newlines.
 *
 * @see theme_signup_user_form()
 */
function theme_signup_custom_data_email($data) {
  $output = '';

  // Loop through each first level element.
  foreach ($data as $key => $value) {
    if (is_array($value)) {

      // Element is nested, render it recursively.
      // Instead of the overhead of theme(), just call ourself directly.
      $output .= "\n\r" . call_user_func(__FUNCTION__, $value) . "\n\r";
    }
    else {
      $output .= $key . ': ' . $value . "\n\r";
    }
  }
  return $output;
}

/**
 * Renders custom signup user data into a table.
 *
 * @param $data
 *   Array of custom user signup data.
 *
 * @return
 *   The themed table with custom signup user data.
 *
 * @see theme_signup_user_form()
 */
function theme_signup_custom_data_table($data) {
  $output = '';
  if (is_array($data)) {
    $header = array(
      array(
        'data' => t('Your signup information'),
        'colspan' => 2,
      ),
    );
    $rows = theme('signup_custom_data_rows', $data);
    $output .= theme('table', $header, $rows);
  }
  return $output;
}

/**
 * Renders custom signup user data into table rows.
 *
 * WARNING: This theme function is recursive (it calls itself for
 * nested data), so if you override it, be sure not to change the part
 * where it does "call_user_func(__FUNCTION__)".
 *
 * @param $data
 *   Array of custom user signup data.
 *
 * @return
 *   An array of table rows.
 *
 * @see theme_signup_user_form()
 */
function theme_signup_custom_data_rows($data) {
  $rows = array();

  // Loop through each first level element.
  foreach ($data as $key => $value) {
    if (is_array($value)) {

      // Element is nested, render it recursively.
      // Instead of the overhead of theme(), just call ourself directly.
      $rows += call_user_func(__FUNCTION__, $value);
    }
    else {
      $rows[] = array(
        $key . ':',
        check_plain($value),
      );
    }
  }
  return $rows;
}

/**
 * Renders custom signup user data into a human-readable format.
 *
 * WARNING: This theme function is recursive (it calls itself for
 * nested data), so if you override it, be sure not to change the part
 * where it does "call_user_func(__FUNCTION__)".
 *
 * @param $data
 *   Array of custom user signup data.
 *
 * @return
 *   User data directly formatted in divs.
 *
 * @see theme_signup_user_form()
 */
function theme_signup_custom_data($data) {
  $output = '';

  // Loop through each first level element.
  foreach ($data as $key => $value) {
    $output .= '<div id="' . signup_id_safe($key) . '">';
    if (is_array($value)) {

      // Element is nested, render it recursively.
      // Instead of the overhead of theme(), just call ourself directly.
      $output .= call_user_func(__FUNCTION__, $value);
    }
    else {
      $output .= $key . ': ' . check_plain($value);
    }
    $output .= "</div>\n";
  }
  return $output;
}

/**
 * Converts an arbitrary string into something safe to use for a CSS id.
 *
 * Stolen wholesale from the Zen theme. ;)
 * @see zen_id_safe()
 */
function signup_id_safe($string) {

  // Replace with dashes anything that isn't a-zA-Z, numbers, dashes, or
  // underscores.
  $string = drupal_strtolower(preg_replace('/[^a-zA-Z0-9_-]+/', '-', $string));

  // If the first character is not a-z, add 'id' in front.
  // Don't use ctype_alpha since it's locale aware.
  if (!ctype_lower($string[0])) {
    $string = 'id' . $string;
  }
  return $string;
}

Functions

Namesort descending Description
signup_admin_page Prints the admin signup overview page located at admin/content/signup
signup_alter_node_form Alters the node form to inject the appropriate per-node signup settings.
signup_alter_node_type_form Alters the form for administrator settings per node type. (admin/content/types)
signup_block Implementation of hook_block().
signup_build_signup_data Deprecated function to render signup data in a human-readable form.
signup_cancel_signup Callback function for canceling signups
signup_close_signup Callback function for closing signups
signup_close_signups_form
signup_close_signups_form_submit
signup_close_signup_admin Callback function for opening signups via a link in the admin tables
signup_cron Implementation of hook_cron().
signup_filter_status_form
signup_filter_status_form_submit
signup_form Builder function for the signup form
signup_forms Implementation of hook_forms().
signup_form_alter Implementation of hook_form_alter().
signup_form_cancel Builder function for the cancel signup form
signup_form_cancel_submit Submits the cancel signup form
signup_form_submit Executes the user signup form
signup_form_validate Validates the user signup form
signup_help Implementation of hook_help().
signup_id_safe Converts an arbitrary string into something safe to use for a CSS id.
signup_list_user_signups Returns an array of node titles with links for all events the specified user has signed up for.
signup_menu Implementation of hook_menu().
signup_nodeapi hook_nodeapi implementation
signup_open_signup Callback function for reopening signups
signup_open_signups_form
signup_open_signups_form_submit
signup_open_signup_admin Callback function for opening signups via a link in the admin tables
signup_perm Implementation of hook_perm().
signup_settings_page Form builder for the settings page under admin/setttings/signup
signup_settings_page_submit Submits the signup settings form
signup_sign_up_user Signs up a user to a node.
signup_user Implementation of hook_user().
signup_user_cancel_form
signup_user_schedule Prints a schedule of the given user's signups.
signup_user_signups_form Prints the signup details for a single node when the signups tab is clicked
signup_validate_anon_email Validates an anonymous signup email.
theme_signup_custom_data Renders custom signup user data into a human-readable format.
theme_signup_custom_data_email Renders custom signup data into unfiltered output for use in email.
theme_signup_custom_data_rows Renders custom signup user data into table rows.
theme_signup_custom_data_table Renders custom signup user data into a table.
theme_signup_email_token_custom_data Returns the value to use for the %info email token for custom signup data.
theme_signup_filter_status_form
_signup_admin_form Returns the form for the per-node signup settings. This is shared by the settings page and the node edit page.