You are here

registration.module in Entity Registration 7.2

Same filename and directory in other branches
  1. 8.2 registration.module
  2. 8 registration.module
  3. 7 registration.module

File

registration.module
View source
<?php

/**
 * @file
 * Module file for registrations.
 */
module_load_include('inc', 'registration', 'includes/registration.field');
module_load_include('inc', 'registration', 'includes/registration.forms');
define('REGISTRATION_STATE_ONE', 1);
define('REGISTRATION_INSUFFICIENT_SPACES_MESSAGE', 'Insufficient spaces remaining.');

/**
 * If user has access to create registrations for only their own email address.
 */
define('REGISTRATION_REGISTRANT_TYPE_SELF', 'registration_registrant_type_self');

/**
 * If user has access to create registrations for any email address.
 */
define('REGISTRATION_REGISTRANT_TYPE_OTHER', 'registration_registrant_type_other');

/**
 * Implements hook_entity_info().
 */
function registration_entity_info() {
  $entities = array(
    'registration' => array(
      'label' => t('Registration'),
      'plural label' => t('Registrations'),
      'controller class' => 'EntityAPIController',
      'entity class' => 'Registration',
      'metadata controller class' => 'RegistrationMetadataController',
      'base table' => 'registration',
      'fieldable' => TRUE,
      'entity keys' => array(
        'id' => 'registration_id',
        'bundle' => 'type',
      ),
      'access callback' => 'registration_access',
      'bundle keys' => array(
        'bundle' => 'name',
      ),
      'bundles' => array(),
      'view modes' => array(
        'full' => array(
          'label' => t('Full Registration'),
          'custom settings' => FALSE,
        ),
      ),
      'uri callback' => 'entity_class_uri',
      'token type' => 'registration',
      'module' => 'registration',
      'label callback' => 'entity_class_label',
      'entity cache' => module_exists('entitycache'),
    ),
    'registration_type' => array(
      'label' => t('Registration type'),
      'entity class' => 'RegistrationType',
      'metadata controller class' => 'EntityDefaultMetadataController',
      'controller class' => 'RegistrationTypeController',
      'base table' => 'registration_type',
      'fieldable' => FALSE,
      'bundle of' => 'registration',
      'exportable' => TRUE,
      'entity keys' => array(
        'id' => 'id',
        'name' => 'name',
        'label' => 'label',
      ),
      'access callback' => 'registration_type_access',
      'module' => 'registration',
      // Enable the entity API's admin UI.
      'admin ui' => array(
        'path' => 'admin/structure/registration/registration_types',
        'file' => 'registration_type.admin.inc',
        'file path' => drupal_get_path('module', 'registration') . '/includes',
        'controller class' => 'RegistrationTypeUIController',
      ),
      'entity cache' => module_exists('entitycache'),
    ),
    'registration_state' => array(
      'label' => t('Registration State'),
      'plural label' => t('Registration states'),
      'controller class' => 'RegistrationStateController',
      'entity class' => 'RegistrationState',
      'base table' => 'registration_state',
      'fieldable' => FALSE,
      'entity keys' => array(
        'id' => 'registration_state_id',
        'label' => 'label',
        'name' => 'name',
      ),
      'bundles' => array(
        'registration_state' => array(
          'label' => 'Registration States',
        ),
      ),
      'admin ui' => array(
        'path' => 'admin/structure/registration/registration_states',
        'file' => 'registration.forms.inc',
        'file path' => drupal_get_path('module', 'registration') . '/includes',
        'controller class' => 'RegistrationStatesUIController',
      ),
      'token type' => 'registration_state',
      'module' => 'registration',
      'access callback' => 'registration_state_access',
      'exportable' => TRUE,
      'entity cache' => module_exists('entitycache'),
    ),
  );
  return $entities;
}

/**
 * Implements hook_entity_info_alter().
 */
function registration_entity_info_alter(&$entity_info) {

  // @todo: we're testing to ensure the schema exists; needed because running gui
  // install profile was hitting this BEFORE the schema was installed.
  if (drupal_get_schema('registration')) {

    // We are adding the info about the registration types via a hook to avoid a
    // recursion issue as loading the model types requires the entity info as well.
    // Need to do a class_exists check for updates from earlier versions
    // of registration module.
    if (class_exists('RegistrationTypeController')) {
      foreach (registration_get_types() as $type => $info) {
        $entity_info['registration']['bundles'][$type] = array(
          'label' => $info->label,
          'admin' => array(
            'path' => 'admin/structure/registration/registration_types/manage/%registration_type',
            'real path' => 'admin/structure/registration/registration_types/manage/' . $type,
            'bundle argument' => 5,
            'access arguments' => array(
              'administer registration types',
            ),
          ),
        );
      }
    }
  }
}

/**
 * Implements hook_menu().
 */
function registration_menu() {
  $items['admin/structure/registration'] = array(
    'title' => 'Registration',
    'description' => 'Administer Registration items, such as types, states, etc.',
    'page callback' => 'system_admin_menu_block_page',
    'access arguments' => array(
      'administer registration',
    ),
    'file' => 'system.admin.inc',
    'file path' => drupal_get_path('module', 'system'),
  );
  $items['registration/%registration'] = array(
    'title callback' => 'registration_page_title',
    'title arguments' => array(
      1,
    ),
    'page callback' => 'registration_view',
    'page arguments' => array(
      1,
    ),
    'access callback' => 'registration_own_access',
    'access arguments' => array(
      'view',
      1,
    ),
  );
  $items['registration/%registration/view'] = array(
    'title' => 'View',
    'page callback' => 'registration_view',
    'page arguments' => array(
      1,
    ),
    'access callback' => 'registration_own_access',
    'access arguments' => array(
      'view',
      1,
    ),
    'type' => MENU_DEFAULT_LOCAL_TASK,
  );
  $items['registration/%registration/edit'] = array(
    'title' => 'Edit',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'registration_form',
      1,
      FALSE,
    ),
    'access callback' => 'registration_own_access',
    'access arguments' => array(
      'update',
      1,
    ),
    'weight' => 10,
    'type' => MENU_LOCAL_TASK,
  );
  $items['registration/%registration/delete'] = array(
    'title' => 'Delete',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'registration_delete_confirm',
      1,
    ),
    'access callback' => 'registration_own_access',
    'access arguments' => array(
      'delete',
      1,
    ),
    'type' => MENU_CALLBACK,
  );
  $items['registration/%registration/%/%'] = array(
    'title' => 'Registration Access Check',
    'page callback' => 'registration_access_check_redirect',
    'page arguments' => array(
      1,
      2,
    ),
    'access callback' => 'registration_own_access',
    'access arguments' => array(
      2,
      1,
      3,
    ),
  );

  // Entity local tasks.
  foreach (registration_get_registration_instances() as $instance) {
    $type = $instance['entity_type'];
    if (!in_array($type, array(
      'registration',
      'registration_type',
    ))) {
      $hide_register_tab = isset($instance['settings']['hide_register_tab']) && $instance['settings']['hide_register_tab'];
      $items[$type . '/%entity_object/register'] = array(
        'load arguments' => array(
          $type,
        ),
        'title' => 'Register',
        'page callback' => 'registration_register_page',
        'page arguments' => array(
          0,
          1,
        ),
        'access callback' => 'registration_register_page_access',
        'access arguments' => array(
          0,
          1,
        ),
        'type' => $hide_register_tab ? MENU_CALLBACK : MENU_LOCAL_TASK,
      );
      $items[$type . '/%entity_object/registrations'] = array(
        'load arguments' => array(
          $type,
        ),
        'title' => 'Manage Registrations',
        'page callback' => 'registration_registrations_page',
        'page arguments' => array(
          0,
          1,
        ),
        'access callback' => 'registration_administer_registrations_access',
        'access arguments' => array(
          0,
          1,
        ),
        'type' => MENU_LOCAL_TASK,
      );
      $items[$type . '/%entity_object/registrations/list'] = array(
        'load arguments' => array(
          $type,
        ),
        'title' => 'Registrations',
        'page callback' => 'registration_registrations_page',
        'page arguments' => array(
          0,
          1,
        ),
        'access callback' => 'registration_administer_registrations_access',
        'access arguments' => array(
          0,
          1,
        ),
        'type' => MENU_DEFAULT_LOCAL_TASK,
      );
      $items[$type . '/%entity_object/registrations/settings'] = array(
        'load arguments' => array(
          $type,
        ),
        'title' => 'Settings',
        'page callback' => 'registration_entity_settings_page',
        'page arguments' => array(
          0,
          1,
        ),
        'access callback' => 'registration_administer_registrations_access',
        'access arguments' => array(
          0,
          1,
        ),
        'weight' => 9,
        'type' => MENU_LOCAL_TASK,
      );
      $items[$type . '/%entity_object/registrations/broadcast'] = array(
        'load arguments' => array(
          $type,
        ),
        'title' => 'Email Registrants',
        'page callback' => 'drupal_get_form',
        'page arguments' => array(
          'registration_registrations_broadcast_form',
          0,
          1,
        ),
        'access callback' => 'registration_administer_registrations_access',
        'access arguments' => array(
          0,
          1,
        ),
        'weight' => 10,
        'type' => MENU_LOCAL_TASK,
      );
    }
  }
  if (module_exists('devel')) {
    $items['registration/%registration/devel'] = array(
      'title' => 'Devel',
      'page callback' => 'devel_load_object',
      'page arguments' => array(
        'node',
        1,
      ),
      'access arguments' => array(
        'access devel information',
      ),
      'type' => MENU_LOCAL_TASK,
      'file path' => drupal_get_path('module', 'devel'),
      'file' => 'devel.pages.inc',
      'weight' => 100,
    );
    $items['registration/%registration/devel/load'] = array(
      'title' => 'Load',
      'type' => MENU_DEFAULT_LOCAL_TASK,
    );
  }
  return $items;
}

/**
 * Hash function used for enabling anonymous access to registrations.
 *
 * Similar to user_pass_rehash().
 */
function registration_anonymous_access_hash($registration, $options = array(), $name = "", $type = "") {
  return drupal_hmac_base64($registration->created . $registration
    ->identifier(), drupal_get_hash_salt() . $registration->anon_mail);
}

/**
 * Get a full link to view an anonymous registration, with access hash.
 *
 * For backwards-compatibility with longstanding issue queue solution.
 */
function registration_anonymous_link_get($registration, $options = array(), $name = "", $type = "") {

  // Create a link that can be used to access an anonymously created event from anywhere.
  $path = entity_uri('registration', $registration);
  $url = url($path['path'] . '/view/' . registration_anonymous_access_hash($registration), array(
    'absolute' => TRUE,
  ));
  return $url;
}

/**
 * Helper function to run a hashed URL through a permissions check first.
 */
function registration_access_check_redirect($registration, $function) {
  drupal_goto('registration/' . $registration
    ->identifier() . '/' . $function);
}

/**
 * Define our own view/update/delete access.
 *
 * Check access based on the entity permissions for all users
 * Also check that an anonymous user created the registration via session token.
 */
function registration_own_access($action, $registration, $hash = NULL) {
  if (entity_access($action, 'registration', $registration)) {

    // Only check session information if this is an anonymous user.
    if (!user_is_anonymous()) {

      // They have access to the registration and they aren't anonymous.
      return TRUE;
    }

    // Anonymous has access, and we are anonymous. Check for a valid hash.
    $in_session = FALSE;

    // If we were not handed a hash, check for it in the session or a query
    // parameter.
    if (!$hash) {

      // If they made it or validated already this session:
      if (isset($_SESSION['registration_ids'][$registration->registration_id])) {
        $in_session = TRUE;
        $hash = $_SESSION['registration_ids'][$registration->registration_id];
      }
      else {
        global $_SESSION;
        $params = drupal_get_query_parameters();
        if (isset($params['registration_hash'])) {
          $hash = $params['registration_hash'];
        }
      }
    }
    if ($hash === registration_anonymous_access_hash($registration)) {
      if (!$in_session) {
        $_SESSION['registration_ids'][$registration->registration_id] = $hash;
      }
      return TRUE;
    }
  }
  return FALSE;
}

/**
 * Implements hook_admin_paths().
 */
function registration_admin_paths() {
  $paths = array();
  if (variable_get('registration_paths_administrative', FALSE)) {
    foreach (registration_get_registration_instances() as $instance) {
      $type = $instance['entity_type'];
      if (!in_array($type, array(
        'registration',
        'registration_type',
      ))) {
        $paths[$type . '/*/registrations'] = TRUE;
        $paths[$type . '/*/registrations/list'] = TRUE;
        $paths[$type . '/*/registrations/settings'] = TRUE;
        $paths[$type . '/*/registrations/broadcast'] = TRUE;
      }
    }
  }
  return $paths;
}

/**
 * Implements hook_permission().
 */
function registration_permission() {
  $permissions = array(
    'administer registration types' => array(
      'title' => t('Administer registration types'),
      'description' => t('Manage registration types, fields, and display settings.'),
      'restrict access' => TRUE,
    ),
    'administer registration states' => array(
      'title' => t('Administer registration states'),
      'description' => t('Manage registration states, fields, and display settings.'),
      'restrict access' => TRUE,
    ),
    'administer registration' => array(
      'title' => t('Administer registration'),
      'description' => t('View, edit, delete, and manage all registrations, regardless of type.'),
      'restrict access' => TRUE,
    ),
  );
  foreach (registration_get_types() as $type_info) {
    $permissions += registration_permission_list($type_info);
  }
  return $permissions;
}

/**
 * Builds permissions for a registration type.
 *
 * @param object $info
 *   Information about a registration type.
 *
 * @return array
 *   An array of permission names and descriptions keyed by permission name.
 */
function registration_permission_list($info) {
  $type = $info->name;
  $label = $info->label;
  return array(
    "administer {$type} registration" => array(
      'title' => t('%type_name: Administer settings', array(
        '%type_name' => $label,
      )),
      'description' => t('Allow changing registration settings for all entities of this type.'),
    ),
    "administer own {$type} registration" => array(
      'title' => t('%type_name: Administer own settings', array(
        '%type_name' => $label,
      )),
      'description' => t('Allow changing registration settings for entities which a user has edit access.'),
    ),
    "view {$type} registration" => array(
      'title' => t('%type_name: View all registrations', array(
        '%type_name' => $label,
      )),
    ),
    "view own {$type} registration" => array(
      'title' => t('%type_name: View own registrations', array(
        '%type_name' => $label,
      )),
    ),
    "create {$type} registration" => array(
      'title' => t('%type_name: Create new registration for anybody', array(
        '%type_name' => $label,
      )),
    ),
    "create own {$type} registration" => array(
      'title' => t('%type_name: Create new registration for yourself', array(
        '%type_name' => $label,
      )),
    ),
    "update any {$type} registration" => array(
      'title' => t('%type_name: Edit any registrations', array(
        '%type_name' => $label,
      )),
    ),
    "update own {$type} registration" => array(
      'title' => t('%type_name: Edit own registrations', array(
        '%type_name' => $label,
      )),
    ),
    "delete any {$type} registration" => array(
      'title' => t('%type_name: Delete any registrations', array(
        '%type_name' => $label,
      )),
    ),
    "delete own {$type} registration" => array(
      'title' => t('%type_name: Delete own registrations', array(
        '%type_name' => $label,
      )),
    ),
    "edit {$type} registration state" => array(
      'title' => t('%type_name: Edit registration state', array(
        '%type_name' => $label,
      )),
    ),
  );
}

/**
 * Implements hook_user_cancel().
 */
function registration_user_cancel($edit, $account, $method) {
  if (isset($account->uid)) {
    if ($method == 'user_cancel_reassign') {
      db_update('registration')
        ->fields(array(
        'author_uid' => 0,
      ))
        ->condition('author_uid', $account->uid)
        ->execute();
    }
  }
}

/**
 * Implements hook_entity_delete().
 */
function registration_entity_delete($entity, $type) {
  list($entity_id) = entity_extract_ids($type, $entity);
  if (!$entity_id) {
    return;
  }
  if ($type == 'user') {

    // Delete all registrations owned by the user.
    $query = new EntityFieldQuery();
    $result = $query
      ->entityCondition('entity_type', 'registration')
      ->propertyCondition('author_uid', $entity_id)
      ->execute();
    if ($result) {
      registration_delete_multiple(array_keys($result['registration']));

      // Delete entries in the cache as well.
      cache_clear_all('*', 'cache_entity_registration', TRUE);
    }
  }

  // Look for registration type values related to the entity being deleted.
  $relevant_types = array();
  foreach (registration_get_types() as $registration_type) {
    if ($registration_type->registrant_entity_type == $type) {
      $relevant_types[] = $registration_type->name;
    }
  }
  if (count($relevant_types)) {

    // Users associated with {registration}.registrant_id do not own the
    // registration. Simply disassociate the registrant.
    db_update('registration')
      ->fields(array(
      'registrant_id' => NULL,
    ))
      ->condition('type', $relevant_types)
      ->condition('registrant_id', $entity_id)
      ->execute();

    // Delete entries in the cache as well.
    cache_clear_all('*', 'cache_entity_registration', TRUE);
  }

  // Delete registrations and settings for this host entity .
  db_delete('registration')
    ->condition('entity_id', $entity_id)
    ->condition('entity_type', $type)
    ->execute();
  db_delete('registration_entity')
    ->condition('entity_id', $entity_id)
    ->condition('entity_type', $type)
    ->execute();
}

/**
 * Display a registration.
 *
 * @param object $registration
 *   A fully loaded registration object.
 *
 * @return array
 *   Renderable elements.
 */
function registration_view(Registration $registration, $view_mode = 'full') {
  return $registration
    ->view($view_mode);
}

/**
 * Title callback: Generate a title for a registration entity.
 *
 * Callback for hook_menu() within system_themes_page().
 *
 * @param @registration
 *   A fully loaded registration object.
 *
 * @return string
 */
function registration_page_title(Registration $registration) {
  return $registration
    ->label();
}

/**
 * Access callback: for registration_register_page().
 *
 * Check if user has access to register for a host entity.
 *
 * @param string $entity_type
 *   The host entity type.
 * @param object $entity
 *   The host entity.
 *
 * @return bool
 *   Whether a user can create a new registration for a host entity.
 *
 * @see registration_register_page()
 * @see registration_menu()
 */
function registration_register_page_access($entity_type, $entity) {
  list($entity_id) = entity_extract_ids($entity_type, $entity);
  if ($type = registration_get_entity_registration_type($entity_type, $entity)) {
    $registration = entity_get_controller('registration')
      ->create(array(
      'entity_type' => $entity_type,
      'entity_id' => $entity_id,
      'type' => $type,
    ));
    if (entity_access('create', 'registration', $registration)) {
      $settings = registration_entity_settings($entity_type, $entity_id);
      if ($settings['status']) {
        return TRUE;
      }
    }
  }
  return FALSE;
}

/**
 * Access callback: for registration_registrations_page().
 *
 * Check if user has access to administer registrations for a host entity.
 *
 * @param string $entity_type
 *   The host entity type.
 * @param object $entity
 *   The host entity.
 * @param object $account = NULL
 *   An account for which to check access. If NULL is provided the current
 *   user is used.
 *
 * @return bool
 *   Whether a user can view registrations for a host entity.
 *
 * @see registration_registrations_page()
 * @see registration_menu()
 */
function registration_administer_registrations_access($entity_type, $entity, $account = NULL) {
  $registration_type = registration_get_entity_registration_type($entity_type, $entity);
  if ($registration_type) {
    if (user_access("administer {$registration_type} registration", $account)) {
      return TRUE;
    }
    elseif (user_access("update own {$registration_type} registration", $account) && entity_access('update', $entity_type, $entity, $account)) {
      return TRUE;
    }
  }
  return FALSE;
}

/**
 * Page callback: Add a new registration to a host entity.
 *
 * @param string $entity_type
 *   The host entity type.
 * @param object $entity
 *   The host entity.
 * @param string $default_state
 *   The default state to display in the state dropdown in the form.
 *
 * @return array
 *   A render array
 *
 * @see registration_register_access()
 * @see registration_menu()
 */
function registration_register_page($entity_type, $entity, $default_state = NULL) {
  list($entity_id) = entity_extract_ids($entity_type, $entity);
  if (registration_status($entity_type, $entity_id)) {
    $registration_type = registration_get_entity_registration_type($entity_type, $entity);
    $registration = entity_get_controller('registration')
      ->create(array(
      'entity_type' => $entity_type,
      'entity_id' => $entity_id,
      'type' => $registration_type,
    ));
    if (user_access("edit {$registration_type} registration state")) {
      $registration->state = $default_state;
    }
    return drupal_get_form('registration_form', $registration);
  }
  else {
    return t('Sorry, registrations are no longer available for %name', array(
      '%name' => entity_label($entity_type, $entity),
    ));
  }
}

/**
 * Page callback: Show a list of registrations for a host entity.
 *
 * @param string $entity_type
 *   The host entity type.
 * @param object $entity
 *   The host entity.
 *
 * @return array
 *   A render array
 *
 * @see registration_administer_registrations_access()
 * @see registration_menu()
 */
function registration_registrations_page($entity_type, $entity) {
  $header = array(
    array(
      'data' => t('id'),
      'field' => 'registration_id',
      'type' => 'property',
      'specifier' => 'registration_id',
    ),
    array(
      'data' => t('Email'),
    ),
    array(
      'data' => t('Registrant'),
      'field' => 'registrant_id',
      'type' => 'property',
      'specifier' => 'registrant_id',
    ),
    array(
      'data' => t('Created By'),
      'field' => 'author_uid',
      'type' => 'property',
      'specifier' => 'author_uid',
    ),
    array(
      'data' => t('Count'),
      'field' => 'count',
      'type' => 'property',
      'specifier' => 'count',
    ),
    array(
      'data' => t('Created'),
      'field' => 'created',
      'sort' => 'desc',
      'type' => 'property',
      'specifier' => 'created',
    ),
    array(
      'data' => t('State'),
      'field' => 'state',
      'type' => 'property',
      'specifier' => 'state',
    ),
    array(
      'data' => t('Actions'),
    ),
  );
  list($entity_id) = entity_extract_ids($entity_type, $entity);
  $label = entity_label($entity_type, $entity);
  $query = new EntityFieldQuery();
  $result = $query
    ->entityCondition('entity_type', 'registration')
    ->propertyCondition('entity_id', $entity_id)
    ->propertyCondition('entity_type', $entity_type)
    ->pager(20)
    ->tableSort($header)
    ->execute();
  if (!empty($result['registration'])) {
    $registrations = registration_load_multiple(array_keys($result['registration']));
    $rows = array();
    foreach ($registrations as $registration) {
      $wrapper = entity_metadata_wrapper('registration', $registration);
      $author = $wrapper->author
        ->value();
      $registrant_type = $wrapper->registrant
        ->type();
      $registrant = $wrapper->registrant
        ->value();
      $state = $wrapper->state
        ->value();
      $author_col = $registration->author_uid ? theme('username', array(
        'account' => $author,
      )) : '';
      $registrant_col = theme('registration_registrant_link', array(
        'registrant_type' => $registrant_type,
        'registrant' => $registrant,
      ));
      $actions = array();
      if (entity_access('view', 'registration', $registration)) {
        $actions[] = l(t('View'), 'registration/' . $registration->registration_id);
      }
      if (entity_access('update', 'registration', $registration)) {
        $actions[] = l(t('Edit'), 'registration/' . $registration->registration_id . '/edit', array(
          'query' => drupal_get_destination(),
        ));
      }
      if (entity_access('delete', 'registration', $registration)) {
        $actions[] = l(t('Delete'), 'registration/' . $registration->registration_id . '/delete', array(
          'query' => drupal_get_destination(),
        ));
      }
      $rows[] = array(
        l($registration->registration_id, 'registration/' . $registration->registration_id),
        l($wrapper->registrant_mail
          ->value(), 'mailto:' . $wrapper->registrant_mail
          ->value()),
        $registrant_col,
        $author_col,
        $registration->count,
        format_date($registration->created),
        $state ? filter_xss_admin(entity_label('registration_state', $state)) : '',
        implode(' | ', $actions),
      );
    }
    $settings = registration_entity_settings($entity_type, $entity_id);
    $table = array(
      'header' => $header,
      'rows' => $rows,
    );
    if ($settings['capacity'] != 0) {
      $table['caption'] = t('List of registrations for %title. !count of !capacity spaces are filled.', array(
        '%title' => $label,
        '!count' => '<strong>' . registration_event_count($entity_type, $entity_id) . '</strong>',
        '!capacity' => '<strong>' . $settings['capacity'] . '</strong>',
      ));
    }
    else {
      $table['caption'] = t('List of registrations for %title. !count spaces are filled.', array(
        '%title' => $label,
        '!count' => '<strong>' . registration_event_count($entity_type, $entity_id) . '</strong>',
      ));
    }
    $out = theme('table', $table) . theme('pager');
  }
  else {
    $out = t('There are no registrants for %name', array(
      '%name' => $label,
    ));
  }
  return $out;
}

/**
 * Page callback for entity registration settings.
 *
 * @param $entity_type
 * @param $entity
 *
 * @return array
 *   Registration entity settings form.
 */
function registration_entity_settings_page($entity_type, $entity) {
  list($entity_id) = entity_extract_ids($entity_type, $entity);
  $settings = registration_entity_settings($entity_type, $entity_id);
  return drupal_get_form('registration_entity_settings_form', $settings, $entity_type, $entity_id);
}

/**
 * Determines if a host entity has spaces remaining.
 *
 * @param string $entity_type
 *   The host entity type.
 * @param int $entity_id
 *   The host entity ID.
 * @param int $spaces
 *   (optional) Used if validating a new registration. The number of spaces
 *   attempting to fill.
 * @param int $registration_id
 *   The registration ID. Used to exclude specified registration from count.
 *
 * @return bool
 *
 * @see registration_status()
 */
function registration_has_room($entity_type, $entity_id, $spaces = 1, $registration_id = NULL, $reset = FALSE) {
  $settings = registration_entity_settings($entity_type, $entity_id, $reset);
  $capacity = $settings['capacity'];
  if ($capacity) {
    $count = registration_event_count($entity_type, $entity_id, $registration_id, $reset) + $spaces;
    if ($capacity - $count < 0) {
      return FALSE;
    }
  }
  return TRUE;
}

/**
 * Determines current number of spaces filled for a host entity.
 *
 * @param string $entity_type
 *   The host entity type.
 * @param int $entity_id
 *   The host entity ID.
 * @param int $registration_id
 *   The registration ID. If specified, exclude identified registration from count.
 *
 * @return int
 *   The number of spaces remaining for a host entity.
 *
 * @see registration_has_room()
 */
function registration_event_count($entity_type, $entity_id, $registration_id = NULL, $reset = FALSE) {
  $count =& drupal_static(__FUNCTION__ . '_' . $entity_type . '_' . $entity_id . '_' . $registration_id, FALSE);
  if (!$count || $reset) {
    $query = db_select('registration', 'r');
    $query
      ->addExpression('sum(count)', 'count');
    $query
      ->condition('entity_id', $entity_id);
    $query
      ->condition('entity_type', $entity_type);
    if ($registration_id != NULL) {
      $query
        ->condition('registration_id', $registration_id, '<>');
    }
    $active_held_states = array_merge(registration_get_active_states(), registration_get_held_states());
    if (!empty($active_held_states)) {
      $query
        ->condition('state', $active_held_states, 'IN');
    }
    $result = $query
      ->execute();
    $count = $result
      ->fetchField();
    $count = $count == '' ? 0 : $count;
  }

  // Allow other mods to override count.
  $settings = registration_entity_settings($entity_type, $entity_id);
  $context = array(
    'entity_type' => $entity_type,
    'entity_id' => $entity_id,
    'registration_id' => $registration_id,
    'settings' => $settings,
  );
  drupal_alter('registration_event_count', $count, $context);
  return $count;
}

/**
 * Implements hook_entity_insert().
 */
function registration_entity_insert($entity, $entity_type) {
  $registration_type = registration_get_entity_registration_type($entity_type, $entity);
  if ($registration_type !== FALSE) {
    registration_entity_set_default_settings($entity_type, $entity);
  }
}

/**
 * Implements hook_entity_update().
 */
function registration_entity_update($entity, $entity_type) {
  $registration_type = registration_get_entity_registration_type($entity_type, $entity);
  if ($registration_type !== FALSE) {
    list($entity_id) = entity_extract_ids($entity_type, $entity);
    $settings = registration_entity_settings($entity_type, $entity_id);

    // no settings yet, try to set defaults
    if (!$settings) {
      registration_entity_set_default_settings($entity_type, $entity);
    }
  }
}

/**
 * Sets the the registration entity settings to the deafults.
 *
 * @param $entity_type
 * @param $entity
 */
function registration_entity_set_default_settings($entity_type, $entity) {
  list($entity_id, , $bundle) = entity_extract_ids($entity_type, $entity);
  $registration_instances = registration_get_registration_instances(array(
    'entity_type' => $entity_type,
    'bundle' => $bundle,
  ));
  foreach ($registration_instances as $instance) {
    if (isset($instance['settings']['default_registration_settings'])) {
      $settings = registration_convert_form_settings($instance['settings']['default_registration_settings']);
      registration_update_entity_settings($entity_type, $entity_id, $settings);
    }
  }
}

/**
 * Get registration settings for a host entity.
 *
 * @param string $entity_type
 *   The host entity type.
 * @param int $entity_id
 *   The host entity ID.
 *
 * @return array|bool
 *   A row from {registration_entity}, or FALSE if no settings exist.
 */
function registration_entity_settings($entity_type, $entity_id, $reset = FALSE) {
  $result =& drupal_static(__FUNCTION__ . $entity_type . $entity_id);
  if (!$result || $reset) {
    $result = db_select('registration_entity', 're')
      ->fields('re')
      ->condition('entity_id', $entity_id, '=')
      ->condition('entity_type', $entity_type, '=')
      ->execute()
      ->fetchAssoc();
    if ($result) {
      $result['settings'] = unserialize($result['settings']);
    }
  }
  return $result;
}

/**
 * Implements hook_theme().
 */
function registration_theme() {
  return array(
    'registration_link' => array(
      'variables' => array(
        'label' => NULL,
        'path' => NULL,
      ),
    ),
    'registration_registrant_link' => array(
      'variables' => array(
        'registrant' => NULL,
      ),
    ),
    'registration_state_overview_form' => array(
      'file' => 'includes/registration.forms.inc',
      'render element' => 'form',
    ),
    'registration_property_field' => array(
      'variables' => array(
        'label_hidden' => FALSE,
        'title_attributes' => NULL,
        'label' => '',
        'content_attributes' => NULL,
        'items' => array(),
        'item_attributes' => array(
          0 => '',
        ),
        'classes' => '',
        'attributes' => '',
      ),
    ),
  );
}

/**
 * Theme function for registration properties.
 *
 * Simple wrapper around theme_field that sets default values and ensures
 * properties render consistently with fields.
 */
function theme_registration_property_field($variables) {
  return theme_field($variables);
}

/**
 * Theme handler for registration links.
 *
 * @param array $variables
 *   Contains the label and path for the link.
 */
function theme_registration_link($variables) {
  $output = '';
  $registration_label = $variables['label'];
  $registration_path = $variables['path'];
  $registration_options = isset($variables['options']) ? $variables['options'] : array();
  $output .= l($registration_label, $registration_path, $registration_options);
  return $output;
}

/**
 * Theme handler for registrant links.
 *
 * @param array $variables
 *   Contains the registrant type and entity.
 *
 * @return string
 */
function theme_registration_registrant_link($variables) {
  $registrant_type = $variables['registrant_type'];
  $registrant = $variables['registrant'];
  if ($registrant_type && $registrant) {
    $registrant_label = entity_label($registrant_type, $registrant);
    $registrant_uri = entity_uri($registrant_type, $registrant);
    return l($registrant_label, $registrant_uri['path']);
  }
  return t('Anonymous');
}

/**
 * Implements hook_mail().
 */
function registration_mail($key, &$message, $params) {
  $subject = $params['subject'];
  $body = $params['message'];
  $message['subject'] .= str_replace(array(
    "\r",
    "\n",
  ), '', $subject);
  $message['body'][] = drupal_html_to_text($body);
}

/**
 * Send an email to all registrations for a host entity.
 *
 * @param string $entity_type
 *   The host entity type.
 * @param int $entity_id
 *   The host entity ID.
 * @param string $subject
 *   Subject of email.
 * @param string $message
 *   Message body of email.
 */
function registration_send_broadcast($entity_type, $entity_id, $subject, $message) {
  global $language;

  // Return early if there are no active registration states configured.
  $active_states = registration_get_active_states();
  if (empty($active_states)) {
    $error_message = t('There are no active registration states configured. For email to be sent to registrants, an active registration state must be specified at "admin/structure/registration/registration_states".');
    drupal_set_message($error_message, 'error');
    watchdog('registration', $error_message, WATCHDOG_ERROR);
    return;
  }

  // grab registration entity settings
  $settings = registration_entity_settings($entity_type, $entity_id);
  $from = $settings['settings']['from_address'];

  // grab all registrations
  $query = new EntityFieldQuery();
  $entities = $query
    ->entityCondition('entity_type', 'registration')
    ->propertyCondition('entity_id', $entity_id)
    ->propertyCondition('entity_type', $entity_type)
    ->propertyCondition('state', $active_states, 'IN')
    ->execute();
  if (!empty($entities)) {
    $recipients = array();
    $message_template = $message;
    $params = array(
      'subject' => $subject,
      'message' => $message,
    );

    // load registrations and build an array of recipients
    $registrations = registration_load_multiple(array_keys($entities['registration']));

    // Give other modules a chance to alter registrations (for example, did
    // someone opt-out of the reminder?
    // Invoke hook_registration_send_broadcast_alter(),
    // hook_registration_send_broadcast_ENTITY_TYPE_alter() and
    // hook_registration_send_broadcast_ENTITY_TYPE_ENTITY_ID_alter()
    $hooks = array(
      'registration_send_broadcast',
    );
    $hooks[] = 'registration_send_broadcast_' . $entity_type;
    $hooks[] = 'registration_send_broadcast_' . $entity_type . '_' . $entity_id;
    $context = array(
      'entity_type' => $entity_type,
      'entity_id' => $entity_id,
    );
    drupal_alter($hooks, $registrations, $context);

    // send the email to each registrant and communicate results
    $success_count = 0;
    foreach ($registrations as $registration) {
      $wrapper = entity_metadata_wrapper('registration', $registration);
      $registrant_mail = $wrapper->registrant_mail
        ->value();
      if (!$registrant_mail) {
        continue;
      }
      $recipients[] = $registrant_mail;
      $entity = entity_load_single($entity_type, $entity_id);
      if (module_exists('token')) {
        $message = token_replace($message_template, array(
          $entity_type => $entity,
          'registration' => $registration,
        ), array(
          'clear' => TRUE,
        ));
      }
      $params['message'] = $message;
      $result = drupal_mail('registration', 'broadcast', $registrant_mail, $language, $params, $from);
      if ($result['result'] !== FALSE) {
        $success_count++;
      }
      else {
        watchdog('registration', 'Failed to send registration broadcast email to %email.', array(
          '%email' => $registrant_mail,
        ), WATCHDOG_ERROR);
      }
    }
    if ($success_count) {
      drupal_set_message(t('Registration broadcast sent to @count registrants.', array(
        '@count' => $success_count,
      )));
      watchdog('registration', 'Registration broadcast sent to @count registrants.', array(
        '@count' => $success_count,
      ));
    }
  }
  else {
    drupal_set_message(t('There are no participants registered for this %type.', array(
      '%type' => $entity_type,
    )), 'warning');
  }
}

/**
 * Update or create registration settings for a host entity.
 *
 * Updates settings for a host entity, and displays a message to the user.
 *
 * @param string $entity_type
 *   The host entity type.
 * @param int $entity_id
 *   The host entity ID.
 * @param array $settings
 *   Array keyed by field names from {registration_entity}
 */
function registration_update_entity_settings($entity_type, $entity_id, $settings) {

  // Insert or update registration entity settings.
  db_merge('registration_entity')
    ->key(array(
    'entity_id' => $entity_id,
    'entity_type' => $entity_type,
  ))
    ->fields($settings)
    ->execute();
  drupal_set_message(t('Registration settings have been saved.'));
}

/**
 * Implements hook_cron().
 */
function registration_cron() {

  //@TODO: need to have a sensible batch limit, passed in as a limit param

  // Send reminder emails.
  // Grab all registrations that have reminders set for this day.
  $results = db_select('registration_entity', 're')
    ->fields('re')
    ->condition('send_reminder', 1)
    ->condition('reminder_date', date('Y-m-d G:i:s'), '<=')
    ->condition('reminder_date', date('Y-m-d G:i:s', strtotime('-2 days')), '>')
    ->range(0, 10)
    ->addTag('registration_cron_select')
    ->execute()
    ->fetchAllAssoc('entity_id');
  foreach ($results as $result) {
    $entity = entity_load_single($result->entity_type, $result->entity_id);
    $message = $result->reminder_template;
    $subject = t('Reminder for !label', array(
      '!label' => entity_label($result->entity_type, $entity),
    ));
    registration_send_broadcast($result->entity_type, $result->entity_id, $subject, $message);

    // Set reminder flag to off.
    db_update('registration_entity')
      ->fields(array(
      'send_reminder' => 0,
    ))
      ->condition('entity_id', $result->entity_id)
      ->condition('entity_type', $result->entity_type)
      ->execute();
  }

  // Remove any registrations from held state that qualify.
  $held_states = registration_get_held_states();
  if (!empty($held_states)) {
    $results = db_select('registration', 'r')
      ->fields('r')
      ->condition('state', $held_states, 'IN')
      ->execute()
      ->fetchAllAssoc('registration_id');
    $registrations = registration_load_multiple(array_keys($results));
    foreach ($registrations as $registration) {
      $registration_wrapper = entity_metadata_wrapper('registration', $registration);

      // Determine whether hold expire minimum time time has elapsed.
      // Setting for how long the hold lasts is stored on the registration type entity.
      $registration_type = registration_type_load($registration_wrapper
        ->getBundle());

      // Change registration state if hold has expired.
      // Expiration time is in hours - hence the multiplication.
      if ($registration_type->held_expire !== '0' && time() - $registration_type->held_expire * 60 * 60 > $registration->updated) {

        // Set registration state to what is configured for its registration type.
        $registration_wrapper->state
          ->set($registration_type->held_expire_state);
        $registration_wrapper
          ->save();
      }
    }
  }
}

/**
 * Check if new registrations are permitted for a host entity.
 *
 * Modules may implement hook_registration_status_alter() to alter the status at
 * runtime.
 *
 * @param string $entity_type
 *   The host entity type.
 * @param int $entity_id
 *   The host entity ID.
 * @param bool $reset
 *   (optional) Whether to force checking status in case registration_status
 *   may have been called previously for this host entity.
 * @param int $spaces
 *   (optional) The number of spaces to check that there is room for.
 * @param int $registration_id
 *   (optional) A registration id to exclude from the has room check.
 * @param array $errors
 *   (optional) An array of error message strings.
 *
 * @return bool
 */
function registration_status($entity_type, $entity_id, $reset = FALSE, $spaces = 1, $registration_id = NULL, &$errors = array()) {
  $checked =& drupal_static(__FUNCTION__, array());
  if (!$reset && isset($checked[$entity_type][$entity_id])) {
    if (!is_array($checked[$entity_type][$entity_id]['errors'])) {
      $checked[$entity_type][$entity_id]['errors'] = array();
    }
    $errors = is_array($errors) ? array_merge($errors, $checked[$entity_type][$entity_id]['errors']) : $checked[$entity_type][$entity_id]['errors'];
    return $checked[$entity_type][$entity_id]['status'];
  }
  $entity = entity_load_single($entity_type, $entity_id);
  $registration_type = registration_get_entity_registration_type($entity_type, $entity);

  // The host entity does not have registrations enabled.
  if (!$registration_type) {
    return FALSE;
  }
  $settings = registration_entity_settings($entity_type, $entity_id, $reset);
  registration_interpret_settings($settings, $entity_type, $entity);
  $status = $settings['status'];
  $open = isset($settings['open']) ? strtotime($settings['open']) : NULL;
  $close = isset($settings['close']) ? strtotime($settings['close']) : NULL;
  $now = REQUEST_TIME;

  // only explore other settings if main status is enabled
  if ($status) {

    // check max allowed spaces per registration
    if (isset($settings['settings']['maximum_spaces']) && $settings['settings']['maximum_spaces'] && $spaces > $settings['settings']['maximum_spaces']) {
      $status = FALSE;
      $errors[] = t('You may not register for more than @count spaces.', array(
        '@count' => $settings['settings']['maximum_spaces'],
      ));
    }

    // check capacity
    if (!registration_has_room($entity_type, $entity_id, $spaces, $registration_id, $reset)) {
      $status = FALSE;
      $errors[] = t(REGISTRATION_INSUFFICIENT_SPACES_MESSAGE);
    }

    // check open date range
    if (isset($open) && $now < $open) {
      $status = FALSE;
      $errors[] = t('Registration is not yet open.');
    }

    // check close date range
    if (isset($close) && $now >= $close) {
      $status = FALSE;
      $errors[] = t('Registration is closed.');
    }
  }
  else {
    $errors[] = t('Registration is disabled.');
  }

  // Allow other mods to override status.
  $context = array(
    'entity_type' => $entity_type,
    'entity_id' => $entity_id,
    'spaces' => $spaces,
    'registration_id' => $registration_id,
    'errors' => &$errors,
  );
  drupal_alter('registration_status', $status, $context);
  $checked[$entity_type][$entity_id] = array(
    'status' => $status,
    'errors' => $errors,
  );
  return $status;
}

/**
 * Get the registration type bundle for a host entity.
 *
 * @param string $entity_type
 *   The host entity type.
 * @param object $entity
 *   The host entity.
 *
 * @return string|bool
 *   Registration type associated with a host entity, or FALSE if none is
 *   associated.
 */
function registration_get_entity_registration_type($entity_type, $entity) {
  $fields = field_read_fields(array(
    'type' => 'registration',
  ));
  foreach ($fields as $field) {
    if (isset($entity->{$field['field_name']})) {
      $items = field_get_items($entity_type, $entity, $field['field_name']);

      // we're assuming there's only a single value in this field
      if (!empty($items) && count($items) == 1 && !empty($items[0]['registration_type'])) {
        return $items[0]['registration_type'];
      }
    }
  }
  return FALSE;
}

/**
 * Return all registration field instances.
 *
 * @return array
 *   A list of field instances
 */
function registration_get_registration_instances($params = array()) {
  $registration_fields = field_read_fields(array(
    'type' => 'registration',
  ));
  $registration_instances = array();
  if (!empty($registration_fields)) {
    $field_name = array(
      'field_name' => array_keys($registration_fields),
    );
    $params = array_merge($field_name, $params);
    $registration_instances = field_read_instances($params);
  }
  return $registration_instances;
}

/**
 * Implement hook_token_info().
 */
function registration_token_info() {
  $type = array(
    'name' => t('Registration'),
    'description' => t('Tokens related to individual Registrations.'),
    'needs-data' => 'registration',
  );
  $registration['entity'] = array(
    'name' => t("Registration Host Entity"),
    'description' => t("The host entity for the registration."),
  );
  $registration['registrant'] = array(
    'name' => t("Registrant Entity"),
    'description' => t("The entity the registration is for."),
  );
  return array(
    'types' => array(
      'registration' => $type,
    ),
    'tokens' => array(
      'registration' => $registration,
    ),
  );
}

/**
 * Implements hook_tokens().
 */
function registration_tokens($type, $tokens, array $data = array(), array $options = array()) {
  if ($type == 'registration' && !empty($data['registration'])) {
    $registration = $data['registration'];
    $wrapper = entity_metadata_wrapper('registration', $data['registration']);
    $replacements = array();
    if ($entity_tokens = token_find_with_prefix($tokens, 'entity')) {
      $entity = $wrapper->entity
        ->value();
      $replacements += token_generate($registration->entity_type, $entity_tokens, array(
        $registration->entity_type => $entity,
      ), $options);
    }
    if ($entity_tokens = token_find_with_prefix($tokens, 'registrant')) {
      $registration_type = registration_get_types($wrapper
        ->type());
      $registrant = $wrapper->registrant
        ->value();
      $replacements += token_generate($registration_type->registrant_entity_type, $entity_tokens, array(
        $registration_type->registrant_entity_type => $registrant,
      ), $options);
    }
    return $replacements;
  }
}

/**
 * Determine if a person has an active registration for a host entity.
 *
 * @param Registration $registration
 *   A fully loaded registration object.
 * @param string $mail
 *   (optional) An email address.
 *
 * @return bool
 */
function registration_is_registered(Registration $registration, $mail) {

  // Must provide an email.
  if (!$mail) {
    return FALSE;
  }
  $registrations = registration_get_registrations($registration->entity_type, $registration->entity_id, $mail);

  // Exclude existing registration.
  if (isset($registration->registration_id)) {
    unset($registrations[$registration->registration_id]);
  }
  return count($registrations) > 0;
}

/**
 * Implements hook_field_extra_fields().
 */
function registration_field_extra_fields() {

  // expose the email property on the fields and display settings forms.
  $extra = array();
  foreach (registration_get_types() as $type => $reg_type) {
    $extra['registration'][$type] = array(
      'form' => array(
        'who_is_registering' => array(
          'label' => t('Registrant'),
          'description' => t('Select who is registering for this event.'),
          'weight' => 0,
        ),
        'anon_mail' => array(
          'label' => t('Email'),
          'description' => t('Registrant\'s email address.'),
          'weight' => 0,
        ),
      ),
      'display' => array(
        'mail' => array(
          'label' => t('Email'),
          'description' => t('Registrant\'s email address.'),
          'weight' => 0,
        ),
        'host_entity_link' => array(
          'label' => t('Entity Link'),
          'description' => t('Link to host entity.'),
          'weight' => 0,
        ),
        'created' => array(
          'label' => t('Created'),
          'description' => t('When the registration was created.'),
          'weight' => 0,
        ),
        'updated' => array(
          'label' => t('Updated'),
          'description' => t('When the registration was updated.'),
          'weight' => 0,
        ),
        'spaces' => array(
          'label' => t('Spaces Used'),
          'description' => t('How many spaces were used in this registration.'),
          'weight' => 0,
        ),
        'author' => array(
          'label' => t('Author'),
          'description' => t('User who created the registration.'),
          'weight' => 0,
        ),
        'registrant' => array(
          'label' => t('Registrant'),
          'description' => t('Registrant associated with this registration.'),
          'weight' => 0,
        ),
        'state' => array(
          'label' => t('State'),
          'description' => t('State of the registration.'),
          'weight' => 0,
        ),
      ),
    );
  }
  return $extra;
}

/**
 * Load registrations for a host entity, optionally filtered to a particular registrant email.
 *
 * @param string $entity_type
 *  Host entity type.
 * @param object $entity_id
 *  Host entity id.
 * @param string $registrant_mail
 *  Registrant mail - optional.
 * @param array $states
 *  List of names of States to look for.
 *
 * @return array
 *  List of registrations.
 */
function registration_get_registrations($entity_type, $entity_id, $registrant_mail = NULL, $states = NULL) {
  $entity = entity_load_single($entity_type, $entity_id);
  $registration_type = registration_get_types(registration_get_entity_registration_type($entity_type, $entity));
  if (!$states) {
    $states = registration_get_active_states();
  }
  $query = db_select('registration', 'r')
    ->fields('r', array(
    'registration_id',
  ))
    ->condition('entity_type', $entity_type)
    ->condition('entity_id', $entity_id)
    ->condition('state', $states, 'IN');

  // There's a registrant with this email, check to see if they are registered.
  $registrant = registration_registrant_load_by_mail($registration_type, $registrant_mail);
  if ($registrant) {
    $query
      ->condition(db_or()
      ->condition('anon_mail', $registrant_mail)
      ->condition('registrant_id', $registrant
      ->getIdentifier()));
  }
  else {
    $query
      ->condition('anon_mail', $registrant_mail);
  }
  $result = $query
    ->execute()
    ->fetchCol();
  if ($result) {
    return entity_load('registration', $result);
  }
  return array();
}

/**
 * Get the registrant entity for a registration, or NULL if no registrant is associated.
 *
 * @param $registration
 *  Registration object.
 *
 * @return object
 *  Registrant entity object.
 */
function registration_get_registrant($registration) {
  $registrant = NULL;
  if (is_numeric($registration->registrant_id)) {
    $type = registration_get_types($registration->type);
    $entity = entity_load_single($type->registrant_entity_type, $registration->registrant_id);
    if ($entity) {
      $registrant = entity_metadata_wrapper($type->registrant_entity_type, $entity);
    }
  }
  return $registrant;
}

/**
 * Fetch a registrant entity for a given email address, or NULL if no entity found.
 *
 * @param object $registration_type
 *  Registration type object.
 * @param string $mail
 *  Email address.
 *
 * @return object
 *  Entity object.
 */
function registration_registrant_load_by_mail($registration_type, $mail) {
  $registrant_entity_type = $registration_type->registrant_entity_type;
  $property_parts = explode(':', $registration_type->registrant_email_property);
  $email_property = $property_parts[0];
  $email_field_col = isset($property_parts[1]) ? $property_parts[1] : NULL;
  $query = new EntityFieldQuery();
  $query
    ->entityCondition('entity_type', $registrant_entity_type);
  if ($email_field_col) {
    $query
      ->fieldCondition($email_property, $email_field_col, $mail, '=');
  }
  else {
    $query
      ->propertyCondition($email_property, $mail, '=');
  }
  $result = $query
    ->execute();
  if (!empty($result[$registrant_entity_type])) {
    $ids = array_keys($result[$registrant_entity_type]);
    $registrant = entity_load_single($registrant_entity_type, reset($ids));
    if ($registrant) {
      return entity_metadata_wrapper($registrant_entity_type, $registrant);
    }
  }
  return NULL;
}

/**
 * Create a new registrant object for a given registration type. Optionally set registrant email.
 *
 * @param RegistrationType $registration_type
 * @param string $mail
 * @return object
 */
function registration_registrant_create($registration_type, $mail = NULL) {
  $registrant_entity_type = $registration_type->registrant_entity_type;
  $property_parts = explode(':', $registration_type->registrant_email_property);
  $email_property = $property_parts[0];
  $email_field_col = isset($property_parts[1]) ? $property_parts[1] : NULL;
  $values = array();
  $info = entity_get_info($registrant_entity_type);
  if (!empty($info['entity keys']['bundle'])) {
    $values[$info['entity keys']['bundle']] = $registration_type->registrant_bundle;
  }
  $registrant = entity_create($registrant_entity_type, $values);
  if ($mail) {
    if ($email_field_col) {
      $registrant->{$email_property} = array(
        LANGUAGE_NONE => array(
          array(
            $email_field_col => $mail,
          ),
        ),
      );
    }
    else {
      $registrant->{$email_property} = $mail;
    }
  }
  return entity_metadata_wrapper($registrant_entity_type, $registrant);
}

/**
 * Return all registration state entities.
 *
 * @param array $conditions - key => value array of properties and conditions
 *   that restrict what registration_state entities will be returned
 *
 * @return array
 *   An array of registration state entities.
 */
function registration_states($conditions = array()) {
  $states =& drupal_static(__FUNCTION__ . serialize($conditions), array());
  if (!empty($states)) {
    return $states;
  }
  $entity_type = 'registration_state';
  $query = new EntityFieldQuery();
  $query
    ->entityCondition('entity_type', $entity_type)
    ->propertyOrderBy('weight', 'ASC');
  foreach ($conditions as $col => $val) {
    $query
      ->propertyCondition($col, $val);
  }
  if ($results = $query
    ->execute()) {
    $states = entity_load($entity_type, array_keys($results[$entity_type]));
  }
  return $states;
}

/**
 * Return an array of all active state machine names.
 *
 * @return array
 */
function registration_get_active_states() {
  $active = array();
  $states = registration_states(array(
    'active' => TRUE,
  ));
  foreach ($states as $state) {
    $active[] = $state
      ->identifier();
  }
  return $active;
}

/**
 * Return an array of all held state machine names.
 *
 * @return array
 */
function registration_get_held_states() {
  $held = array();
  $states = registration_states(array(
    'held' => TRUE,
  ));
  foreach ($states as $state) {
    $held[] = $state
      ->identifier();
  }
  return $held;
}

/**
 * Return default state
 *
 * @return array
 */
function registration_get_default_state($type = NULL) {

  // If a type of registration is specified, look for its default registration
  // state before using the global default.
  $states_query = array(
    'default_state' => 1,
  );
  if (isset($type)) {
    $reg_type = registration_get_types($type);
    $states_query = isset($reg_type->default_state) ? array(
      'name' => $reg_type->default_state,
    ) : $states_query;
  }
  $states = registration_states($states_query);
  return !empty($states) ? reset($states) : NULL;
}

/**
 * Gets an array of all registration states, keyed by the name.
 *
 * @param $name
 *   If set, the type with the given name is returned.
 */
function registration_get_states($name = NULL) {
  $types = entity_load_multiple_by_name('registration_state', isset($name) ? array(
    $name,
  ) : FALSE);
  return isset($name) ? reset($types) : $types;
}

/**
 * Get an array of states structured as options for a form select elements
 *
 * @param array $conditions
 *
 * @return array
 */
function registration_get_states_options($conditions = array()) {
  $options = array();

  // Rules likes to pass an object as the first param in an option list callback. Get rid of it.
  if (!is_array($conditions)) {
    $conditions = array();
  }
  $states = registration_states($conditions);
  foreach ($states as $state) {
    $options[$state
      ->identifier()] = entity_label('registration_state', $state);
  }
  return $options;
}

/**
 * Callback to get $registration->host.
 */
function registration_property_host_get(Registration $registration, array $options, $property_name, $entity_type) {
  $entity = entity_load_single($registration->entity_type, $registration->entity_id);
  return entity_metadata_wrapper($registration->entity_type, $entity);
}

/**
 * Callback to set $registration->host.
 */
function registration_property_host_set(Registration $registration, $name, $value, $lang, $type, $info) {
  $registration->entity_type = $value
    ->type();
  $registration->entity_id = $value
    ->getIdentifier();
}

/**
 * Callback to get $registration->author.
 */
function registration_property_author_get(Registration $registration, array $options, $property_name, $entity_type) {
  if (is_numeric($registration->author_uid)) {
    $entity = entity_load_single('user', $registration->author_uid);
    return entity_metadata_wrapper('user', $entity);
  }
}

/**
 * Callback to get $registration->registrant.
 */
function registration_property_registrant_get(Registration $registration, array $options, $property_name, $entity_type) {
  return registration_get_registrant($registration);
}

/**
 * Callback to set $registration->registrant that supports null value.
 */
function registration_property_registrant_set(Registration $registration, $name, $value, $lang, $type, $info) {
  $registration->{$info['schema field']} = is_object($value) ? $value
    ->getIdentifier() : NULL;
}

/**
 * Callback to get $registration->registrant->mail.
 */
function registration_property_registrant_mail_get($registration, array $options, $name, $type) {
  $registrant = registration_get_registrant($registration);
  $type = registration_get_types($registration->type);
  $property_parts = explode(':', $type->registrant_email_property);
  $property = $property_parts[0];
  $field_col = isset($property_parts[1]) ? $property_parts[1] : NULL;
  $mail = NULL;
  if (isset($registrant->{$property})) {
    $value = $registrant->{$property}
      ->value();

    // If we have multiple values, just grab the first one:
    if (is_array($value) && isset($value[0])) {
      $value = $value[0];
    }
    if ($field_col && isset($value[$field_col])) {
      $mail = $value[$field_col];
    }
    else {
      $mail = $value;
    }
  }
  return $mail ? $mail : $registration->anon_mail;
}

/**
 * Loads a registration by ID.
 */
function registration_load($registration_id) {
  if (empty($registration_id)) {
    return FALSE;
  }
  $registrations = registration_load_multiple(array(
    $registration_id,
  ), array());
  return $registrations ? reset($registrations) : FALSE;
}

/**
 * Loads multiple registrations by ID or based on a set of matching conditions.
 *
 * @see entity_load()
 *
 * @param $registration_ids
 * @param $conditions
 *   An array of conditions on the {registration} table in the form
 *     'field' => $value.
 * @param $reset
 *   Whether to reset the internal registration loading cache.
 *
 * @return
 *   An array of registration objects indexed by registration_id.
 */
function registration_load_multiple($registration_ids = array(), $conditions = array(), $reset = FALSE) {
  if (empty($registration_ids) && empty($conditions)) {
    return array();
  }
  return entity_load('registration', $registration_ids, $conditions, $reset);
}

/**
 * Deletes multiple registrations by ID.
 *
 * @param $registration_ids
 *   An array of registration IDs to delete.
 *
 * @return
 *   TRUE on success, FALSE otherwise.
 */
function registration_delete_multiple($registration_ids) {
  return entity_get_controller('registration')
    ->delete($registration_ids);
}

/**
 * Saves a registration.
 *
 * @param $registration
 *   The full registration object to save.
 *
 * @return
 *   If the record insert or update failed, returns FALSE. If it succeeded,
 *   returns SAVED_NEW or SAVED_UPDATED, depending on the operation performed.
 */
function registration_save(Registration $registration) {
  return $registration
    ->save();
}

/**
 * Access callback: Entity API for Registration entities.
 *
 * Checks if a user has permission to execute an operation on a registration
 * entity.
 *
 * @param string $op
 *   Operation user wishes to perform.
 * @param Registration $registration
 *   (optional) A fully loaded registration object.
 * @param object $account
 *   (optional) An user object, or omit for logged in user.
 *
 * @return bool
 *
 * @see registration_entity_info()
 */
function registration_access($op, Registration $registration = NULL, $account = NULL) {
  global $user;
  $account = isset($account) ? $account : $user;
  $admin = user_access('administer registration', $account);
  if (!isset($registration)) {
    return $admin;
  }
  $type = $registration
    ->bundle();

  // bypass further access checks if user can administer registration
  if ($admin || user_access("administer {$type} registration", $account)) {
    return TRUE;
  }

  // First grant access to the entity for the specified operation if no other
  // module denies it and at least one other module says to grant access.
  $access_results = module_invoke_all('registration_access', $op, $registration, $account);
  if (in_array(FALSE, $access_results, TRUE)) {
    return FALSE;
  }
  elseif (in_array(TRUE, $access_results, TRUE)) {
    return TRUE;
  }
  $wrapper = entity_metadata_wrapper('registration', $registration);
  $author = $wrapper->author
    ->value();
  $account_own = $author && $author->uid == $account->uid;

  // Fall back to assigned permissions
  switch ($op) {
    case 'view':
      return $account_own && user_access("view own {$type} registration", $account) || user_access("view {$type} registration", $account);
    case 'update':
      return $account_own && user_access("update own {$type} registration", $account) || user_access("update any {$type} registration", $account);
    case 'create':
      return user_access("create {$type} registration", $account) || user_access("create own {$type} registration", $account);
    case 'delete':
      return $account_own && user_access("delete own {$type} registration", $account) || user_access("delete any {$type} registration", $account);
  }
}

/**
 * Gets an array of all registration types, keyed by the name.
 *
 * @param $name
 *   If set, the type with the given name is returned.
 */
function registration_get_types($name = NULL) {
  if (isset($name)) {
    if ($name) {
      $types = entity_load_multiple_by_name('registration_type', array(
        $name,
      ));
      return $types ? reset($types) : NULL;
    }
    return NULL;
  }
  return entity_load_multiple_by_name('registration_type');
}

/**
 * Gets an array of all registration types, keyed by the name.
 *
 * @return array
 *   A list of all registration types.
 */
function registration_type_get_names($name = NULL) {
  $types = registration_get_types();
  $data = array();
  foreach (array_keys($types) as $name) {
    $data[$name] = $name;
  }
  return $data;
}

/**
 * Menu argument loader; Load a registration type by string.
 *
 * @param $type
 *   The machine-readable name of a registration type to load.
 *
 * @return
 *   A registration type array or FALSE if $type does not exist.
 */
function registration_type_load($type) {
  return registration_get_types($type);
}

/**
 * Access callback for the entity API.
 */
function registration_type_access($op, $entity = NULL, $account = NULL, $entity_type = NULL) {
  return user_access('administer registration types', $account);
}

/**
 * Saves a model type to the db.
 */
function registration_type_save(RegistrationType $type) {
  $type
    ->save();
}

/**
 * Deletes a model type from the db.
 */
function registration_type_delete(RegistrationType $type) {
  $type
    ->delete();
}

/**
 * Access callback for the entity API.
 */
function registration_state_access($op, $entity = NULL, $account = NULL, $entity_type = NULL) {
  return user_access('administer registration states', $account);
}

/**
 * Implements hook_i18n_string_info().
 */
function registration_i18n_string_info() {
  $groups = array();
  $groups['registration'] = array(
    'title' => t('Entity registration'),
    'description' => t('Translatable registration settings.'),
    'format' => FALSE,
    'list' => TRUE,
  );
  return $groups;
}

/**
 * Translates a config string to the current language or to a given language.
 *
 * @param $name string
 *   A key *without* a textgroup (this module's textgroup will be prefixed
 *   automatically).
 * @param $string string
 *   A user-entered configuration string in the default language. The default
 *   language may or may not be English.
 * @param $langcode string
 *   The language code. Defaults to NULL.
 *
 * @return string
 *   The translated string, if i18n_string() is available. Otherwise, returns
 *   $string.
 *
 * @see https://drupal.org/node/1114010
 * @see i18n_string()
 */
function _registration_translate($name, $string, $langcode = NULL) {
  return function_exists('i18n_string') ? i18n_string('registration:' . $name, $string, array(
    'langcode' => $langcode,
  )) : $string;
}

/**
 * Registers a config string so it can later be translated.
 *
 * @param $name string
 *   A key *without* a textgroup (this module's textgroup will be prefixed
 *   automatically).
 * @param $string string
 *   A user-entered configuration string in the default language. The default
 *   language may or may not be English.
 * @param $langcode string
 *   The language code. Defaults to NULL.
 *
 * @return string
 *   The translated string, if i18n_string() is available. Otherwise, returns
 *   $string.
 *
 * @see https://drupal.org/node/1114010
 * @see i18n_string()
 */
function _registration_translate_update($name, $string, $langcode = NULL) {
  return function_exists('i18n_string') ? i18n_string('registration:' . $name, $string, array(
    'update' => TRUE,
    'langcode' => $langcode,
  )) : FALSE;
}

/**
 * Process registration settings to provide usable values.
 *
 * Initial purpose is to interpret dates that are using tokens rather than raw
 * dates.
 */
function registration_interpret_settings(&$settings, $entity_type, $entity) {
  $fields = array(
    'open',
    'close',
  );
  foreach ($fields as $field) {
    if (!empty($settings[$field . '_date_token'])) {
      $settings[$field] = token_replace($settings[$field . '_date_token'], array(
        $entity_type => $entity,
      ), array(
        'clear' => TRUE,
      ));
    }
  }
}

Functions

Namesort descending Description
registration_access Access callback: Entity API for Registration entities.
registration_access_check_redirect Helper function to run a hashed URL through a permissions check first.
registration_administer_registrations_access Access callback: for registration_registrations_page().
registration_admin_paths Implements hook_admin_paths().
registration_anonymous_access_hash Hash function used for enabling anonymous access to registrations.
registration_anonymous_link_get Get a full link to view an anonymous registration, with access hash.
registration_cron Implements hook_cron().
registration_delete_multiple Deletes multiple registrations by ID.
registration_entity_delete Implements hook_entity_delete().
registration_entity_info Implements hook_entity_info().
registration_entity_info_alter Implements hook_entity_info_alter().
registration_entity_insert Implements hook_entity_insert().
registration_entity_settings Get registration settings for a host entity.
registration_entity_settings_page Page callback for entity registration settings.
registration_entity_set_default_settings Sets the the registration entity settings to the deafults.
registration_entity_update Implements hook_entity_update().
registration_event_count Determines current number of spaces filled for a host entity.
registration_field_extra_fields Implements hook_field_extra_fields().
registration_get_active_states Return an array of all active state machine names.
registration_get_default_state Return default state
registration_get_entity_registration_type Get the registration type bundle for a host entity.
registration_get_held_states Return an array of all held state machine names.
registration_get_registrant Get the registrant entity for a registration, or NULL if no registrant is associated.
registration_get_registrations Load registrations for a host entity, optionally filtered to a particular registrant email.
registration_get_registration_instances Return all registration field instances.
registration_get_states Gets an array of all registration states, keyed by the name.
registration_get_states_options Get an array of states structured as options for a form select elements
registration_get_types Gets an array of all registration types, keyed by the name.
registration_has_room Determines if a host entity has spaces remaining.
registration_i18n_string_info Implements hook_i18n_string_info().
registration_interpret_settings Process registration settings to provide usable values.
registration_is_registered Determine if a person has an active registration for a host entity.
registration_load Loads a registration by ID.
registration_load_multiple Loads multiple registrations by ID or based on a set of matching conditions.
registration_mail Implements hook_mail().
registration_menu Implements hook_menu().
registration_own_access Define our own view/update/delete access.
registration_page_title Title callback: Generate a title for a registration entity.
registration_permission Implements hook_permission().
registration_permission_list Builds permissions for a registration type.
registration_property_author_get Callback to get $registration->author.
registration_property_host_get Callback to get $registration->host.
registration_property_host_set Callback to set $registration->host.
registration_property_registrant_get Callback to get $registration->registrant.
registration_property_registrant_mail_get Callback to get $registration->registrant->mail.
registration_property_registrant_set Callback to set $registration->registrant that supports null value.
registration_register_page Page callback: Add a new registration to a host entity.
registration_register_page_access Access callback: for registration_register_page().
registration_registrant_create Create a new registrant object for a given registration type. Optionally set registrant email.
registration_registrant_load_by_mail Fetch a registrant entity for a given email address, or NULL if no entity found.
registration_registrations_page Page callback: Show a list of registrations for a host entity.
registration_save Saves a registration.
registration_send_broadcast Send an email to all registrations for a host entity.
registration_states Return all registration state entities.
registration_state_access Access callback for the entity API.
registration_status Check if new registrations are permitted for a host entity.
registration_theme Implements hook_theme().
registration_tokens Implements hook_tokens().
registration_token_info Implement hook_token_info().
registration_type_access Access callback for the entity API.
registration_type_delete Deletes a model type from the db.
registration_type_get_names Gets an array of all registration types, keyed by the name.
registration_type_load Menu argument loader; Load a registration type by string.
registration_type_save Saves a model type to the db.
registration_update_entity_settings Update or create registration settings for a host entity.
registration_user_cancel Implements hook_user_cancel().
registration_view Display a registration.
theme_registration_link Theme handler for registration links.
theme_registration_property_field Theme function for registration properties.
theme_registration_registrant_link Theme handler for registrant links.
_registration_translate Translates a config string to the current language or to a given language.
_registration_translate_update Registers a config string so it can later be translated.

Constants

Namesort descending Description
REGISTRATION_INSUFFICIENT_SPACES_MESSAGE
REGISTRATION_REGISTRANT_TYPE_OTHER If user has access to create registrations for any email address.
REGISTRATION_REGISTRANT_TYPE_SELF If user has access to create registrations for only their own email address.
REGISTRATION_STATE_ONE