You are here

party.module in Party 7

Same filename and directory in other branches
  1. 8.2 party.module

Provides a generic CRM party entity.

File

party.module
View source
<?php

/**
 * @file
 * Provides a generic CRM party entity.
 */

/**
 * Implements hook_help().
 */
function party_help($path, $arg) {
  switch ($path) {
    case 'admin/help#party':
      return t("TODO: Create admin help text.");
    case 'admin/config/party/labels':
      return t("Party name label generators save a label whenever a party is created or updated. This lets you form the party's label or name in a variety of methods.");
    case 'admin/config/party/piece-order':
      return t("The tabs that display below each party can be dragged into the desired order below.");
    case 'admin/config/party/primary-fields':
      return t('Primary fields allow you to store key information quickly and accessibly for use in bulk and automated operations. For each type, select where you want to pull this information from.');
  }
}

/**
 * Implements hook_entity_info().
 *
 * Declare our party entity to the entity system.
 */
function party_entity_info() {
  $party_info['party'] = array(
    'label' => t('Party'),
    'plural label' => t('Parties'),
    'entity class' => 'Party',
    'controller class' => 'PartyController',
    'merge handler class' => 'PartyMergeHandler',
    // We define this so entity module provides us with basic Views data, while
    // allowing us to define more of our own.
    // See http://drupal.org/node/1307760.
    'views controller class' => 'EntityDefaultViewsController',
    'base table' => 'party',
    //'uri callback' => 'party_uri',
    'fieldable' => TRUE,
    'module' => 'party',
    'entity keys' => array(
      'id' => 'pid',
      'label' => 'label',
    ),
    'access callback' => 'party_entity_access',
    'uri callback' => 'party_uri',
    // Entity API admin UI.
    'admin ui' => array(
      'controller class' => 'PartyUIController',
      'path' => 'admin/community',
      // Urgh. Our admin UI is split between admin.inc and pages.inc!
      // @todo untangle and fix.
      'file' => 'party.pages.inc',
    ),
    //'static cache' => TRUE,
    'bundles' => array(
      'party' => array(
        'label' => t('Party'),
        'admin' => array(
          'path' => 'admin/community/party',
          'access arguments' => array(
            'administer parties',
          ),
        ),
      ),
    ),
    'view modes' => array(
      'full' => array(
        'label' => t('Full Contact'),
        'custom settings' => FALSE,
      ),
    ),
    // We don't want redirects on party.
    'redirect' => FALSE,
  );
  return $party_info;
}

/**
 * Implements hook_entity_info_alter().
 *
 * Make standard alterations to entities that declare they work with Parties
 * via hook_party_data_set_info().
 *
 * @see PartyDefaultDataSet::hook_entity_info_alter()
 */
function party_entity_info_alter(&$entity_info) {
  $sets = party_get_data_set_info();
  foreach ($sets as $set) {
    $entity_info[$set['entity type']]['crm controller class'] = $set['class'];

    // Pass this onto the class to make changes.
    call_user_func_array(array(
      $set['class'],
      'hook_entity_info_alter',
    ), array(
      &$entity_info[$set['entity type']],
    ));
  }
}

/**
 * Implements hook_menu().
 */
function party_menu() {

  // CRM settings.
  // Making up a new config category.
  // Alternative is to put this all under top-level admin.
  // But all this structure is rough anyway.
  $items['admin/config/party'] = array(
    'title' => 'Party',
    'description' => 'Party Settings',
    'position' => 'left',
    'weight' => -10,
    'page callback' => 'system_admin_menu_block_page',
    'access arguments' => array(
      'access administration pages',
    ),
    'file' => 'system.admin.inc',
    'file path' => drupal_get_path('module', 'system'),
  );
  $items['admin/config/party/labels/%'] = array(
    'title' => 'Configure Plugin',
    'description' => 'Configure the Chosen Label Plugin',
    'page callback' => 'party_settings_label_plugin_config_page',
    'page arguments' => array(
      4,
    ),
    'access arguments' => array(
      'administer crm settings',
    ),
    'file' => 'party.admin.inc',
  );
  $items['admin/config/party/piece-order'] = array(
    'title' => 'Piece order',
    'description' => 'Rearrange the order of pieces within a party.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'party_settings_pieces_order_form',
    ),
    'access arguments' => array(
      'administer crm settings',
    ),
    'file' => 'party.admin.inc',
  );
  $items['admin/community/datasets'] = array(
    'title' => 'Manage data sets',
    'page callback' => 'party_data_set_admin',
    'access arguments' => array(
      'administer crm settings',
    ),
    'file' => 'party.admin.inc',
    'type' => MENU_LOCAL_TASK,
  );
  $items['admin/community/party'] = array(
    'title' => 'Manage parties',
    'description' => 'Manage contact information',
    // A copy of the 'Manage fields' menu item from Field UI, as that is our
    // default tab.
    'access callback' => 'user_access',
    'access arguments' => array(
      'administer crm settings',
    ),
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'field_ui_field_overview_form',
      'party',
      'party',
    ),
    'type' => MENU_LOCAL_TASK,
    'file' => 'field_ui.admin.inc',
    'file path' => drupal_get_path('module', 'field_ui'),
  );
  $items['admin/community/party/primary-fields/%'] = array(
    'title' => 'Manage primary fields',
    'title callback' => 'party_primary_fields_title',
    'title arguments' => array(
      4,
    ),
    'access callback' => 'user_access',
    'access arguments' => array(
      'administer crm settings',
    ),
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'party_primary_fields_edit_field',
      4,
    ),
    'type' => MENU_CALLBACK,
    'file' => 'party.admin.inc',
  );
  $items['party/%party'] = array(
    'title callback' => 'party_page_title',
    'title arguments' => array(
      1,
    ),
    'page callback' => 'party_page_view',
    'page arguments' => array(
      1,
    ),
    'file' => 'party.pages.inc',
    'access callback' => 'party_access',
    'access arguments' => array(
      'view',
      1,
    ),
    'type' => MENU_NORMAL_ITEM,
  );

  // Build the party view first-level tabs.
  // @todo: Write a contrib module to turn a set of tabs into ajax tabs,
  // probably using or as part of quicktabs module.
  $pieces = party_get_party_piece_info();

  // These come sorted by weight so we know which to make the default tab.
  foreach ($pieces as $path => $piece) {
    if (!isset($seen_first_piece)) {
      $seen_first_piece = TRUE;
      $piece['type'] = MENU_DEFAULT_LOCAL_TASK;
    }

    // Add in some defaults for convenience.
    // @todo: once these stabilize, document them in the API file.
    $piece += array(
      'type' => MENU_LOCAL_TASK,
      'page arguments' => array(
        1,
        2,
      ),
      // Make this explicit so it overrides the parent.
      'access callback' => 'party_access',
      'access arguments' => array(
        'view',
        1,
        2,
      ),
    );
    $items["party/%party/{$path}"] = $piece;
  }
  $items['party/%party/party'] = array(
    'title' => 'Overview',
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'access callback' => 'party_access',
    'access arguments' => array(
      'view',
      1,
    ),
    'page callback' => 'party_page_view',
    'page arguments' => array(
      1,
    ),
    'file' => 'party.pages.inc',
    'weight' => -20,
  );

  // This is temporary until I figure out a neat way to do tabs below pieces.
  $items['party/%party/party/view'] = array(
    'title' => 'View',
    'page callback' => 'party_page_view',
    'page arguments' => array(
      1,
    ),
    'file' => 'party.pages.inc',
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -10,
  );
  $items['party/%party/party/edit'] = array(
    'title' => 'Edit',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'party_form',
      1,
    ),
    'access callback' => 'party_access',
    'access arguments' => array(
      'edit',
      1,
    ),
    'file' => 'party.pages.inc',
    'type' => MENU_LOCAL_TASK,
    'weight' => -5,
  );

  // End temporary.
  $items['party/%party/party/archive'] = array(
    'title' => 'Archive',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'party_archive_form',
      1,
    ),
    'access callback' => 'party_access',
    'access arguments' => array(
      'archive',
      1,
    ),
    'file' => 'party.pages.inc',
    'type' => MENU_LOCAL_TASK,
    'weight' => 9,
  );
  $items['party/%party/party/delete'] = array(
    'title' => 'Delete',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'party_delete_form',
      1,
    ),
    'access callback' => 'party_access',
    'access arguments' => array(
      'delete',
      1,
    ),
    'file' => 'party.pages.inc',
    'type' => MENU_LOCAL_TASK,
    'weight' => 10,
  );

  // Attached entity menu items
  // Edit an attached entity
  $items['party/%party/%party_data_set/edit/%'] = array(
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'party_attached_entity_edit_form',
      1,
      2,
      4,
    ),
    'access callback' => 'party_access',
    'access arguments' => array(
      'edit',
      1,
      2,
    ),
    'file' => 'party.pages.inc',
  );

  // Detach an attached entity
  $items['party/%party/%party_data_set/remove/%'] = array(
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'party_attached_entity_remove_confirm',
      1,
      2,
      4,
    ),
    'access callback' => 'party_access',
    'access arguments' => array(
      'detach',
      1,
      2,
    ),
    'file' => 'party.pages.inc',
  );

  // Add an attached entity
  $items['party/%party/%party_data_set/add'] = array(
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'party_attached_entity_action_form',
      1,
      2,
      'add',
    ),
    'access callback' => 'party_access',
    'access arguments' => array(
      'add',
      1,
      2,
    ),
    'file' => 'party.pages.inc',
  );

  // Attach an existing entity.
  $items['party/%party/%party_data_set/attach'] = array(
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'party_attached_entity_action_form',
      1,
      2,
      'attach',
    ),
    'access callback' => 'party_access',
    'access arguments' => array(
      'attach',
      1,
      2,
    ),
    'file' => 'party.pages.inc',
  );
  $items['party/add'] = array(
    'title' => 'Add party',
    'page callback' => 'party_add',
    'file' => 'party.pages.inc',
    'access arguments' => array(
      'create parties',
    ),
  );

  // Provide useful information for developers.
  // This may eventually move to a devel_party module if there's more of it.
  if (module_exists('devel')) {
    $items['devel/crm/info'] = array(
      'title' => 'CRM info',
      'description' => 'View CRM data.',
      'page callback' => 'party_devel_info_page',
      'access arguments' => array(
        'access devel information',
      ),
      'file' => 'party.admin.inc',
      'menu_name' => 'devel',
      'weight' => 10,
    );
  }
  return $items;
}

/**
 * Implements hook_menu_alter().
 */
function party_menu_alter(&$items) {

  // Make the 'Manage fields' party UI page a default tab.
  $items['admin/community/party/fields']['type'] = MENU_DEFAULT_LOCAL_TASK;
}

/**
 * Implements hook_menu_local_tasks_alter().
 *
 * Adds local tasks in the following places:
 *  - to add a party from the party list admin page
 *  - to add/import a new data set (ie entity bundle) from the data sets admin page
 *  - to add new entities on a party piece that's based on a data set
 */
function party_menu_local_tasks_alter(&$data, $router_item, $root_path) {

  // Add action link to 'party/add' on 'admin/community/list' page.
  if ($root_path == 'admin/community/list' || $root_path == 'admin/community') {
    $item = menu_get_item('party/add');
    if ($item['access']) {
      $data['actions']['output'][] = array(
        '#theme' => 'menu_local_action',
        '#link' => $item,
      );
    }
  }

  // Add action links to 'admin/community/datasets' page.
  if ($root_path == 'admin/community/datasets') {
    $data_sets = party_get_data_set_info();
    $attached_entity_types = array();
    foreach ($data_sets as $data_set) {
      $attached_entity_types[$data_set['entity type']] = $data_set['admin'];
    }
    foreach ($attached_entity_types as $type => $admin) {
      $type = entity_get_info($type);
      if (isset($admin['create'])) {
        $item = menu_get_item($admin['create']);
        if ($item['access']) {
          $item['title'] = 'Add ' . $type['label'] . ' data set';
          $item['localized_options'] = array(
            'query' => array(
              'destination' => 'admin/community/datasets',
            ),
          );
          $data['actions']['output'][] = array(
            '#theme' => 'menu_local_action',
            '#link' => $item,
          );
        }
      }
      if (isset($admin['import'])) {
        $item = menu_get_item($admin['import']);
        $item['localized_options'] = array(
          'query' => array(
            'destination' => 'admin/community/datasets',
          ),
        );
        $item['title'] = 'Import ' . $type['label'] . ' data set';
        if ($item['access']) {
          $data['actions']['output'][] = array(
            '#theme' => 'menu_local_action',
            '#link' => $item,
          );
        }
      }
    }
  }

  // Add action links for attaching new entities on party pieces that are built
  // from data sets.
  if (substr($root_path, 0, 8) == 'party/%/') {
    if (!isset($router_item['original_map'][3])) {
      $piece_subpath = $router_item['original_map'][2];
      $pieces = party_get_party_piece_info();
      if (!isset($pieces[$piece_subpath])) {
        return;
      }
      $piece = $pieces[$piece_subpath];
      if (isset($piece['data_set'])) {
        $data_set_base_path = implode('/', $router_item['original_map']);
        $data_set = party_get_data_set_info($piece['data_set']);
        foreach ($data_set['actions'] as $action => $action_info) {
          $item = menu_get_item($data_set_base_path . '/' . $action);

          // Menu access checks party_access(), which checks whether actions are
          // allowed by the data set definition.
          if ($item['access']) {
            $data['actions']['output'][] = array(
              '#theme' => 'menu_local_action',
              '#link' => array(
                'title' => t($action_info['action label'], array(
                  '@data-set' => $data_set['label'],
                )),
                'href' => $data_set_base_path . '/' . $action,
              ),
            );
          }
        }
      }
    }
  }
}

/**
 * Menu loader for data sets. First check against pieces, and if nothings found
 * check against the $data_set_names themselves.
 *
 * @param $data_set_url_string
 *  The url-form of a data set name, ie with hyphens instead of underscores.
 *
 * @return
 *  A loaded data set definition.
 */
function party_data_set_load($data_set_url_string) {
  foreach (party_get_data_set_info() as $data_set => $info) {

    // Match the path string we've been given with the one that's defined in
    // data sets and return the data set we find.
    if (isset($info['path element']) && $info['path element'] == $data_set_url_string) {
      return $info;
    }
  }

  // If nothings been found yet, check up the data set names
  return party_get_data_set_info($data_set_url_string);
}

/**
 * Wrap around party_access() for the entity api 'access callback'.
 *
 * @param $op
 *   The operation being performed.
 * @param $party
 *   A party to check access for.
 * @param $account
 *   (optional) The user to check access for. Omit to check for the global user.
 *
 * @return
 *   Boolean; TRUE to grant access, FALSE to deny it.
 *
 * @see party_access
 */
function party_entity_access($op, $party = NULL, $account = NULL) {
  return party_access($op, $party, NULL, $account);
}

/**
 * Determines whether operations are allowed on a Party and attached entities.
 *
 * @param $op
 *   The operation being performed.
 *   Currently one of:
 *    - 'view': Whether the user may view the given party or data set.
 *    - 'edit': Whether the user may edit the given party or data set.
 *    - 'add': Whether the user may create a new entity the given data set to
 *      the party.
 *    - 'attach': Whether the user may attach an existing entity in the given
 *      data set to the party.
 *    - 'detach': Whether the user may detach the given data set from the
 *      party.
 *    - 'create': Whether the user may create a party of the type given by
 *      $party->type.
 *   @todo: consider distinguishing $op values for solo party vs attached
 *   entity, eg 'view attached' / 'view', so that we don't have to keep mucking
 *   about testing isset($attached_entity).
 * @param $party
 *   A party to check access for.
 * @param $data_set
 *   (optional) A dataset name or full definition to check access for. If
 *   nothing is given, access for just the party itself is determined.
 * @param $account
 *   (optional) The user to check for. Omit to check for the global user.
 *
 * @return boolean
 *   Whether access is allowed or not.
 *
 * @see hook_party_access()
 * @see party_party_access()
 */
function party_access($op, $party = NULL, $data_set = NULL, $account = NULL) {

  // Let the admin through when there's no attached entity being considered.
  if (!isset($data_set) && user_access('administer parties', $account)) {
    return TRUE;
  }

  // If the party has been archived, check we have access.
  if (is_object($party) && $party->archived && !user_access('view archived parties', $account)) {
    return FALSE;
  }

  // If we've been passed a data set name we change it into an array.
  if (isset($data_set) && is_string($data_set)) {
    $data_set = party_get_data_set_info($data_set);
  }
  if (!isset($account)) {
    $account = $GLOBALS['user'];
  }

  // If $party is a string, convert it to an entity.
  if (is_string($party)) {
    $party = party_create();
  }

  // Allow modules to grant / deny access.
  // Keep track of which modules grant / deny access. This allows for easier
  // debugging and may be helpful in the future.
  foreach (module_implements('party_access') as $module) {
    $access[$module] = module_invoke($module, 'party_access', $op, $party, $data_set, $account);
  }

  // Only grant access if at least one module granted access and no one denied
  // access.
  if (in_array(FALSE, $access, TRUE)) {
    return FALSE;
  }
  elseif (in_array(TRUE, $access, TRUE)) {
    return TRUE;
  }
  return FALSE;
}

/**
 * Implements hook_admin_paths().
 */
function party_admin_paths() {
  $paths = array(
    'party/*/*/edit/*' => TRUE,
    'party/*/*/remove/*' => TRUE,
    'party/*/*/add' => TRUE,
  );
  return $paths;
}

/**
 * Page title callback for party view page.
 *
 * Has to be in the main module file because it's used by several menu items.
 */
function party_page_title($party) {
  return $party->label;
}

/**
 * Implements hook_hook_info().
 */
function party_hook_info() {

  // General hooks that are called frequently on operations or page builds.
  $hooks['party_access'] = array(
    'group' => 'party',
  );
  $hooks['party_data_set_attach'] = array(
    'group' => 'party',
  );
  $hooks['party_data_set_detach'] = array(
    'group' => 'party',
  );
  $hooks['party_data_set_save'] = array(
    'group' => 'party',
  );
  $hooks['party_operations'] = array(
    'group' => 'party',
  );
  $hooks['party_page_view_alter'] = array(
    'group' => 'party',
  );
  $hooks['party_primary_fields_alter'] = array(
    'group' => 'party',
  );

  // Info based hooks. These should all be cached and called infrequently.
  $hooks['party_data_set_info'] = array(
    'group' => 'party_info',
  );
  $hooks['party_data_set_info_alter'] = array(
    'group' => 'party_info',
  );
  $hooks['party_party_piece_info'] = array(
    'group' => 'party_info',
  );
  $hooks['party_party_pieces'] = array(
    'group' => 'party_info',
  );
  $hooks['party_admin_columns_info'] = array(
    'group' => 'party_info',
  );
  $hooks['party_primary_fields_fields_alter'] = array(
    'group' => 'party_info',
  );
  $hooks['party_primary_fields_sources_alter'] = array(
    'group' => 'party_info',
  );

  // Acquisition hooks.
  $hooks['party_acquisition_values_alter'] = array(
    'group' => 'party_acquisition',
  );
  $hooks['party_acquisition_query_alter'] = array(
    'group' => 'party_acquisition',
  );
  $hooks['party_acquisition_post_acquisition'] = array(
    'group' => 'party_acquisition',
  );
  return $hooks;
}

/**
 * Implements hook_modules_enabled()
 */
function party_modules_enabled($modules) {

  // Clear our caches in case things have changed.
  cache_clear_all('party:', 'cache', TRUE);
}

/**
 * Implements hook_modules_enabled()
 */
function party_modules_disabled($modules) {

  // Clear our caches in case things have changed.
  cache_clear_all('party:', 'cache', TRUE);
}

// -----------------------------------------------------------------------
// Party pieces: defining extra hook_menu() items according to data structure.

/**
 * Get all party piece definitions from hook_party_party_pieces().
 *
 * This is only used by hook_menu() and an admin page so no need to cache.
 *
 * @return
 *  An array of party pieces, keyed by path and sorted by weight.
 *  Keys in addition to those defined in hook_party_party_pieces():
 *  - data_set: The id of a data set if this piece is defined from one.
 */
function party_get_party_piece_info() {
  $pieces = module_invoke_all('party_party_pieces');

  // Add in weights from the admin setting.
  $weights = variable_get('party_name_pieces_weights', array());
  foreach ($pieces as $path => $piece) {

    // The weight setting overrides weights defined in the hook.
    if (isset($weights[$path])) {
      $pieces[$path]['weight'] = $weights[$path];
    }
  }

  // Sort pieces array by weight for hook_menu() to figure out the default tab
  // and the admin UI to show them in the right order.
  // drupal_sort_weight() treats a missing weight key as a 0.
  uasort($pieces, 'drupal_sort_weight');
  return $pieces;
}

// -----------------------------------------------------------------------
// API for getting and changing data sets.

/**
 * Get the data set definition associated with a given entity and bundle.
 *
 * @param $entity_type
 *  The entity type e.g profile2 etc.
 * @param $entity
 *  The entity object or id.
 *
 * @return
 *  A data set name if one exists, NULL otherwise.
 */
function party_get_entity_data_set($entity_type, $entity) {
  $data_set_info = party_get_data_set_info();

  // Get bundle.
  $info = entity_get_info($entity_type);
  $bundle_key = empty($info['entity keys']['bundle']) ? FALSE : $info['entity keys']['bundle'];
  if (!$bundle_key) {
    $bundle = $entity_type;
  }
  else {
    $bundle = $entity->{$bundle_key};
  }
  foreach ($data_set_info as $data_set_name => $data_set) {
    if ($data_set['entity type'] == $entity_type && (!isset($data_set['entity bundle']) || $data_set['entity bundle'] == $bundle)) {
      return $data_set_name;
    }
  }
  return NULL;
}

/**
 * Get all data sets from hook_party_data_set_info().
 *
 * @todo: cache this, either in the DB or using DrupalCacheArray.
 *
 * @param $data_set_name
 *  (optional) The name of the data set to return. If
 *  omitted, all are returned.
 * @param $reset
 *  (optional) If set to true this will rebuild the data
 *  info, ignoring any cached version.
 *
 * @return
 *  Either a single data set, or an array of data sets keyed by set name.
 *  Each set is an array whose keys are as returned by
 *  hook_party_data_set_info(), with in addition:
 *  - 'module': The module defining this set.
 *  - 'table': The table in which this set's data is stored.
 */
function party_get_data_set_info($data_set_name = NULL, $reset = FALSE) {
  $sets =& drupal_static(__FUNCTION__);
  if (!isset($sets) || $reset) {
    if (($cache = cache_get('party:data_set_info', 'cache')) && !$reset) {
      $sets = $cache->data;
    }
    else {
      $sets = array();
      foreach (module_implements('party_data_set_info') as $module) {

        // Due to http://drupal.org/node/890660 we can't use module_invoke_all()
        // because we need to know the provenance of each set.
        $sets_module = module_invoke($module, 'party_data_set_info');
        foreach ($sets_module as $set_name => $set) {

          // Add in some essential data we need, but allow modules to set this too.
          if (isset($set['piece'])) {
            $path_element = $set['piece']['path'];
          }
          else {

            // @todo: fix this, as it's pretty weak and certainly in the case of
            // the user data set, relies on the piece happening to define its
            // path the same way.
            $path_element = str_replace('_', '-', $set_name);
          }
          $set += array(
            'set_name' => $set_name,
            'module' => $module,
            'singleton' => FALSE,
            'view mode' => 'party',
            'form callback' => 'party_default_attached_entity_form',
            // Singleton entities have no bundle; use the entity type.
            'entity bundle' => $set['entity type'],
            'path element' => $path_element,
          );

          // Translate 'singleton' to max cardinality of 1.
          // TODO: remove the singleton property? remove the cardinality
          // property? Which one actually gets used?
          if ($set['singleton']) {
            $set['max cardinality'] = 1;
          }
          $sets[$set_name] = $set;
        }
      }

      // Alter the data sets with hook_party_data_set_info_alter().
      drupal_alter('party_data_set_info', $sets);
      cache_set('party:data_set_info', $sets);
    }
  }
  if (isset($data_set_name)) {

    // @todo: Throw a big error if the $data_set_name doesn't exist
    return isset($sets[$data_set_name]) ? $sets[$data_set_name] : FALSE;
  }
  else {
    return $sets;
  }
}

/**
 * Get the data sets expected for a party.
 *
 * @todo: rename this function; the name is ambiguous.
 *
 * @param $party
 *  The party object in question.
 *
 * @return
 *  An array of data set names.
 *
 * @todo: Consider caching against the set of hats rather than the party id.
 */
function party_get_party_data_sets($party) {
  $party_data_sets =& drupal_static(__FUNCTION__);

  // If the sets have already been worked out return the cached list.
  if (isset($party->pid) && !empty($party_data_sets[$party->pid])) {
    return $party_data_sets[$party->pid];
  }
  $data_sets = array_keys(party_get_data_set_info());

  // Allow modules to alter the party data sets.
  drupal_alter('party_data_sets', $data_sets, $party);

  // If a pid is set cache the result.
  if (isset($party->pid)) {
    $party_data_sets[$party->pid] = $data_sets;
  }
  return $data_sets;
}

// -----------------------------------------------------------------------
// API for relating and unrelating entities to a party.

/**
 * Attach an entity to a party according to a given data set.
 *
 * @param $party
 *  The party to assign the entity to.
 * @param $entity
 *  The entity to relate to.
 * @param $data_set
 *  The name of the data set.
 *  DX WTF: can we sniff this out given the entity type and the entity object?
 *  DX WTF: Lots of sniffing now done BUT do we want the $data_set instance made elsewhere. If so where?
 */
function party_attach_entity($party, $entity, $data_set_name) {
  $party
    ->getDataSetController($data_set_name)
    ->attachEntity($entity)
    ->save();
}

/**
 * Detach an entity from a party according to a given data set.
 *
 * @param $party
 *  The party to detach the entity from.
 * @param $entity
 *  The entity to detach. This may also be just the entity id.
 * @param $data_set
 *  The name of the data set.
 *  DX WTF: can we sniff this out given the entity type and the entity object?
 */
function party_detach_entity($party, $entity, $data_set_name) {

  /* To Test */
  $party
    ->getDataSetController($data_set_name)
    ->detachEntity($entity)
    ->save();
}

// -----------------------------------------------------------------------
// API for loading data about a party.

/**
 * Get the form for a data set (more to the point get a set of fields for the data set
 * might need to work on this). This function checks the edit party attached data set
 * permission.
 *
 * @param $party
 *  The party object of the party we're concerned with.
 * @param $data_set_name
 *  The data set name as defined in hook_data_set_info().
 * @param $set_id
 *  The set id as stored in the related table
 *
 * @return
 *   A form array.
 *
 * @todo: On adding a party work well.
 */
function party_data_set_form($party, $data_set_name, $set_id = FALSE) {

  // Check that the user has access to this form
  if (!party_access('edit', $party, $data_set_name)) {
    return FALSE;
  }
  $set = party_get_data_set_info($data_set_name);
  $function = $set['form callback'];
  if (function_exists($function)) {
    $result = call_user_func($function, $party, $set_type, $set_id);
  }
  else {
    return FALSE;
  }
  return $result;
}

/**
 * Implements hook_permission().
 */
function party_permission() {
  $permissions = array(
    'administer crm settings' => array(
      'title' => t('Administer party settings'),
      'restrict access' => TRUE,
    ),
    'administer parties' => array(
      'title' => t('Administer parties'),
      'restrict access' => TRUE,
    ),
    'view parties' => array(
      'title' => t('View parties'),
    ),
    'view archived parties' => array(
      'title' => t('View archived parties'),
      'restrict access' => TRUE,
    ),
    'create parties' => array(
      'title' => t('Create parties'),
    ),
    'edit parties' => array(
      'title' => t('Edit parties'),
    ),
    'archive parties' => array(
      'title' => t('Archive parties'),
    ),
    'delete parties' => array(
      'title' => t('Delete parties'),
    ),
  );

  // Add permissions for each data set.
  foreach (party_get_data_set_info() as $data_set_name => $data_set) {

    // Build an array of default overrides.
    $overrides = array_fill_keys(array(
      'view',
      'attach',
      'edit',
      'detach',
    ), array());

    // Get the overrides from the data set if applicable.
    if (isset($data_set['permissions'])) {
      $overrides = array_merge_recursive($overrides, $data_set['permissions']);
    }
    $permissions['view party attached ' . $data_set_name] = $overrides['view'] + array(
      'title' => t('View party attached %name', array(
        '%name' => $data_set['label'],
      )),
    );
    $permissions['attach party ' . $data_set_name] = $overrides['attach'] + array(
      'title' => t('Add party attached %name', array(
        '%name' => $data_set['label'],
      )),
    );
    $permissions['edit party attached ' . $data_set_name] = $overrides['edit'] + array(
      'title' => t('Edit party attached %name', array(
        '%name' => $data_set['label'],
      )),
    );
    $permissions['detach party attached ' . $data_set_name] = $overrides['detach'] + array(
      'title' => t('Remove party attached %name', array(
        '%name' => $data_set['label'],
      )),
    );
  }
  return $permissions;
}

/**
 * URI callback for contacts.
 */
function party_uri($party) {
  return array(
    'path' => 'party/' . $party->pid,
  );
}

/**
 * Implements hook_theme().
 */
function party_theme() {
  return array(
    'party_settings_pieces_order_form' => array(
      'render element' => 'form',
    ),
    'party_settings_label_plugins_form' => array(
      'render element' => 'form',
    ),
    'entity__all__party' => array(
      'render element' => 'elements',
      'template' => 'entity--all--party',
      'path' => drupal_get_path('module', 'party') . '/theme',
    ),
  );
}

/**
 * Implements hook_preprocess_HOOK().
 *
 * Add our own template for party display.
 */
function party_preprocess_entity(&$variables) {
  if ($variables['view_mode'] == 'party') {
    $entity_type = $variables['entity_type'];
    $entity = $variables['elements']['#entity'];
    list($eid, ) = entity_extract_ids($entity_type, $entity);
    $party_id = $entity->party_attaching_party;

    // @todo: clean this up -- having to load up the controller all over
    // again when we probably had it at some earlier point is just messy.
    $data_set_name = party_get_entity_data_set($entity_type, $entity);
    $data_set_controller = party_load($party_id)
      ->getDataSetController($data_set_name);

    // Load the entities.
    $data_set_controller
      ->getEntities();

    //$attached_entity->setAttachedEntity($eid);

    // Add the attached entity actions to the build content.
    // @todo: change these to contextual links?
    // Links broken: http://drupal.org/node/1600816
    $variables['content']['party_actions'] = array(
      '#theme' => 'links',
      '#links' => $data_set_controller
        ->getActions($party_id, $eid),
      '#attributes' => array(
        'class' => array(
          'links inline crm-set-action-links',
        ),
      ),
      '#weight' => 100,
    );

    // @todo This would be better off as a new feature in Entity API
    $variables['theme_hook_suggestions'][] = 'entity__all__party';

    // @todo Hmmmm how do we let hook_theme() know about this?
    $variables['theme_hook_suggestions'][] = 'entity__' . $entity_type . '__all__party';
  }
}

/**
 * Load a party entity from the database.
 *
 * @param $pid
 *  The party ID.
 * @param $reset
 *  Whether to reset the party_load_multiple cache.
 *
 * @return
 *   A party object.
 */
function party_load($pid = NULL, $reset = FALSE) {
  $pids = !empty($pid) ? array(
    $pid,
  ) : array();
  $party = party_load_multiple($pids, $reset);
  return $party ? reset($party) : FALSE;
}

/**
 * Load party entities from the database.
 *
 * @param $pids
 *  An array of party IDs.
 * @param $conditions
 *   (deprecated) An associative array of conditions on the {party}
 *   table, where the keys are the database fields and the values are the
 *   values those fields must have. Instead, it is preferable to use
 *   EntityFieldQuery to retrieve a list of entity IDs loadable by
 *   this function.
 * @param $reset
 *   Whether to reset the cache.
 *
 * @return
 *   An array of party objects indexed by nid.
 */
function party_load_multiple($pids = array(), $conditions = array(), $reset = FALSE) {
  return entity_load('party', $pids, $conditions, $reset);
}

/**
 * Delete multiple parties.
 *
 * @param $pids
 *  An array of party IDs.
 *
 * @return
 *  The return value from entity_delete_multiple().
 */
function party_delete_multiple($pids = array()) {
  return entity_delete_multiple('party', $pids);
}

/**
 * Delete a party.
 *
 * @param $party
 *  A party object.
 */
function party_delete(Party $party) {
  $party
    ->delete();
}

/**
 * Save a party.
 *
 * @param $party
 *  A party object.
 */
function party_save(&$party) {
  return entity_get_controller('party')
    ->save($party);
}

/**
 * Create a party object ready for saving to the database.
 *
 * @param $info
 *  An array carrying all the important information.
 *
 * @return
 *  A party object that can be passed to party_save(), or FALSE if the array
 *  was not suitable for creating a party.
 */
function party_create($info = array()) {
  return entity_create('party', $info);
}

/**
 * Merge multiple parties.
 *
 * @todo: move this to an inc file; add file inclusion to hook_party_operations().
 */
function party_party_operations_merge($parties = array()) {
  if (count($parties) < 2) {
    drupal_set_message(t("You muset select two or more parties to merge"));
    return FALSE;
  }

  // the first party out of the array.
  $first = array_shift($parties);
  foreach ($parties as $party) {
    party_merge($first, $party);
  }

  // @todo: write me!
}

// -----------------------------------------------------------------------
// Views hook implementations.

/**
 * Implements hook_views_api().
 */
function party_views_api() {
  return array(
    'api' => '3.0-alpha1',
    'path' => drupal_get_path('module', 'party') . '/includes/views',
  );
}

// -----------------------------------------------------------------------
// CTools hook implementations.

/**
 * Implements hook_ctools_plugin_directory().
 */
function party_ctools_plugin_directory($owner, $plugin_type) {
  if ($owner == 'page_manager' && $plugin_type == 'tasks' || $owner == 'ctools' && $plugin_type == 'relationships' || $owner == 'ctools' && $plugin_type == 'content_types' || $owner == 'panelizer' && $plugin_type == 'entity') {
    return "plugins/{$plugin_type}";
  }
}

// --------------------------------------------------------------------------
// Party Attached Entity Form API

/**
 * Get the form callback for attached entities.
 *
 * @param $data_set_name
 *  The name of a data set.
 */
function party_attached_entity_form_callback($data_set_name) {
  $data_set_info = party_get_data_set_info($data_set_name);
  return $data_set_info['form callback'];
}

/**
 * Attach attached entity forms to a form.
 *
 * This takes all attached entities in the $form_state['#attached_entity'] array, runs
 * their form callback and puts it in $form[$attached_entity->hash()]
 *
 * @param $standalone
 *  Whether this is being used as a form to edit just the one attached entity.
 *  If true, the fieldset is not shown around the entity form.
 */
function party_data_set_attach_form(&$form, &$form_state, $data_set, $deltas = FALSE, $create = TRUE) {

  // Get the form callback.
  $callback = party_attached_entity_form_callback($data_set
    ->getDataInfo('name'));

  // Create an array of the $deltas we are working with:
  // If no deltas are specified assume we're using all the deltas.
  if (empty($deltas) && count($data_set
    ->getEntityIds()) > 0) {
    $deltas = array_keys($data_set
      ->getEntityIds());
  }
  elseif (empty($deltas)) {
    $deltas = array(
      0,
    );
  }
  foreach ($deltas as $delta) {
    $entity = $data_set
      ->getEntity($delta, $create);

    // It is possible $entity is false as if $create was false and $delta was
    // unset there would be no entity.
    if (!$entity) {
      continue;
    }
    $form_key = $data_set
      ->getDataInfo('name') . ':' . $delta;
    $form[$form_key] = array(
      '#party' => $data_set
        ->getParty(),
      '#data_set_name' => $data_set
        ->getDataInfo('name'),
      '#data_set' => $data_set,
      '#delta' => $delta,
      '#entity' => $entity,
      '#parents' => array(
        $form_key,
      ),
      '#tree' => TRUE,
      '#type' => 'fieldset',
      '#title' => check_plain(entity_label($data_set
        ->getDataInfo('entity type'), $entity)),
    );
    $callback($form[$form_key], $form_state, $data_set, $delta, $data_set
      ->getParty());
  }

  // Save the data set in the array.
  $form['#data_set_controllers'][$data_set
    ->getDataInfo('name')] = $data_set;
  $form['#submit'][] = 'party_data_set_attach_form_submit';
  $form['#validate'][] = 'party_data_set_attach_form_validate';
}

/**
 * Validate attached entity forms.
 */
function party_data_set_attach_form_validate(&$form, &$form_state) {
  return TRUE;
}

/**
 * Submit attached entity forms
 */
function party_data_set_attach_form_submit(&$form, &$form_state) {

  // Collect which controllers we need to save.
  $controllers = array();
  foreach (element_children($form) as $form_key) {
    $element =& $form[$form_key];

    // Only act if we are confident this is an attached entity form element.
    if (empty($element['#data_set_name']) || !party_get_data_set_info($element['#data_set_name'])) {
      continue;
    }

    // Get the form callback.
    $callback = party_attached_entity_form_callback($element['#data_set_name']) . '_submit';

    // Execute the form callback.
    $return = $callback($element, $form_state, $element['#data_set'], $element['#delta'], $element['#data_set']
      ->getParty());
    $controllers[$element['#data_set_name']] = $element['#data_set'];
  }
  foreach ($controllers as $controller) {
    $controller
      ->save();
  }
}

/**
 * The default data set form callback.
 * This returns a $form array for the entity in $attached_entity
 *
 * @param $form
 *   The form we're attaching to. This may be the whole form or a sub-section
 *   of the form with the #parents key set.
 * @param $form_state
 *   The form state
 * @param $attached_entity
 *   The attached entity this form is for
 * @param $party
 *   The party the $attached entity is attached to
 */
function party_default_attached_entity_form(&$form, &$form_state, $data_set, $delta, $party) {
  field_attach_form($data_set
    ->getDataInfo('entity type'), $data_set
    ->getEntity($delta), $form, $form_state);
}

/**
 *  The default attached entity form validate callback.
 *
 * @param $form
 *   The whole form so far
 * @param $form_state
 *   The form state
 * @param $attached_entity
 *   The attached entity this form is for
 * @param $party
 *   The party the $attached entity is attached to
 */
function party_default_attached_entity_form_validate($form, &$form_state, &$attached_entity, $party) {

  // Validate fields.
  $pseudo_entity = (object) $form_state['values'][$attached_entity
    ->hash()];
  $psuedo_entity->type = $attached_entity
    ->getEntityBundle();
  field_attach_form_validate($attached_entity
    ->getEntityType(), $pseudo_entity, $form[$attached_entity
    ->hash()], $form_state);
}

/**
 * The default attached entity form submit callback.
 *
 * @param $form
 *   The whole form so far
 * @param $form_state
 *   The form state
 * @param $attached_entity
 *   The attached entity this form is for
 * @param $party
 *   The party the $attached entity is attached to
 */
function party_default_attached_entity_form_submit($element, &$form_state, $data_set, $delta, $party) {

  // Submit fields.
  $data_set_info = party_get_data_set_info($data_set
    ->getDataInfo('name'));
  field_attach_submit($data_set
    ->getDataInfo('entity type'), $data_set
    ->getEntity($delta), $element, $form_state);
  entity_save($data_set
    ->getDataInfo('entity type'), $data_set
    ->getEntity($delta));
}

// --------------------------------------------------------------------------------------------------------------
// Party Actions & Processes

/**
 * Merge two parties.
 *
 * @deprecated Use entity_merge() instead.
 *
 * When running this function, the first party gets all of the second party's attached entities and the second party
 * becomes a 'ghost' party that points to the first.
 *
 * @param $first the first party.
 * @param $second the second party
 */
function party_merge($first, $second) {
  trigger_error(__FUNCTION__ . " is deprecated. Use entity_merge() instead.", E_USER_DEPRECATED);
  if (!module_exists('entity_merge')) {
    watchdog('party', 'Entity Merge module is required to use party_merge().', array(), WATCHDOG_ERROR);
  }
  return entity_merge($first, $second, 'party');
}

// --------------------------------------------------------------------------------------------
// React to entity changes

/**
 * Implements hook_entity_update($entity, $type);
 *
 * Trigger rules and update party label if an attached entity has been saved.
 */
function party_entity_update($entity, $type) {
  if ($type == "party" && module_exists('rules')) {
    rules_invoke_event('party_update', $entity);
  }

  // Get necessary entity info.
  $wrapper = entity_metadata_wrapper($type, $entity);

  // Get data sets.
  $data_sets = party_get_data_set_info();

  // Is this entity party of a data set?
  $data_set_name = FALSE;
  foreach ($data_sets as $name => $def) {
    if ($def['entity type'] == $type && $def['entity bundle'] == $wrapper
      ->getBundle()) {
      $data_set_name = $name;
      break;
    }
  }

  // If it's not part of a data set do nothing else
  if (!$data_set_name) {
    return;
  }

  // Update the primary fields if this is attached to a party.
  if (!empty($entity->party_attaching_party)) {
    $controller = entity_get_controller('party');
    $party = party_load($entity->party_attaching_party);
    $controller
      ->setPrimaryFields($party);
  }
}

/**
 * Implements hook_party_delete().
 */
function party_party_delete($party) {
  foreach (party_get_data_set_info() as $data_set) {
    $party
      ->getDataSetController($data_set['set_name'])
      ->hook_party_delete();
  }
}

/**
 * Implements hook_entity_delete.
 */
function party_entity_delete($entity, $entity_type) {
  $query = db_select('party_attached_entity', 'pae')
    ->fields('pae', array(
    'pid',
    'data_set',
    'delta',
  ))
    ->condition('entity_type', $entity_type)
    ->condition('eid', entity_id($entity_type, $entity));
  $query
    ->addExpression('CONCAT(pid,\'-\',delta)', 'id');
  $rows = $query
    ->execute()
    ->fetchAllAssoc('id');
  foreach ($rows as $row) {
    $party = party_load($row->pid);
    if ($party) {
      $party
        ->getDataSetController($row->data_set)
        ->detachEntityByDelta($row->delta)
        ->save();
    }
  }
}

/**
 * Callback for getting attached entity property values.
 *
 * @see entity_metadata_field_property_get().
 */
function party_property_dataset_get($party, array $options, $data_set_name, $entity_type, $info) {
  $entities = $party
    ->getDataSetController($data_set_name)
    ->getEntityIds();

  // @TODO: Fix this to handle multiple entities when that's available.
  return $entities ? reset($entities) : NULL;
}

/**
 * Callback for getting has attached entity property values.
 *
 * @see entity_metadata_field_property_get().
 */
function party_property_has_dataset_get($party, array $options, $property_name, $entity_type, $info) {

  // Check that property is prefixed with 'has_dataset'.
  if (substr($property_name, 0, 11) != 'has_dataset') {
    return FALSE;
  }

  // Remove the 'has_dataset_' from the front of the property name.
  $data_set_name = substr($property_name, 12);
  $data_set_ids = $party
    ->getDataSetController($data_set_name)
    ->getEntityIds();

  // Return TRUE/FALSE base on the outcome of the getEntityIds().
  return $data_set_ids ? TRUE : FALSE;
}

/**
 * Returns a new SelectQuery extended by PartyQuery for the active database.
 *
 * @param $options
 *   An array of options to control how the query operates.
 *
 * @return SelectQuery
 *   A new SelectQuery object for this connection.
 *
 * @see db_select()
 */
function party_query($alias = 'party', $options = array()) {
  return db_select('party', $alias, $options)
    ->addTag('party_access')
    ->extend('PartyQuery');
}

/**
 * Implements hook_query_alter().
 *
 * When loading an entity, join to the party_attached_entity table and add the
 * party id and data set name to the query.
 */
function party_query_alter(&$query) {

  // Deal with queries that need match our tags.
  if ($query
    ->hasTag('party_access')) {

    // See if we have access to archived parties.
    $archived = !(user_access('administer parties') || user_access('view archived parties'));

    // Find all the party tables and apply.
    $tables =& $query
      ->getTables();
    foreach ($tables as $alias => &$table) {
      if ($table['table'] == 'party') {

        // If it's the primary table, add as query conditions.
        if ($table['join type'] === NULL) {
          $query
            ->condition("{$alias}.hidden", 0);
          if ($archived) {
            $query
              ->condition("{$alias}.archived", 0);
          }
        }
        else {
          $table['condition'] .= " AND {$alias}.hidden = 0";
          if ($archived) {
            $table['condition'] .= " AND {$alias}.archived = 0";
          }
        }
      }
    }
  }

  // Cache the $entity_types and $query_tags
  $entity_types =& drupal_static(__FUNCTION__ . 'entity_types', array());
  $query_tags =& drupal_static(__FUNCTION__ . 'query_tags', array());
  if (empty($entity_types)) {

    // Get data sets.
    $data_sets = party_get_data_set_info();
    foreach ($data_sets as $name => $def) {
      $entity_types[$def['entity type']] = $def['entity type'];
      $query_tags[$def['entity type']] = $def['entity type'] . '_load_multiple';
    }
  }

  // Don't do anything if none of the tags are present.
  if (!call_user_func_array(array(
    $query,
    'hasAnyTag',
  ), $query_tags)) {
    return;
  }

  // Find which tag the query has.
  foreach ($query_tags as $entity_type => $tag) {

    // Skip if this isn't the right entity type.
    if (!call_user_func_array(array(
      $query,
      'hasTag',
    ), array(
      $tag,
    ))) {
      continue;
    }
    $entity_info = entity_get_info($entity_type);

    // Add the join to the query.
    $join_condition = '%alias.eid = base.' . $entity_info['entity keys']['id'] . ' AND %alias.entity_type = :entity_type';
    $join_args = array(
      ':entity_type' => $entity_type,
    );
    $pae_alias = $query
      ->leftJoin('party_attached_entity', 'pae', $join_condition, $join_args);
    $query
      ->addField($pae_alias, 'pid', 'party_attaching_party');
    $query
      ->addField($pae_alias, 'data_set', 'data_set_name');
    break;
  }
}

/**
 * Implements hook_entity_query_alter().
 */
function party_entity_query_alter($query) {
  if (isset($query->entityConditions['entity_type']['value'])) {
    if ($query->entityConditions['entity_type']['value'] == 'party') {
      $query
        ->addTag('party_access');
    }
  }
}

/**
 * Implements hook_search_api_query_alter().
 */
function party_search_api_query_alter(SearchApiQueryInterface $query) {

  // Check if this is a party index.
  $index = $query
    ->getIndex();
  if ($index->item_type == 'party') {

    // See whether we're explicitly bypassing party access.
    if (!$query
      ->getOption('party_access_bypass', FALSE)) {

      // Check whether we have access to archived parties.
      if (!(user_access('administer parties') || user_access('view archived parties'))) {

        // Check whether the archived field is indexed.
        $fields = $index
          ->getFields();
        if (isset($fields['archived'])) {
          $query
            ->condition('archived', 0);
        }
      }
    }
  }
}

/**
 * Find all columns of particular types on parties and their data sets.
 *
 * @param array|string $types
 *   Either a single or an array of possible schema types.
 * @param bool $reset
 *   Whether to rebuild the information.
 *
 * @return array
 *   Nested arrays of possible columns suitable for #options. The top level key
 *   is the data set label and the child arrays are of the format:
 *   - keys: data_set_name:field_name:column where field_name is empty for
 *     properties of an entity.
 *   - values: depending on whether a property or field
 *     - property_label (column)
 *     - field_label (field_name:column)
 *
 * @see http://api.drupal.org/api/drupal/includes!database!schema.inc/group/schemaapi/7
 */
function party_find_fields_of_types($types, $reset = FALSE) {

  // Get hold of our cached data.
  $types = drupal_map_assoc((array) $types);
  $hash_key = md5(serialize($types));
  $cache =& drupal_static(__FUNCTION__, array());

  // Check whether we need to build the information.
  if ($reset || !isset($cache[$hash_key])) {

    // Make sure we have an empty array to add to.
    $cache[$hash_key] = array();
    $options =& $cache[$hash_key];

    // Iterate over our data sets finding all the fields that are relevant.
    foreach (party_get_data_set_info() as $data_set_name => $set_info) {

      // Get our set key which is the label for the option group.
      $set_key = format_string('@label (@name)', array(
        '@label' => $set_info['label'],
        '@name' => $data_set_name,
      ));
      $options[$set_key] = array();

      // Add all of the properties of this entity.
      $entity_info = entity_get_info($set_info['entity type']);
      $schema = drupal_get_schema($entity_info['base table']);
      $property_info = entity_get_all_property_info($set_info['entity type']);
      foreach ($schema['fields'] as $column => $definition) {

        // Check whether this matches our allowed types.
        if (in_array($definition['type'], $types)) {

          // Build our key - data_set_name:field_name:column.
          $property_key = $set_info['set_name'] . '::' . $column;
          $options[$set_key][$property_key] = format_string('@label (@column)', array(
            '@label' => isset($property_info[$column]['label']) ? $property_info[$column]['label'] : $column,
            '@column' => $column,
          ));
        }
      }

      // Get hold of our fields and iterate over them adding them to our options.
      $fields = field_info_instances($set_info['entity type'], $set_info['entity bundle']);
      foreach ($fields as $field) {

        // Get hold of field info so we can check out the columns.
        $field_info = field_info_field($field['field_name']);
        foreach ($field_info['columns'] as $column => $definition) {

          // Check whether this matches our allowed types.
          if (in_array($definition['type'], $types)) {

            // Build our key - data_set_name:field_name:column.
            $field_key = $set_info['set_name'] . ':' . $field['field_name'] . ':' . $column;
            $options[$set_key][$field_key] = format_string('@label (@name:@column)', array(
              '@label' => $field['label'],
              '@name' => $field['field_name'],
              '@column' => $column,
            ));
          }
        }
      }
    }
  }
  return $cache[$hash_key];
}

/**
 * Find a value from a property or field on a data set.
 *
 * @param Party $party
 *   The party to find a value for.
 * @param string $data_set_name
 *   The data set name we're pulling from.
 * @param string $column
 *   The column to retrieve. This can be a column of a field or a property
 *   depending on whether $field_name was given.
 * @param string $field_name
 *   (optional) A field name. If not provided, $column will be treated as a
 *   property of the entity.
 *
 * @return
 *   The value retreived from the data set, or NULL if none was found.
 */
function party_get_data_set_value($party, $data_set_name, $column, $field_name = NULL) {
  $controller = $party
    ->getDataSetController($data_set_name);
  if ($entity = $controller
    ->getEntity()) {
    if ($field_name) {
      $items = field_get_items($controller
        ->getDataInfo('entity type'), $entity, $field_name);
      if ($items) {
        $item = reset($items);
        return isset($item[$column]) ? $item[$column] : NULL;
      }
      else {
        return NULL;
      }
    }
    else {
      return isset($entity->{$column}) ? $entity->{$column} : NULL;
    }
  }
}

/**
 * Set a value to a property or field on a data set.
 *
 * @param Party $party
 *   The party to find a value for.
 * @param mixed $value
 *   The value to set.
 * @param string $data_set_name
 *   The data set name we're pulling from.
 * @param string $column
 *   The column to retrieve. This can be a column of a field or a property
 *   depending on whether $field_name was given.
 * @param string $field_name
 *   (optional) A field name. If not provided, $column will be treated as a
 *   property of the entity.
 */
function party_set_data_set_value($party, $value, $data_set_name, $column, $field_name = NULL) {
  $controller = $party
    ->getDataSetController($data_set_name);
  if ($entity = $controller
    ->getEntity(0, TRUE)) {
    if ($field_name) {
      $entity->{$field_name}[LANGUAGE_NONE][0][$column] = $value;
    }
    else {
      $entity->{$column} = $value;
    }
    $controller
      ->save(TRUE);
  }
}

/**
 * Implements hook_forms().
 */
function party_forms($form_id, $args) {
  $forms = array();
  if (substr($form_id, 0, 36) == 'party_attached_entity_edit_form_form') {
    $forms[$form_id] = array(
      'callback' => 'party_attached_entity_edit_form_form',
    );
  }
  return $forms;
}

/**
 * Implements hook_search_api_alter_callback_info().
 */
function party_search_api_alter_callback_info() {
  $callbacks['party_alter_status_filter'] = array(
    'name' => t('Status filter'),
    'description' => t('Exclude items from indexing based on their status and merging.'),
    'class' => 'PartyAlterStatusFilter',
    // Filters should be executed first.
    'weight' => -10,
  );
  return $callbacks;
}

/**
 * Acquire or create a party based off conditions.
 *
 * @param array $values
 *   An array of party fields to match on. Keys are the field and values are
 *   the expected values. If empty, we will use the given behavior.
 * @param array $context
 *   An array of contextual information. If the 'class' key is set, we'll
 *   attempt to use the specified class. Otherwise we fall back to the system
 *   default in the 'party_acquire_default_class' variable.
 * @param string $method
 *   Optionally pass a variable to be filled with the acquisition method.
 *
 * @return Party|FALSE
 *   The acquired or newly created party or FALSE on a failure.
 */
function party_acquire(array $values, $context = array(), &$method = '') {

  // Figure out which class to use.
  $class = isset($context['class']) ? $context['class'] : variable_get('party_acquire_default_class', 'PartyAcquisition');

  // Check this is a valid acquisition class.
  if (!class_exists($class)) {
    throw new Exception(t('Party acquisition class %class cannot be found.', array(
      '%class' => $class,
    )));
  }
  $acquisition = new $class();
  if (!$acquisition instanceof PartyAcquisitionInterface) {
    throw new Exception(t('Class %class is not a valid acquisition class.', array(
      '%class' => $class,
    )));
  }

  // Pass onto the acquire method.
  return $acquisition
    ->acquire($values, $context, $method);
}

/**
 * Implements hook_field_widget_info().
 */
function party_field_widget_info() {
  return array(
    'party_primary_field' => array(
      'label' => t('Primary field'),
      'description' => t('Pull field data from multiple sources to cache on a party.'),
      'field types' => array_keys(field_info_field_types()),
      'settings' => array(
        'sources' => array(),
      ),
      'behaviors' => array(
        'multiple values' => FIELD_BEHAVIOR_CUSTOM,
        'default value' => FIELD_BEHAVIOR_NONE,
      ),
    ),
  );
}
function party_form_field_ui_field_edit_form_alter(&$form, &$form_state) {
  if ($form['#instance']['widget']['type'] == 'party_primary_field') {
    $form['instance']['#weight'] = -2;
    $form['sources'] = array(
      '#type' => 'fieldset',
      '#title' => t('Sources'),
      '#target' => $form['#field']['field_name'],
      '#parents' => array(
        'instance',
        'widget',
        'settings',
        'sources',
      ),
      '#default_value' => $form['#instance']['widget']['settings']['sources'],
      '#weight' => -1,
    );
    form_load_include($form_state, 'inc', 'party', 'party.primary_fields');
    PartyPrimaryFields::sourceForm($form['sources'], $form_state, $form);
  }
}

/**
 * Implements hook_form_alter() for field_ui_field_overview_form().
 *
 * Prevent the primary fields widget from being available to other entities.
 */
function party_form_field_ui_field_overview_form_alter(&$form, &$form_state) {
  if ($form['#entity_type'] != 'party') {
    $options = array();

    // Get the normal select options.
    foreach (array(
      '_add_new_field',
      '_add_existing_field',
    ) as $key) {
      $options[] =& $form['fields'][$key]['widget_type']['#options'];
    }

    // Also get the JS settings options.
    foreach ($form['#attached']['js'] as &$js) {
      if (is_array($js) && isset($js['type']) && $js['type'] == 'setting') {
        if (isset($js['data']['fieldWidgetTypes'])) {
          $options[] =& $js['data']['fieldWidgetTypes'];
        }
      }
    }

    // Remove the primary field widget.
    foreach ($options as &$option) {
      foreach ($option as &$group) {
        unset($group['party_primary_field']);
      }
    }
  }
}

/**
 * Implements hook_form_alter() for field_ui_widget_type_form().
 *
 * Prevent the primary fields widget from being available to other entities.
 */
function party_form_field_ui_widget_type_form_alter(&$form, &$form_state) {
  if ($form['#entity_type'] != 'party') {
    unset($form['basic']['widget_type']['#options']['party_primary_field']);
  }
}

/**
 * Implements hook_field_extra_fields().
 */
function party_field_extra_fields() {
  $extra = array();
  $info = PartyPrimaryFields::getPropertyInfo('party', 'party');
  $i = -20;
  foreach ($info as $property => $info) {
    if (!empty($info['party primary field'])) {
      $extra['party']['party']['form'][$property] = array(
        'label' => $info['label'],
        'description' => $info['description'],
        'weight' => $i++,
        'edit' => l(t('edit'), 'admin/community/party/primary-fields/' . $property),
      );
    }
  }
  return $extra;
}

Functions

Namesort descending Description
party_access Determines whether operations are allowed on a Party and attached entities.
party_acquire Acquire or create a party based off conditions.
party_admin_paths Implements hook_admin_paths().
party_attached_entity_form_callback Get the form callback for attached entities.
party_attach_entity Attach an entity to a party according to a given data set.
party_create Create a party object ready for saving to the database.
party_ctools_plugin_directory Implements hook_ctools_plugin_directory().
party_data_set_attach_form Attach attached entity forms to a form.
party_data_set_attach_form_submit Submit attached entity forms
party_data_set_attach_form_validate Validate attached entity forms.
party_data_set_form Get the form for a data set (more to the point get a set of fields for the data set might need to work on this). This function checks the edit party attached data set permission.
party_data_set_load Menu loader for data sets. First check against pieces, and if nothings found check against the $data_set_names themselves.
party_default_attached_entity_form The default data set form callback. This returns a $form array for the entity in $attached_entity
party_default_attached_entity_form_submit The default attached entity form submit callback.
party_default_attached_entity_form_validate The default attached entity form validate callback.
party_delete Delete a party.
party_delete_multiple Delete multiple parties.
party_detach_entity Detach an entity from a party according to a given data set.
party_entity_access Wrap around party_access() for the entity api 'access callback'.
party_entity_delete Implements hook_entity_delete.
party_entity_info Implements hook_entity_info().
party_entity_info_alter Implements hook_entity_info_alter().
party_entity_query_alter Implements hook_entity_query_alter().
party_entity_update Implements hook_entity_update($entity, $type);
party_field_extra_fields Implements hook_field_extra_fields().
party_field_widget_info Implements hook_field_widget_info().
party_find_fields_of_types Find all columns of particular types on parties and their data sets.
party_forms Implements hook_forms().
party_form_field_ui_field_edit_form_alter
party_form_field_ui_field_overview_form_alter Implements hook_form_alter() for field_ui_field_overview_form().
party_form_field_ui_widget_type_form_alter Implements hook_form_alter() for field_ui_widget_type_form().
party_get_data_set_info Get all data sets from hook_party_data_set_info().
party_get_data_set_value Find a value from a property or field on a data set.
party_get_entity_data_set Get the data set definition associated with a given entity and bundle.
party_get_party_data_sets Get the data sets expected for a party.
party_get_party_piece_info Get all party piece definitions from hook_party_party_pieces().
party_help Implements hook_help().
party_hook_info Implements hook_hook_info().
party_load Load a party entity from the database.
party_load_multiple Load party entities from the database.
party_menu Implements hook_menu().
party_menu_alter Implements hook_menu_alter().
party_menu_local_tasks_alter Implements hook_menu_local_tasks_alter().
party_merge Deprecated Merge two parties.
party_modules_disabled Implements hook_modules_enabled()
party_modules_enabled Implements hook_modules_enabled()
party_page_title Page title callback for party view page.
party_party_delete Implements hook_party_delete().
party_party_operations_merge Merge multiple parties.
party_permission Implements hook_permission().
party_preprocess_entity Implements hook_preprocess_HOOK().
party_property_dataset_get Callback for getting attached entity property values.
party_property_has_dataset_get Callback for getting has attached entity property values.
party_query Returns a new SelectQuery extended by PartyQuery for the active database.
party_query_alter Implements hook_query_alter().
party_save Save a party.
party_search_api_alter_callback_info Implements hook_search_api_alter_callback_info().
party_search_api_query_alter Implements hook_search_api_query_alter().
party_set_data_set_value Set a value to a property or field on a data set.
party_theme Implements hook_theme().
party_uri URI callback for contacts.
party_views_api Implements hook_views_api().