Skip to content
relation.module 32.1 KiB
Newer Older
ubuntu's avatar
ubuntu committed
require_once dirname(__FILE__) . '/relation.rules.inc';
require_once dirname(__FILE__) . '/relation.ctools.inc';
 * Describes relations between entities.
/**
 * Implements hook_entity_info().
 */
function relation_entity_info() {
  $entities['relation'] = array(
    'label' => t('Relation'),
    'base table' => 'relation',
    'fieldable' => TRUE,
    'controller class' => 'DrupalDefaultEntityController',
    'save callback' => 'relation_save',
    'creation callback' => 'relation_rules_create',
    'deletion callback' => 'relation_delete',
    'access callback' => 'relation_rules_access',
    'uri callback' => 'relation_uri',
chx's avatar
chx committed
    'view callback' => 'relation_multiple_view',
      'bundle' => 'relation_type',
      'bundle' => 'relation_type',
    'bundles' => array(),
  foreach (relation_get_types() as $type => $info) {
    $entities['relation']['bundles'][$type] = (array) $info;
    $entities['relation']['bundles'][$type]['admin'] = array(
      'path' => 'admin/structure/relation/manage/%relation_type',
      'real path' => 'admin/structure/relation/manage/' . $type,
      'bundle argument' => 4,
      'access arguments' => array('administer relation types'),
 * Implements hook_entity_property_info().
 */
function relation_entity_property_info() {
  $info = array();
  $properties = &$info['relation']['properties'];

    'rid' => array(
      'label' => t('Relation ID'),
      'description' => t('The internal numeric ID of the relation.'),
      'type' => 'integer',
      'schema field' => 'rid',
    ),
    'relation_type' => array(
      'label' => t('Relation type'),
      'type' => 'token',
      'description' => t('The type of the relation.'),
      'setter callback' => 'entity_property_verbatim_set',
      'setter permission' => 'administer nodes',
      'options list' => 'relation_rules_get_type_options',
      'required' => TRUE,
      'schema field' => 'relation_type',
    ),
    'endpoints' => array(
      'label' => t('Endpoints'),
      'type' => 'list<entity>',
      'description' => t('The endpoints of the relation.'),
      'setter callback' => 'relation_rules_set_endpoints',
      'getter callback' => 'relation_rules_get_endpoints',
      'setter permission' => 'administer nodes',
      'required' => TRUE,
    ),
ubuntu's avatar
ubuntu committed
  );
/**
 * Implements hook_entity_property_info_alter().
 */
function relation_entity_property_info_alter(&$info) {
  // Add relation type-specific information, so that it is easily available
  // for modules that need to introspect the
  // relation structure (ie. Search API).
  foreach (relation_get_types() as $relation_type => $relation) {
    foreach ($relation->source_bundles as $key => $bundles) {
      list($entity_type, $bundle) = explode(':', $bundles, 2);
      $info['relation']['bundles'][$bundle]['properties']['endpoints_source_' . $entity_type] = array(
        'label' => t('@type (source endpoint)', array('@type' => $entity_type)),
        'type' => 'list<' . $entity_type . '>',
        'getter callback' => 'relation_rules_get_specific_endpoints',
        'endpoint_type' => 'source',
        'relation_directional' => $relation->directional,
      );
    }

    foreach ($relation->target_bundles as $key => $bundles) {
      list($entity_type, $bundle) = explode(':', $bundles, 2);
      $info['relation']['bundles'][$bundle]['properties']['endpoints_target_' . $entity_type] = array(
        'label' => t('@type (target endpoint)', array('@type' => $entity_type)),
        'type' => 'list<' . $entity_type . '>',
        'getter callback' => 'relation_rules_get_specific_endpoints',
        'endpoint_type' => 'target',
        'relation_directional' => $relation->directional,
      );
    }

    $source_bundles = $relation->source_bundles;
    $original_sb = array_values($source_bundles);
    $directional = FALSE;
    // If its a directional relation, merge the source and target bundles.
    if (count($relation->target_bundles) >= 1) {
      $original_tb = array_values($relation->target_bundles);
      $target_bundles = array_merge($source_bundles, $relation->target_bundles);
      $source_bundles = $target_bundles;
      $directional = TRUE;
    }
    else {
      $target_bundles = $source_bundles;
    }
    foreach ($target_bundles as $target_key => $target_bundle) {
      list($entity_target) = explode(':', $target_bundle, 2);
      foreach ($source_bundles as $source_key => $source_bundle) {
        $property_reverse = ($directional && !in_array($source_bundle, $original_sb) && !in_array($target_bundle, $original_tb));
        $property = ($property_reverse) ? 'relation_' . $relation_type . '_' . $entity_target . '_reverse' : 'relation_' . $relation_type . '_' . $entity_target;
        list($entity_source) = explode(':', $source_bundle, 2);
        if (!$directional || ($property_reverse) || (in_array($target_bundle, $original_tb) && in_array($source_bundle, $original_sb))) {
          $info[$entity_source]['properties'][$property] = array(
            'label' => t('Relation @relation_type (to @entity' . (($property_reverse) ? ' reverse)' : ')'), array('@relation_type' => $relation_type, '@entity' => $entity_target)),
            'type' => 'list<' . $entity_target . '>',
            'relation_type' => $relation_type,
            'target_type' => $entity_target,
            'description' => t("A list of entities related."),
            'getter callback' => 'relation_rules_get_related_entities',
          );
        }
      }
    }
  }
}

 * Implements hook_permission().
 */
function relation_permission() {
  return array(
    'administer relation types' => array(
      'title' => t('Administer Relation types'),
      'description' => t('Create, edit, delete, and perform administration tasks for relation types.'),
    ),
    'export relation types' => array(
      'title' => t('Export Relation types'),
      'description' => t('Export relation types.'),
    ),
    'access relations' => array(
      'title' => t('View Relations'),
      'description' => t('Grant access to view the endpoints of Relations.'),
    ),
    'create relations' => array(
      'title' => t('Create Relations'),
      'description' => t('Create Relations between entities.'),
    ),
    'edit relations' => array(
      'title' => t('Edit Relations'),
      'description' => t('Edit fields on existing relations.'),
    ),
    'delete relations' => array(
      'title' => t('Delete Relations'),
      'description' => t('Delete existing relations.'),
    'administer relations' => array(
      'title' => t('Administer Relations'),
      'description' => t('Administer existing relations.'),
    ),
 * Implements hook_menu().
function relation_menu() {
  $items['relation/%relation'] = array(
    'title callback' => 'relation_page_title',
    'title arguments' => array(1),
    'access arguments' => array('access relations'),
    'page callback' => 'relation_page',
    'page arguments' => array(1),
  );
  $items['relation/%relation/view'] = array(
    'title' => 'View',
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -10,
  );
  $items['relation/%relation/edit'] = array(
    'title' => 'Edit',
    'access arguments' => array('edit relations'),
    'page callback' => 'drupal_get_form',
    'page arguments' => array('relation_edit_form', 1),
    'type' => MENU_LOCAL_TASK,
  $items['relation/%relation/delete'] = array(
    'title' => 'Delete',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('relation_delete_confirm', 1),
    'access arguments' => array('delete relations'),
    'weight' => 10,
    'type' => MENU_LOCAL_TASK,
  );
  $items['admin/structure/relation'] = array(
    'title' => 'Relation types',
    'access arguments' => array('administer relation types'),
naught101's avatar
naught101 committed
    'page callback' => 'relation_type_list',
chx's avatar
chx committed
    'file' => 'relation.admin.inc',
    'description' => 'Manage relation types, including relation properties (directionality, transitivity etc), available bundles, and fields.',
  $items['admin/structure/relation/list'] = array(
    'title' => 'List',
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -10,
  );
  $items['admin/structure/relation/add'] = array(
    'title' => 'Add relation type',
    'access arguments' => array('administer relation types'),
    'page callback' => 'drupal_get_form',
    'page arguments' => array('relation_type_form'),
    'type' => MENU_LOCAL_ACTION,
chx's avatar
chx committed
    'file' => 'relation.admin.inc',
chx's avatar
chx committed
  $items['admin/structure/relation/import'] = array(
    'title' => 'Import  relation type',
    'access arguments' => array('administer relation types'),
    'page callback' => 'drupal_get_form',
    'page arguments' => array('relation_type_import'),
    'type' => MENU_LOCAL_ACTION,
    'file' => 'relation.admin.inc',
  );
  $items['admin/structure/relation/manage/%relation_type'] = array(
chx's avatar
chx committed
    'title' => 'Edit relation type',
    'title callback' => 'relation_type_page_title',
    'title arguments' => array(4),
    'access arguments' => array('administer relation types'),
    'page callback' => 'drupal_get_form',
    'page arguments' => array('relation_type_form', 4),
chx's avatar
chx committed
    'file' => 'relation.admin.inc',
  $items['admin/structure/relation/manage/%relation_type/edit'] = array(
    'title' => 'Edit',
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -10,
  );
  $items['admin/structure/relation/manage/%relation_type/delete'] = array(
    'page arguments' => array('relation_type_delete_confirm', 4),
    'access arguments' => array('administer relation types'),
    'type' => MENU_LOCAL_TASK,
    'file' => 'relation.admin.inc',
  $items['admin/structure/relation/manage/%relation_type/export'] = array(
    'title' => 'Export',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('relation_export_relation_type', 4),
    'access arguments' => array('export relation types'),
    'type' => MENU_LOCAL_TASK,
    'file' => 'relation.ctools.inc',
    'weight' => 10,
  );
  $items['admin/config/development/generate/relation'] = array(
    'title' => 'Generate relations',
    'access arguments' => array('administer relation types'),
    'page callback' => 'drupal_get_form',
    'page arguments' => array('relation_generate_form'),
    'file' => 'relation.admin.inc',
  );
  // Relations listing.
  $items['admin/content/relation'] = array(
    'title' => 'Relations',
    'page callback' => 'relation_admin_content',
    'access arguments' => array('administer relations'),
    'description' => 'View, edit and delete all the available relations on your site.',
    'file' => 'relation.admin.inc',
    'type' => MENU_LOCAL_TASK,
  );
 * Creates a relation bundle.
 * @param $info
 *   Array of relation type settings. All but relation_type are optional,
 *   although if the bundles arrays are empty, relations might be difficult to
 *   create! Keys are:
 *   - relation_type: Relation type machine name (string).
 *   - label: Relation type human-readable name (string). Defaults to
 *     duplicating relation_type.
 *   - directional: whether relation is directional (boolean). Defaults to
 *     FALSE.
 *   - transitive: whether relation is transitive (boolean). Defaults to FALSE.
 *   - r_unique: whether relations of this type are unique (boolean). Defaults
 *     to FALSE.
 *   - min_arity: minimum number of entities in relations of this type
 *     (int >= 2). Defaults to 2.
 *   - max_arity: maximum number of entities in relations of this type
 *     (int >= min_arity). Defaults to 2.
 *   - source_bundles: array containing allowed bundle keys. This is used for
 *     both directional and non-directional relations. Bundle key arrays are
 *     of the form 'entity:bundle', eg. 'node:article', or 'entity:*' for all
 *     bundles of the type.
 *   - target_bundles: array containing arrays allowed target bundle keys.
 *     This is the same format as source_bundles, but is only used for
 *     directional relations.
 *
 * @return
 *   Relation type object, or FALSE if creation fails.
function relation_type_create($info = array()) {
  $info = (array) $info;
  if (empty($info['relation_type'])) {
    return FALSE;
    'min_arity' => 2,
    'max_arity' => 2,
    'directional' => FALSE,
    'transitive' => FALSE,
    'source_bundles' => array(),
    'target_bundles' => array(),
  if (empty($info['label'])) {
    $info['label'] = $info['relation_type'];
  if (empty($info['reverse_label'])) {
    // Directional relations should have a reverse label, but if they don't,
    // or if they are symmetric:
    $info['reverse_label'] = $info['label'];
  }

  return (object) $info;
}

/**
 * Saves a relation bundle.
 *
 * @param $relation_type
 *   stdClass object with relation type properties. See relation_type_create().
 * @param $write_record_keys
 *   Array containing the primary key of the relation ('relation_type'), if we are
 *   updating a relation, or an empty array if we are creating a new relation.
 */
function relation_type_save($relation_type, $write_record_keys = array()) {
  // Make sure all keys are populated.
  $relation_type = relation_type_create($relation_type);

  $type = $relation_type->relation_type;
  $source_bundles = $relation_type->source_bundles;
  $target_bundles = $relation_type->target_bundles;
  unset($relation_type->source_bundles, $relation_type->target_bundles);

  $transaction = db_transaction();
  drupal_write_record('relation_type', $relation_type, $write_record_keys);

  // Remove all existing bundles from the relation type before re-adding.
  db_delete('relation_bundles')
    ->condition('relation_type', $type)
    ->execute();
  $query = db_insert('relation_bundles')
    ->fields(array('relation_type', 'entity_type', 'bundle', 'r_index'));
  foreach ($source_bundles as $entity_bundles) {
    list($entity_type, $bundle) = explode(':', $entity_bundles, 2);
    $query->values(array($type, $entity_type, $bundle, 0));
  if ($relation_type->directional) {
    foreach ($target_bundles as $entity_bundles) {
      list($entity_type, $bundle) = explode(':', $entity_bundles, 2);
      $query->values(array($type, $entity_type, $bundle, 1));
  relation_type_ensure_instance($type);
  menu_rebuild();
}
/**
 * Make sure the instance exists for this type.
 */
function relation_type_ensure_instance($type) {
  if (!field_info_instance('relation', 'endpoints', $type)) {
chx's avatar
chx committed
    $instance = array(
      'field_name' => 'endpoints',
      'entity_type' => 'relation',
chx's avatar
chx committed
    );
    field_create_instance($instance);
  }
 * Loads a relation type (bundle).
 * @param $relation_type
 *   The machine name of the relation type (bundle) to be loaded.
 *   A relation type record (as an Array) in the same format as expected by
function relation_type_load($relation_type) {
  $types = relation_get_types(array($relation_type));
  return isset($types[$relation_type]) ? $types[$relation_type] : FALSE;
}

/**
 * Loads a relation type (bundle), or all relation bundles.
 *
 * @param $types
 *   An array of machine names of the relation types to be loaded. If $types
 *   is empty, load all relation types.
 *
 * @return
 *   A an array of relation type records in the same format as expected by
 *   relation_type_save(), keyed by relation_type.
function relation_get_types($types = array()) {
  ctools_include('export');
  $return = $types ? ctools_export_crud_load_multiple('relation_type', $types) : ctools_export_crud_load_all('relation_type');
  static $recurse = FALSE;
  if (!$recurse) {
    $recurse = TRUE;
    foreach ($return as $type => $data) {
      if (!empty($data->in_code_only)) {
        relation_type_ensure_instance($type);
      }
    }
chx's avatar
chx committed
    $recurse = FALSE;
/**
 * Returns all relation types in a way which can be used
 * on form options.
 */
function relation_get_types_options() {
  $types = relation_get_types();
  $options = array();
  foreach ($types as $type => $relation_type) {
    $options[$type] = $relation_type->label;
  }

  return $options;
}

 * Helper function. Attaches bundles to relation type objects in an array.
function _relation_get_types_bundles(&$relation_types) {
  foreach ($relation_types as &$relation_type) {
chx's avatar
chx committed
    if (empty($relation_type->in_code_only) && empty($relation_type->bundles_loaded)) {
      // If overridden or not exported at all, reset the bundles before
      // loading from the database to avoid duplication.
      $relation_type->source_bundles = array();
      $relation_type->target_bundles = array();
      foreach (db_query('SELECT relation_type, entity_type, bundle, r_index FROM {relation_bundles} WHERE relation_type = :relation_type', array(':relation_type' => $relation_type->relation_type)) as $record) {
        $endpoint = $record->r_index ? 'target_bundles' : 'source_bundles';
        $relation_type->{$endpoint}[] = "$record->entity_type:$record->bundle";
      }
      // Do not run this twice. ctools static caches the types but runs the
      // subrecord callback on the whole cache, every already loaded relation
      // type.
      $relation_type->bundles_loaded = TRUE;
/**
 * Lists all relation types.
 *
 * @return
 *   Array of relation type names in the format "Label (type)", keyed by
 *   relation_type.
 */
function relation_list_types() {
  $results = db_select('relation_type', 'rt')
    ->fields('rt', array('relation_type', 'label'))
    ->execute()->fetchAllAssoc('relation_type');
  $relation_types = array();
  foreach ($results as $type => $relation_type) {
    $relation_types[$type] = $relation_type->label . ' (' . $type . ')';
  }
  return $relation_types;
}

/**
 * Deletes a relation type (bundle).
 *
 * @param $relation_type
 *   The machine name of the relation type (bundle) to be deleted.
 */
function relation_type_delete($relation_type) {
  $endpoints_field = field_read_instance('relation', 'endpoints', $relation_type);
  field_delete_instance($endpoints_field, FALSE);
  field_attach_delete_bundle('relation', $relation_type);
  db_delete('relation_type')->condition('relation_type', $relation_type)->execute();
  db_delete('relation_bundles')->condition('relation_type', $relation_type)->execute();
 * Loads a relation from a relation id.
 * @param $rid
 *   Numerical id of the relation to be loaded.
 *   Loaded relation object. Relation objects are stdClass Object of the form:
 *   - rid: numeric relation id.
 *   - relation_type: relation bundle machine name.
 *   - arity: the number of entities in the relation
 *   - rdf_mapping: not yet implemented (empty array)
 *   - endpoints: Field holding the entities that make up the relation.
 *     Field columns are:
 *     - entity_type: The type of the entity (eg. node).
 *     - entity_id: Numeric entity ID.
function relation_load($rid, $vid = NULL, $reset = FALSE) {
  $conditions = (isset($vid) ? array('vid' => $vid) : array());
  $relations = relation_load_multiple(array($rid), $conditions, $reset);
  return reset($relations);
 * Loads a set of relations from an array of relation ids.
 * @param $rids
 *   Array of numerical relation ids of the relations to be loaded.
 *   Associative array of loaded relation objects, keyed by relation id.
function relation_load_multiple($rids, $conditions = array(), $reset = FALSE) {
  // Entity load handles field_attach_load for us.
  return entity_load('relation', $rids, $conditions, $reset);
/**
 * Relation display page. Currently only displays related entities.
 *
 * @TODO: implement directionality, possibly give more details on entities?
 */
function relation_page($relation) {
  $view_mode = 'full';
chx's avatar
chx committed
  return relation_view($relation);
}

function relation_view($relation, $view_mode = 'full') {
  $entity_type = 'relation';
  $entity = $relation;
  $entities = array($relation->rid => $relation);

  field_attach_prepare_view($entity_type, $entities, $view_mode);
  entity_prepare_view($entity_type, $entities);
  $build = field_attach_view($entity_type, $entity, $view_mode);
  $build += array(
    '#entity' => $relation,
    '#view_mode' => $view_mode,
    '#language' => LANGUAGE_NONE,
  );
  module_invoke_all('entity_view', $entity, $entity_type, $view_mode, LANGUAGE_NONE);
  drupal_alter('entity_view', $build, $entity_type);
chx's avatar
chx committed
function relation_multiple_view($relations, $view_mode) {
  $build = array();
  foreach ($relations as $relation) {
    $build['relation'][$relation->rid] = relation_view($relation, $view_mode);
  }
  return $build;
}

 */
function relation_page_title($relation) {
  return 'Relation ' . $relation->rid;
/**
 * Relation type display/edit page title callback.
 */
function relation_type_page_title($type) {
  return $type->label;
}

 * Relation edit form.
function relation_edit_form($form, &$form_state, $relation) {
  $form_state['relation'] = $relation;
  field_attach_form('relation', $relation, $form, $form_state);
  $form['actions']['#weight'] = 100;
  $form['actions']['save'] = array(
    '#type' => 'submit',
    '#value' => t('Save'),
  );
  return $form;
}

function relation_edit_form_submit($form, &$form_state) {
  $relation = $form_state['relation'];
  entity_form_submit_build_entity('relation', $relation, $form, $form_state);
  $rid = relation_save($relation);
  if ($rid) {
    $form_state['redirect'] = 'relation/' . $rid;
  }
 * Checks if a relation exists.
 * The following example demonstrates how to check if a relation of type
 * 'likes' exists between two entities, user 17 and node 253.
 *
 * @code
 *   $entity_keys = array(
 *     array('entity_type' => 'user', 'entity_id' => 17),
 *     array('entity_type' => 'node', 'entity_id' => 253),
 *   );
 *   $relation_type = 'likes';
 *   $results = relation_relation_exists($entity_keys, $relation_type);
 * @endcode
 *
 *   The entity keys of the relation to found. Entity_keys are arrays keyed by
 *   'entity_type' and 'entity_id'.
 * @param $relation_type
 *   (Optional) The relation type (bundle) of the relation to be checked.
 *
 * @return
 *   Return FALSE if no relation exists, or an array of matching relations.
 */
function relation_relation_exists($entity_keys, $relation_type = NULL) {
  $query = relation_query();
  foreach ($entity_keys as $entity_key) {
    $query->related($entity_key['entity_type'], $entity_key['entity_id']);
  }
  if ($relation_type) {
    $query->propertyCondition('relation_type', $relation_type);
  $query->propertyCondition('arity', count($entity_keys));
  $relation_ids = $query->execute();
  return $relation_ids ? $relation_ids : FALSE;
}

 * Constructs a relation from a relation type machine name and a list of endpoints.
 * @param $relation_type
 *   The relation type (bundle) of the relation to be created.
 * @param $endpoints
 *   A list of endpoint entities. Each endpoint is defined by an associate
 *   array, with an entity_type and entity_id key. For example:
 *   @code
 *   array(
 *     array('entity_type' => 'node', 'entity_id' => 1),
 *     array('entity_type' => 'user', 'entity_id' => 5),
 *   array);
 *   @endcode
function relation_create($type, $endpoints, $account = NULL) {
  if (!isset($account)) {
    $account = $GLOBALS['user'];
  $relation = new stdClass();
  $relation->is_new = TRUE;
  $relation->relation_type = $type;
  $relation->uid = $account->uid;
  $relation->endpoints[LANGUAGE_NONE] = $endpoints;
/**
 * Saves a relation.
 *
 * @param $relation
 *   The relation entity data object. See relation_create() for the appropriate
 *   format (or just use it).
 *
 * @return
 *   The new relation id.
 */
function relation_save($relation) {
  try {
    field_attach_validate('relation', $relation);
  }
  catch (FieldValidationException $e) {
  $transaction = db_transaction();
  $relation->arity = count($relation->endpoints[LANGUAGE_NONE]);
  // use time() instead of REQUEST_TIME, because otherwise tests
  // RelationQuery::order() are impossible.
  $relation->changed = time();
  if (empty($relation->is_new) && !empty($relation->rid)) {
    $keys = array('rid');
    $op = 'update';
chx's avatar
chx committed
    $relation->created = time();
    $keys = array();
    $op = 'insert';
  field_attach_presave('relation', $relation);
  module_invoke_all('entity_presave', $relation, 'relation');
  drupal_write_record('relation_revision', $relation);
  drupal_write_record('relation', $relation, $keys);
  call_user_func("field_attach_$op", 'relation', $relation);
  module_invoke_all('entity_' . $op, $relation, 'relation');
  module_invoke('rules', 'invoke_event', 'relation_' . $op, $relation);
  drupal_static_reset('relation_get_related_entity');
 * Insert a new relation in the database.
function relation_insert($type, $endpoints) {
  $relation = relation_create($type, $endpoints);
  return relation_save($relation);
}

/**
 * Updates a relation.
 */
function relation_update($relation) {
  relation_save($relation);
}

/**
 * Deletes a relation.
 *
 * @param $rid
 *   The numeric id of the relation to be deleted.
function relation_delete($rid) {
  relation_multiple_delete(array($rid));
 * @param $rid
 *   An array of numeric ids of the relation to be deleted.
function relation_multiple_delete($rids) {
  $relations = relation_load_multiple($rids);
  foreach ($relations as $rid => $relation) {
    db_delete('relation')->condition('rid', $rid)->execute();
chx's avatar
chx committed
    db_delete('relation_revision')->condition('rid', $rid)->execute();
    module_invoke_all('entity_delete', $relation, 'relation');
    field_attach_delete('relation', $relation);
 * Menu callback: ask for confirmation of relation deletion.
 */
function relation_delete_confirm($form, &$form_state, $relation) {
  $form['#relation'] = $relation;
  // Always provide entity id in the same form key as in the entity edit form.
  $form['rid'] = array('#type' => 'value', '#value' => $relation->rid);
  return confirm_form($form,
    t('Are you sure you want to delete relation %rid?', array('%rid' => $relation->rid)),
    'relation/' . $relation->rid,
    t('This action cannot be undone.'),
    t('Delete'),
    t('Cancel')
  );
}

/**
 * Executes relation deletion.
 */
function relation_delete_confirm_submit($form, &$form_state) {
  if ($form_state['values']['confirm']) {
    $relation = $form['#relation'];
    relation_delete($form_state['values']['rid']);
    watchdog('relation', '@type: deleted %title.', array('@type' => $relation->relation_type, '%title' => $relation->rid));
    drupal_set_message(t('@type %title has been deleted.', array('@type' => $relation->relation_type, '%title' => $relation->rid)));
  }

  $form_state['redirect'] = '<front>';
}
 * Gets a relation's URI.
 *
 * @see entity_uri()
 */
function relation_uri($relation) {
  return array('path' => 'relation/' . $relation->rid);

/**
 * Returns a query object to find related entities.
 *
 * @param $entity_type
 *   The entity type of one of the endpoints.
 *   The entity id of one of the endpoints.
 *
 * @return RelationQuery
 *   The query object itself.
 */
function relation_query($entity_type = NULL, $entity_id = NULL, $index = NULL) {
  return new RelationQuery($entity_type, $entity_id, $index);
/**
 * Returns the entity object of the first other entity in the first relation
 * that matches the given conditions. Do not expect to get exactly what you
 * want, especially if you have multiple relations of the same type on the
 * search entity.
 *
 * @param $entity_type
 *   The entity type of one of the endpoints.
 * @param $entity_id
 *   The entity id of one of the endpoints.
 * @param $relation_type
 *   (optional) The relation type of the relation to find.
 * @param $r_index
 *   (optional) The index of the search entity in the relation to be found
 *   (0 = source, 1 = target).
 *
 * @return
 *   The entity object from the other endpoint.
 */
function relation_get_related_entity($entity_type, $entity_id, $relation_type = NULL, $r_index = NULL) {

  // Static cache the results of relation_query() and relation_load() to avoid
  // duplicate queries if this is called multiple times with the same arguments
  // during a request.
  $items = &drupal_static(__FUNCTION__);
  $request_key = "$entity_type:$entity_id";
  $cache_key = "$request_key:$relation_type:$r_index";

  if (isset($items[$cache_key])) {
    $entities = $items[$cache_key];
  else {
    $query = relation_query($entity_type, $entity_id, $r_index);
    if ($relation_type) {
      $query->entityCondition('bundle', $relation_type);
    }
    $results = $query->execute();
    $result = reset($results);
    if ($result) {
      $relation = relation_load($result->rid);
      $entities = field_get_items('relation', $relation, 'endpoints');
    }
    else {
      $entities = FALSE;
    }
    $items[$cache_key] = $entities;

  if ($entities) {
    $first_entity_key = $entities[0]['entity_type'] . ':' . $entities[0]['entity_id'];
    if (isset($r_index)) {
      $request_key = $request_key . ':' . $r_index;
      $first_entity_key = $first_entity_key . ':' . $entities[0]['r_index'];
    }
    if ($request_key == $first_entity_key) {
      $other_endpoints = entity_load($entities[1]['entity_type'], array($entities[1]['entity_id']));
      return reset($other_endpoints);
    }
    $other_endpoints = entity_load($entities[0]['entity_type'], array($entities[0]['entity_id']));
/**
 * Returns an array of the relation types that can have the given entity as
 * as an endpoint.
 *
 * @param $entity_type
 *   The entity type of the endpoint.
 * @param $bundle
 *   The bundle of the endpoint.
 * @param $endpoint
 *   (optional) the type of endpoint. This is only used for directional
 *   relation types. Possible options are 'source', 'target', or 'both'.
 *
 * @return
 *   An array of relation types, keyed by relation_type.
 */
function relation_get_available_types($entity_type, $bundle, $endpoint = 'source') {
  $bundle_key = $entity_type . ':' . $bundle;
  $all_bundle_key = $entity_type . ':*';
  $relation_types = relation_get_types();

  foreach ($relation_types as $type => $relation_type) {
    $available = FALSE;
    if ($endpoint == 'source' || $endpoint == 'both') {
      if (in_array($bundle_key, $relation_type->source_bundles) || in_array($all_bundle_key, $relation_type->source_bundles)) {
    if ($endpoint == 'target' || $endpoint == 'both') {
      if (in_array($bundle_key, $relation_type->target_bundles) || in_array($all_bundle_key, $relation_type->target_bundles)) {
        $available = TRUE;
      }
    }
    if (!$available) {
      unset($relation_types[$type]);
    }
  }
  return $relation_types;
}
 * Implements hook_entity_delete().
 */
function relation_entity_delete($entity, $entity_type) {
  list($id) = entity_extract_ids($entity_type, $entity);
  $relations = relation_query($entity_type, $id)->execute();
  relation_multiple_delete(array_keys($relations));
  if ($relations) {
    drupal_set_message(t('Relations !relations have been deleted.', array('!relations' => implode(', ', array_keys($relations)))));
  }

/**
 * Implements hook_views_api().
 */
function relation_views_api() {
  return array(
    'api' => 3.0,
    'path' => drupal_get_path('module', 'relation') . '/views',

/**
 * Gets the label of the relation type of the given relation
 *
 * @param $relation
 *   A relation object.
 * @param $reverse
 *   optional: whether to get the reverse label (boolean).
 *
 * @return
 *   The label of the relation type.
 */
function relation_get_type_label($relation, $reverse = FALSE) {
  $type = relation_type_load($relation->relation_type);
  if ($type->directional && $reverse) {
    return $type->reverse_label;
  }
  else {
    return $type->label;
  }

/**
 * Implements hook_theme().
 */
function relation_theme() {
  $theme = array(
    // relation.admin.inc.
    'relation_admin_content' => array(
      'variables' => array('relations' => NULL),
      'file' => 'relation.admin.inc',
    ),
  );
  return $theme;
}