You are here

relation_add.module in Relation add 7

Relation Add module file.

File

relation_add.module
View source
<?php

/**
 * @file
 * Relation Add module file.
 */

/**
 * Implements hook_permission().
 */
function relation_add_permission() {
  $return = array();
  $return['relation add endpoint autocomplete access'] = array(
    'title' => t('Endpoint autocomplete access'),
    'description' => t('Endpoint autocomplete menu callback access.'),
  );
  return $return;
}

/**
 * Implements hook_menu().
 */
function relation_add_menu() {
  $items['relation_add/autocomplete/%'] = array(
    'access arguments' => array(
      'relation add endpoint autocomplete access',
    ),
    'page callback' => 'relation_add_autocomplete',
    'page arguments' => array(
      2,
      3,
      4,
      5,
      6,
    ),
    'type' => MENU_CALLBACK,
  );
  return $items;
}

/**
 * Autocomplete page for listing entities appropriate for a given relation type.
 *
 * @param string $type
 *   The relation type to search for endpoints for.
 * @param string $direction
 *   The direction for which to allow endpoint bundles.
 * @param string $field
 *   Entity type, field and entity bundle data.
 * @param string $target_bundles
 *   The target bundles in which to search for endpoints.
 * @param string $string
 *   The string for which the search through entity labels will be run.
 */
function relation_add_autocomplete($type = '', $direction = 'target', $field = 'none', $target_bundles = 'all', $string = '') {
  if (empty($type) || empty($direction) || empty($string)) {
    exit;
  }

  // Removing the :reverse suffix if exists.
  $type_array = explode(':', $type);
  $type = $type_array[0];
  $entity_infos = entity_get_info();
  $relation_type = relation_type_load($type);
  $entity_bundles = array();
  $instance = array();
  if ($field !== 'none') {
    list($entity_type, $field_name, $bundle) = explode('-', $field);
    $instance = field_info_instance($entity_type, $field_name, $bundle);
  }

  // Use source bundles unless relation type is directional and we're looking
  // in the forward direction.
  $direction = $relation_type->directional && $direction == 'target' ? 'target_bundles' : 'source_bundles';
  if ($direction == 'target_bundles' && $target_bundles !== 'all') {
    $target_bundles_ar = explode('-', $target_bundles);
    foreach ($target_bundles_ar as $entity_bundle) {
      list($entity_type, $bundle) = explode(':', $entity_bundle, 2);
      $entity_bundles[$entity_type][] = $bundle;
    }
  }
  else {
    foreach ($relation_type->{$direction} as $entity_bundle) {
      list($entity_type, $bundle) = explode(':', $entity_bundle, 2);
      $entity_bundles[$entity_type][] = $bundle;
    }
  }
  if (module_exists('relation_add_views') && isset($instance['widget']['settings']['views']) && !empty($instance['widget']['settings']['views'])) {
    $suggestions = relation_add_views_autocomplet($field, $instance, $type, $direction, $target_bundles, $string);
  }
  else {

    // Get about 12, rounded up.
    $limit = ceil(12 / count(array_keys($entity_bundles)));
    $suggestions = array();
    foreach ($entity_bundles as $entity_type => $bundles) {

      // Get the name of the column in the base table for the entity type.
      if ($entity_type == 'user') {

        // Special case for users.
        $label_key = 'name';
      }
      elseif (isset($entity_infos[$entity_type]['entity keys']['label'])) {
        $label_key = $entity_infos[$entity_type]['entity keys']['label'];
      }
      else {
        if ('redhen_' == substr($entity_type, 0, 7)) {

          // Special case for users.
          $label_key = 'label';
        }
        elseif ('field_collection_item' == $entity_type) {
          $label_key = $entity_infos[$entity_type]['entity keys']['id'];
        }
        else {

          // Can't find a label to search over, give up.
          continue;
        }
      }
      $query = new EntityFieldQuery();
      $query
        ->entityCondition('entity_type', $entity_type);
      if (!empty($instance) && isset($instance['widget']['settings']['relation_endpoint_search_by_id']) && $instance['widget']['settings']['relation_endpoint_search_by_id'] && preg_match("/^[0-9]+\$/", $string)) {

        // We are most likely searching for an entity ID.
        $query
          ->entityCondition('entity_id', (int) $string);
      }
      else {
        if ('redhen_contact' == $entity_type) {
          $query
            ->addTag('redhen_contact_label');
        }
        $query
          ->propertyCondition($label_key, $string, 'CONTAINS');
      }
      $query
        ->range(0, $limit);
      if (!in_array('*', $bundles) && 'taxonomy_term' != $entity_type && 'user' != $entity_type) {
        $query
          ->entityCondition('bundle', $bundles, 'IN');
      }
      elseif (!in_array('*', $bundles) && $entity_type == 'taxonomy_term') {
        $vocabularies = taxonomy_vocabulary_load_multiple(NULL, array(
          'machine_name' => $bundles,
        ));
        $bundles = array_keys($vocabularies);
        $query
          ->propertyCondition('vid', $bundles, 'IN');
      }
      $query
        ->addTag('efq_relation_add_autocomplete');
      if ($results = $query
        ->execute()) {
        foreach (array_keys($results[$entity_type]) as $id) {
          $entities = entity_load($entity_type, array(
            $id,
          ));
          $entity = reset($entities);
          $label = entity_label($entity_type, $entity);
          $bundle = '';
          if (!empty($instance) && $instance['widget']['settings']['relation_endpoint_bundle_display']) {
            list(, , $bundle) = entity_extract_ids($entity_type, $entity);
            if (!empty($bundle)) {
              $bundle = ' - (' . $bundle . ')';
            }
          }
          if (!empty($instance) && $instance['widget']['settings']['relation_endpoint_iso_language_codes'] && isset($entity->language)) {
            $suggestions[$label . $bundle . ' [' . $entity_type . ':' . $id . ']'] = '[' . $entity->language . '] ' . $label . $bundle . ' [' . $entity_type . ':' . $id . ']';
          }
          else {
            $suggestions[$label . $bundle . ' [' . $entity_type . ':' . $id . ']'] = $label . $bundle . ' [' . $entity_type . ':' . $id . ']';
          }
        }
      }
    }
  }
  drupal_json_output($suggestions);
}

/**
 * Implements hook_field_info().
 */
function relation_add_field_info() {
  return array(
    'relation_add' => array(
      'label' => t('Relation add'),
      'description' => t('Stores relationships between entities.'),
      'settings' => array(),
      'default_widget' => 'relation_add',
      'default_formatter' => 'relation_add_endpoints_and_fields',
      'instance_settings' => array(
        'relation_type' => '',
      ),
    ),
  );
}

/**
 * Implements hook_field_is_empty().
 */
function relation_add_field_is_empty($item, $field) {
  if (empty($item['relation_options'])) {
    return TRUE;
  }
  if (!isset($item['relation_options']['rid']) && !isset($item['rid'])) {
    if (isset($item['relation_options']['targets'])) {
      $targets_flip = array_flip($item['relation_options']['targets']);
      if (count($targets_flip) < 2) {
        $target_key = array_shift($targets_flip);
        if (empty($item['relation_options']['targets'][$target_key])) {
          return TRUE;
        }
      }
    }
    else {
      return relation_add_item_is_empty($item['relation_options']);
    }
  }
  return FALSE;
}

/**
 * Determines whether an item is empty.
 */
function relation_add_item_is_empty($fields) {
  $is_empty = TRUE;
  foreach ($fields as $field_name => $items) {
    $field = field_info_field($field_name);

    // Determine the list of languages to iterate on.
    $languages = field_available_languages('relation', $field);
    foreach ($languages as $langcode) {
      if (!empty($items[$langcode])) {

        // If at least one relation-field is not empty; the
        // relation item is not empty.
        foreach ($items[$langcode] as $field_item) {
          if (!module_invoke($field['module'], 'field_is_empty', $field_item, $field)) {
            $is_empty = FALSE;
          }
        }
      }
    }
  }
  return $is_empty;
}

/**
 * Implements hook_field_load().
 */
function relation_add_field_load($entity_type, $entities, $field, $instances, $langcode, &$items, $age) {
  $cache =& drupal_static(__FUNCTION__);
  $types = relation_get_types();
  foreach ($entities as $id => $entity) {
    $cache[$entity_type][$instances[$id]['bundle']]['type_settings'] = array();
    $cache[$entity_type][$instances[$id]['bundle']]['relation_types'] = array();
    if (isset($cache[$entity_type][$instances[$id]['bundle']]) && !empty($cache[$entity_type][$instances[$id]['bundle']]['type_settings'])) {
      $type_settings = $cache[$entity_type][$instances[$id]['bundle']]['type_settings'];
    }
    else {
      if (!empty($instances[$id]['settings']['relation_type'])) {
        $type_settings = $instances[$id]['settings']['relation_type'];
        $cache[$entity_type][$instances[$id]['bundle']]['type_settings'] = $type_settings;
      }
      else {
        $type_settings = array();
        $source_types = relation_get_available_types($entity_type, $instances[$id]['bundle']);
        foreach ($source_types as $r_type_id => $r_type) {
          $type_settings[] = $r_type_id;
        }
        $reverse_types = relation_get_available_types($entity_type, $instances[$id]['bundle'], 'target');
        foreach ($reverse_types as $r_type_id => $r_type) {
          $type_settings[] = $r_type_id . ':reverse';
        }
        $cache[$entity_type][$instances[$id]['bundle']]['type_settings'] = $type_settings;
      }
    }
    $target_bundles = array();
    if (isset($instances[$id]['settings']['relation_target_bundles']) && !empty($instances[$id]['settings']['relation_target_bundles'])) {
      foreach ($instances[$id]['settings']['relation_target_bundles'] as $target_bundle) {
        $target_bundle_array = explode(':', $target_bundle);
        $target_bundles[$target_bundle_array[0]][$target_bundle_array[1]] = $target_bundle_array[1];
      }
    }
    else {
      $target_bundles = 'all';
    }
    if (!empty($type_settings)) {
      if (isset($cache[$entity_type][$instances[$id]['bundle']]) && !empty($cache[$entity_type][$instances[$id]['bundle']]['relation_types'])) {
        $relation_types = $cache[$entity_type][$instances[$id]['bundle']]['relation_types'];
      }
      else {
        foreach ($type_settings as $type) {
          $type_array = explode(':', $type);
          $relation_types[$type_array[0]] = $type_array[0];
        }
      }
      $query = relation_query($entity_type, $id);
      if ($relation_types) {
        $query
          ->entityCondition('bundle', $relation_types, 'IN');
      }
      $relation_ids = array_keys($query
        ->execute());

      // Who knows why but field does not like
      // if the delta does not start at 0...
      $items[$id] = array();
      foreach (entity_load('relation', $relation_ids) as $relation) {
        if (!(is_string($target_bundles) && 'all' == $target_bundles)) {
          if ($relation) {

            // Filter out relation items that contain
            // unwanted endpoint bundle types.
            foreach ($relation->endpoints[LANGUAGE_NONE] as $endpoint) {
              if (!($endpoint['entity_type'] == $entity_type && $endpoint['entity_id'] == $id)) {
                if (isset($target_bundles[$endpoint['entity_type']]) && is_array($target_bundles[$endpoint['entity_type']])) {
                  $query = new EntityFieldQuery();
                  $query
                    ->entityCondition('entity_type', $endpoint['entity_type'])
                    ->entityCondition('bundle', $target_bundles[$endpoint['entity_type']], 'IN')
                    ->entityCondition('entity_id', $endpoint['entity_id']);
                  $result = $query
                    ->execute();
                  if (empty($result)) {
                    unset($relation);
                  }
                }
                else {
                  unset($relation);
                }
              }
            }
          }
        }

        // Only add items when they are either not directional,
        // or have their relation type in the field settings.
        if (isset($relation)) {
          $directional = $types[$relation->relation_type]->directional;
          $relation_reverse = TRUE;
          if ($relation->endpoints[LANGUAGE_NONE][0]['entity_id'] == $id && $relation->endpoints[LANGUAGE_NONE][0]['entity_type'] == $entity_type) {
            $relation_reverse = FALSE;
          }
          if (!$directional || $relation_reverse && in_array($relation->relation_type . ':reverse', $type_settings) || !$relation_reverse && in_array($relation->relation_type, $type_settings)) {
            $item = (array) $relation;
            $item['my_entity_id'] = $id;
            $items[$id][] = $item;
          }
        }
      }
    }
  }
}

/**
 * Implements hook_field_instance_settings_form().
 */
function relation_add_field_instance_settings_form($field, $instance) {

  // The field settings infrastructure is not AJAX enabled by default,
  // because it doesn't pass over the $form_state.
  // Build the whole form into a #process in which we actually have access
  // to the form state.
  $form = array(
    '#type' => 'container',
    '#process' => array(
      '_relation_add_field_instance_settings_form',
    ),
    '#element_validate' => array(
      '_relation_add_field_instance_settings_validate',
    ),
    '#field' => $field,
    '#instance' => $instance,
  );
  return $form;
}

/**
 * Process callback for relation_add_field_instance_settings_form().
 */
function _relation_add_field_instance_settings_form($form, $form_state) {
  $field = isset($form_state['relation_add']['field']) ? $form_state['relation_add']['field'] : $form['#field'];
  $instance = isset($form_state['relation_add']['instance']) ? $form_state['relation_add']['instance'] : $form['#instance'];
  $relation_types = relation_get_types();
  $bundle_key = $instance['entity_type'] . ':' . $instance['bundle'];
  $bundle_wildcard_key = $instance['entity_type'] . ':*';
  $options = array();
  foreach ($relation_types as $relation_type => $relation_type_data) {
    foreach ($relation_type_data->source_bundles as $relation_bundle_key) {
      if ($bundle_key == $relation_bundle_key || $bundle_wildcard_key == $relation_bundle_key) {
        $options[$relation_type] = $relation_type_data->label;
      }
    }
    foreach ($relation_type_data->target_bundles as $relation_bundle_key) {
      if ($bundle_key == $relation_bundle_key || $bundle_wildcard_key == $relation_bundle_key) {
        $options[$relation_type . ':reverse'] = t('@relation_label (reverse)', array(
          '@relation_label' => $relation_type_data->label,
        ));
      }
    }
  }
  ksort($options);
  $form['relation_type'] = array(
    '#type' => 'select',
    '#title' => t('Relation types'),
    '#description' => t('Select all the relation types you want to display in the relation add field. Only relation types applicable to this entity bundle are shown here. If no relation_types are selected, relations of all types will be displayed.'),
    '#default_value' => $instance['settings']['relation_type'],
    '#options' => $options,
    '#multiple' => TRUE,
    '#ajax' => array(
      'event' => 'change',
      'callback' => 'relation_add_settings_ajax',
      'wrapper' => 'relation_target_bundles',
    ),
  );
  $relation_selected_types = !empty($instance['settings']['relation_type']) ? $instance['settings']['relation_type'] : array();
  $relation_relevant_bundles = array();
  foreach ($relation_selected_types as $rel_type) {
    $type_array = explode(':', $rel_type);
    $relation_type = relation_type_load($type_array[0]);
    if (isset($type_array[1]) && $type_array[1] == 'reverse') {
      foreach ($relation_type->source_bundles as $source_bundle) {
        $relation_relevant_bundles[$source_bundle] = $source_bundle;
      }
    }
    else {
      foreach ($relation_type->target_bundles as $target_bundles) {
        $relation_relevant_bundles[$target_bundles] = $target_bundles;
      }
    }
  }
  $entity_infos = entity_get_info();
  $relation_bundles = array();
  $counter = 0;
  foreach ($relation_relevant_bundles as $relation_bundle) {
    list($entity_type, $bundle) = explode(':', $relation_bundle);
    if ('*' == $bundle) {
      foreach ($entity_infos[$entity_type]['bundles'] as $bundle_name => $bundle) {
        $relation_bundles[$entity_infos[$entity_type]['label']][$entity_type . ':' . $bundle_name] = $bundle['label'];
        ++$counter;
      }
    }
    else {
      if (isset($entity_infos[$entity_type]['bundles'][$bundle])) {
        $relation_bundles[$entity_infos[$entity_type]['label']][$entity_type . ':' . $bundle] = $entity_infos[$entity_type]['bundles'][$bundle]['label'];
        ++$counter;
      }
    }
  }
  $form['relation_target_bundles'] = array(
    '#type' => 'select',
    '#title' => t('Target bundles'),
    '#options' => $relation_bundles,
    '#size' => max(5, $counter),
    '#default_value' => isset($instance['settings']['relation_target_bundles']) ? $instance['settings']['relation_target_bundles'] : '',
    '#multiple' => TRUE,
    '#description' => t("Select the target bundle's type. Useful if you want to create separate fields for different possible target bundles."),
    '#prefix' => '<div id="relation-add-target-bundles">',
    '#suffix' => '</div>',
  );
  if (count($instance['settings']['relation_type']) > 1) {

    // The default value can only be set
    // if there are more than 1 relation types.
    $form['fieldset'] = array(
      '#type' => 'fieldset',
      '#title' => t('Default value'),
      '#collapsible' => FALSE,
    );
    $form['fieldset']['content'] = array(
      '#pre' => '<p>',
      '#markup' => t('The default value for this field, used when creating new content.'),
      '#suffix' => '</p>',
    );
    foreach ($instance['settings']['relation_type'] as $rel_type) {
      $type_array = explode(':', $rel_type);
      $relation_type = relation_type_load($type_array[0]);
      $types[$rel_type] = isset($type_array[1]) ? $relation_type->reverse_label : $relation_type->label;
    }
    $form['fieldset']['default_value'] = array(
      '#type' => 'select',
      '#title' => t('Relation type'),
      '#options' => $types,
      '#default_value' => isset($instance['settings']['fieldset']['default_value']) ? $instance['settings']['fieldset']['default_value'] : '',
      '#empty_value' => '',
      '#empty_option' => t('Select a relation type'),
    );
  }
  return $form;
}

/**
 * Validate for relation_add_field_instance_settings_form().
 */
function _relation_add_field_instance_settings_validate($form, &$form_state) {

  // Store the new values in the form state.
  $instance = $form['#instance'];
  if (isset($form_state['values']['instance'])) {
    $instance = $form_state['values']['instance'];
  }
  $form_state['relation_add']['instance'] = $instance;
}

/**
 * Ajax callback for the bundle type settings form.
 *
 * @see relation_add_field_instance_settings_form()
 */
function relation_add_settings_ajax($form, $form_state) {
  return array(
    '#type' => 'ajax',
    '#commands' => array(
      ajax_command_replace("#relation-add-target-bundles", drupal_render($form['instance']['settings']['relation_target_bundles'])),
    ),
  );
}

/**
 * Implements hook_field_widget_settings_form().
 */
function relation_add_field_widget_settings_form($field, $instance) {
  $widget = $instance['widget'];
  $settings = $widget['settings'];
  $options = array(
    'endpoint' => 'Endpoint field',
    'custom' => 'Custom',
    'none' => 'None',
  );
  $form['relation_endpoint_label'] = array(
    '#type' => 'select',
    '#title' => t('Endpoint label'),
    '#default_value' => isset($settings['relation_endpoint_label']) ? $settings['relation_endpoint_label'] : '',
    '#options' => $options,
    '#required' => TRUE,
    '#weight' => 5,
  );
  $form['relation_endpoint_custom_label'] = array(
    '#type' => 'textfield',
    '#title' => t('Label'),
    '#default_value' => isset($settings['relation_endpoint_custom_label']) ? $settings['relation_endpoint_custom_label'] : '',
    '#states' => array(
      'visible' => array(
        ':input[name="instance[widget][settings][relation_endpoint_label]"]' => array(
          'value' => 'custom',
        ),
      ),
    ),
    '#weight' => 5,
  );
  $form['relation_endpoint_label_delta'] = array(
    '#type' => 'checkbox',
    '#title' => t('Adding endpoint delta to the label'),
    '#default_value' => isset($settings['relation_endpoint_label_delta']) ? $settings['relation_endpoint_label_delta'] : FALSE,
    '#weight' => 5,
  );
  $form['relation_endpoint_search_by_id'] = array(
    '#type' => 'checkbox',
    '#title' => t('Search endpoints by entity id if the input contains only numbers'),
    '#default_value' => isset($settings['relation_endpoint_search_by_id']) ? $settings['relation_endpoint_search_by_id'] : FALSE,
    '#weight' => 6,
  );
  $form['relation_endpoint_bundle_display'] = array(
    '#type' => 'checkbox',
    '#title' => t('Display bundle'),
    '#description' => t('Display bundle data in the ajax label.'),
    '#default_value' => isset($settings['relation_endpoint_bundle_display']) ? $settings['relation_endpoint_bundle_display'] : FALSE,
    '#weight' => 7,
  );
  $form['relation_endpoint_iso_language_codes'] = array(
    '#type' => 'checkbox',
    '#title' => t('Show ISO Language Codes for endpoints'),
    '#default_value' => isset($settings['relation_endpoint_iso_language_codes']) ? $settings['relation_endpoint_iso_language_codes'] : FALSE,
    '#description' => t('Add ISO Language Codes for the endpoints'),
    '#weight' => 8,
  );
  if (module_exists('relation_add_views')) {
    relation_add_views_extra_field_widget_settings_form($field, $instance, $form);
  }
  return $form;
}

/**
 * Implements hook_field_widget_info().
 */
function relation_add_field_widget_info() {
  return array(
    'relation_add' => array(
      'label' => t('Relation add widget'),
      'field types' => array(
        'relation_add',
      ),
      'behaviors' => array(
        // To tell field API to not display the base default value widget
        // we provide our own.
        'default value' => FIELD_BEHAVIOR_NONE,
      ),
    ),
  );
}

/**
 * Implements hook_field_widget_form().
 */
function relation_add_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
  $item = isset($items[$delta]) ? $items[$delta] : array();
  $types = array();
  if (empty($instance['settings']['relation_type'])) {
    $relation_types = relation_get_available_types($instance['entity_type'], $instance['bundle']);
    $reverse_types = relation_get_available_types($instance['entity_type'], $instance['bundle'], 'target');
    if (empty($relation_types) && empty($reverse_types)) {
      return $element;
    }

    // Relation type selector. On change, rest of form is loaded via ajax.
    foreach ($relation_types as $relation_type) {
      $types[$relation_type->relation_type] = $relation_type->label;
    }
    foreach ($reverse_types as $relation_type) {

      // Directional n-ary relations are f@*#ing stupid.
      if ($relation_type->directional && $relation_type->max_arity == 2) {

        // Machine name doesn't have colons, so we add a suffix for reverse
        // relations, which we explode off later.
        $types[$relation_type->relation_type . ':reverse'] = $relation_type->reverse_label ? $relation_type->reverse_label : 'reverse ' . $relation_type->reverse_label;
      }
    }
  }
  elseif (count($instance['settings']['relation_type']) > 1) {
    foreach ($instance['settings']['relation_type'] as $rel_type) {
      $type_array = explode(':', $rel_type);
      $relation_type = relation_type_load($type_array[0]);
      $types[$rel_type] = !isset($type_array[1]) ? $relation_type->label : $relation_type->reverse_label;
    }
  }
  ksort($types);
  $wrapper = str_replace('_', '-', $instance['field_name']) . '-relation-add-options-' . $delta;
  $form_element = array(
    '#tree' => TRUE,
  );
  if (!empty($types)) {
    if (isset($item['relation_type'])) {
      if (isset($types[$item['relation_type'] . ':reverse']) && $item['endpoints'][LANGUAGE_NONE][0]['entity_id'] != $item['my_entity_id']) {
        $type = $item['relation_type'] . ':reverse';
        $relation_reverse = TRUE;
      }
      elseif (isset($types[$item['relation_type']])) {
        $type = $item['relation_type'];
        $relation_reverse = FALSE;
      }
    }
    elseif (isset($instance['settings']['fieldset']['default_value'])) {
      $type = $instance['settings']['fieldset']['default_value'];
      $type_array = explode(':', $type);
      $relation_reverse = isset($type_array[1]) && $type_array[1] == 'reverse';
    }
    $form_element['relation_type'] = array(
      '#type' => 'select',
      '#title' => t('Relation type'),
      '#options' => $types,
      '#default_value' => isset($type) ? $type : NULL,
      '#empty_value' => '',
      '#empty_option' => t('Select a relation type'),
      '#ajax' => array(
        'callback' => 'relation_add_widget_ajax',
        'wrapper' => $wrapper,
        'method' => 'replace',
        'effect' => 'fade',
      ),
    );
  }
  else {
    $form_element['relation_type'] = array(
      '#type' => 'value',
      '#value' => reset($instance['settings']['relation_type']),
    );
  }
  if (isset($form_state['triggering_element']['#ajax'])) {
    if (!empty($form_state['values'][$field['field_name']][$langcode][$delta]['relation_type'])) {
      $form_state_relation_type = $form_state['values'][$field['field_name']][$langcode][$delta]['relation_type'];
    }
    elseif (!empty($form_state['input'][$field['field_name']][$langcode][$delta]['relation_type'])) {
      $form_state_relation_type = $form_state['input'][$field['field_name']][$langcode][$delta]['relation_type'];
    }
    if (isset($form_state_relation_type)) {

      // Remove ':reverse' suffix if it exists, and set reverse flag.
      $type_array = explode(':', $form_state_relation_type);
      $type = $type_array[0];
      $relation_reverse = isset($type_array[1]) && $type_array[1] == 'reverse';
    }
  }
  if (empty($types)) {
    $type_array = explode(':', reset($instance['settings']['relation_type']));
    $type = $type_array[0];
    $relation_reverse = isset($type_array[1]) && $type_array[1] == 'reverse';
  }
  $field_parents = $element['#field_parents'];
  $field_name = $element['#field_name'];
  $language = $element['#language'];
  $parents = array_merge($field_parents, array(
    $field_name,
    $language,
    $delta,
  ));
  $parents[] = 'relation_options';
  $form_element['relation_options'] = array(
    '#parents' => $parents,
    '#prefix' => '<div id="' . $wrapper . '">',
    '#suffix' => '</div>',
  );
  $add_required_validation = FALSE;
  if (!empty($type)) {

    // Get all fields.
    $info_rel_instances = field_info_instances('relation', $type);

    // Remove endpoint field.
    unset($info_rel_instances['endpoins']);

    // Find required fields.
    if (!empty($info_rel_instances)) {
      foreach ($info_rel_instances as $info_rel_instance) {
        if (isset($info_rel_instance['required']) && $info_rel_instance['required']) {
          $add_required_validation = TRUE;
          break;
        }
      }
    }
    if (isset($item) && !empty($item)) {
      $relation = (object) $item;

      // $relation->relation_type can also have the :reverse suffix here so we
      // have to remove it.
      $type_array = explode(':', $relation->relation_type);
      $relation_type = relation_type_load($type_array[0]);
      $default_targets = array();
      $i = 2;
      if (!empty($item['endpoints'][LANGUAGE_NONE])) {
        foreach ($item['endpoints'][LANGUAGE_NONE] as $endpoint) {
          $entities = entity_load($endpoint['entity_type'], array(
            $endpoint['entity_id'],
          ));
          $entity = reset($entities);
          $label = entity_label($endpoint['entity_type'], $entity);
          $bundle = '';
          if (isset($instance['widget']['settings']['relation_endpoint_bundle_display'])) {
            list(, , $bundle) = entity_extract_ids($endpoint['entity_type'], $entity);
            if (!empty($bundle)) {
              $bundle = ' - (' . $bundle . ')';
            }
          }
          $entity_label = $label . $bundle . ' [' . $endpoint['entity_type'] . ':' . $endpoint['entity_id'] . ']';
          if ($endpoint['entity_id'] == $item['my_entity_id'] && $endpoint['entity_type'] == $instance['entity_type']) {
            $default_targets[1] = $entity_label;
          }
          else {
            $default_targets[$i] = $entity_label;
            $i++;
          }
        }
      }
    }
    else {

      // $type can also have the :reverse suffix here so we have to remove it.
      $type_array = explode(':', $type);
      $relation_type = relation_type_load($type_array[0]);
      $relation = (object) relation_create($type_array[0], array());
    }

    // Create one autocomplete for each endpoint beyond the first.
    $direction = $relation_reverse ? '/source' : '/target';
    $endpoint_title = '';
    switch ($instance['widget']['settings']['relation_endpoint_label']) {
      case 'endpoint':
        $relation_instance = field_info_instance('relation', 'endpoints', $relation_type->relation_type);

        // @codingStandardsIgnoreStart
        $endpoint_title = t(check_plain($relation_instance['label']));

        // @codingStandardsIgnoreEnd
        break;
      case 'custom':

        // @codingStandardsIgnoreStart
        $endpoint_title = t($instance['widget']['settings']['relation_endpoint_custom_label']);

        // @codingStandardsIgnoreEnd
        break;
    }
    $target_bundles = 'all';
    if (!empty($instance['settings']['relation_target_bundles'])) {
      $target_bundles = implode('-', $instance['settings']['relation_target_bundles']);
    }
    if ($relation_type->max_arity == 0) {
      if (isset($form_state['input'][$instance['field_name']][$element['#language']][$delta]['relation_options']['targets'])) {
        $max_arity = count($form_state['input'][$instance['field_name']][$element['#language']][$delta]['relation_options']['targets']) + 1;

        // Click "Add" button.
        if ('targets_add_' . $instance['field_name'] . '_' . $delta == $form_state['clicked_button']['#name']) {
          $max_arity += 1;
        }
      }
      elseif (isset($default_targets)) {
        $max_arity = count($default_targets) + 1;
      }
      else {
        $max_arity = 2;
      }
    }
    else {
      $max_arity = $relation_type->max_arity;
    }
    $wrapper_targets = str_replace('_', '-', $instance['field_name']) . '-relation-add-targets-' . $delta;
    $form_element['relation_options']['targets'] = array(
      '#prefix' => '<div id="' . $wrapper_targets . '">',
      '#suffix' => '</div>',
    );
    for ($i = 2; $i <= $max_arity; $i++) {
      $endpoint_title .= $instance['widget']['settings']['relation_endpoint_label_delta'] ? ' ' . ($i - 1) : '';
      $form_element['relation_options']['targets']['target_' . $i] = array(
        '#type' => 'textfield',
        '#maxlength' => 320,
        '#title' => $endpoint_title,
        '#default_value' => isset($default_targets[$i]) ? $default_targets[$i] : '',
        '#autocomplete_path' => 'relation_add/autocomplete/' . $type . $direction . '/' . $instance['entity_type'] . '-' . $instance['field_name'] . '-' . $instance['bundle'] . '/' . $target_bundles,
      );
    }
    if ($relation_type->max_arity == 0) {
      $form_element['relation_options']['targets_add'] = array(
        '#type' => 'button',
        '#value' => t('Add'),
        '#name' => 'targets_add_' . $instance['field_name'] . '_' . $delta,
        '#ajax' => array(
          'event' => 'click',
          'callback' => 'relation_add_widget_ajax',
          'wrapper' => $wrapper_targets,
          'method' => 'replace',
          'effect' => 'fade',
        ),
        '#submit' => array(
          'relation_add_widget_ajax',
        ),
        '#limit_validation_errors' => array(),
      );
    }
    if (isset($item['my_entity_id'])) {
      $form_element['relation_options']['rid'] = array(
        '#type' => 'value',
        '#value' => $item['rid'],
      );
    }
    field_attach_form('relation', $relation, $form_element['relation_options'], $form_state);
    $form_element['delete'] = array(
      '#type' => 'checkbox',
      '#title' => t('Delete'),
    );
    $form_element['relation_options']['targets']['#weight'] = $form_element['relation_options']['endpoints']['#weight'];
    if (isset($form_element['relation_options']['targets_add'])) {
      $form_element['relation_options']['targets_add']['#weight'] = $form_element['relation_options']['endpoints']['#weight'] + 1;
    }
    unset($form_element['relation_options']['endpoints']);
  }
  if ($field['cardinality'] == 1) {
    $form_element['relation_options']['#prefix'] = '<div class="relation-add-wrapper">' . $form_element['relation_options']['#prefix'];
    $form_element['delete']['#suffix'] = '</div>';
    $form_element['label'] = $element + array(
      '#type' => 'item',
    );
  }

  // Add element validate function if relation has required field.
  if ($add_required_validation) {
    $form_element['#element_validate'] = array(
      'relation_add_field_required_validate',
    );
  }
  return $element + $form_element;
}

/**
 * AJAX callback for widget form.
 */
function relation_add_widget_ajax($form, $form_state) {
  $path = $form_state['triggering_element']['#parents'];
  $field_name = array_shift($path);
  $language = array_shift($path);
  $item = array_shift($path);
  $type = array_shift($path);
  switch ($type) {
    case 'relation_type':
      return $form[$field_name][$language][$item]['relation_options'];
    case 'relation_options':
      if (!empty($path)) {
        $button = array_shift($path);
        if ('targets_add' == $button) {
          return $form[$field_name][$language][$item]['relation_options']['targets'];
        }
      }
      break;
  }
}

/**
 * It is called when the relation has required fields.
 *
 * @param array $element
 *   Form element.
 * @param array $form_state
 *   Form state value.
 */
function relation_add_field_required_validate($element, &$form_state) {
  if (is_array($element)) {
    $form_path = implode('][', $element['#parents']) . '][relation_options][';
    $form_errors = form_get_errors();

    // Clear form errors.
    $drupal_errors = drupal_get_messages('error');
    form_clear_error();
    if (isset($drupal_errors['error']) && !empty($drupal_errors['error'])) {
      foreach ($drupal_errors['error'] as $key => $error) {
        if (in_array($error, $form_errors)) {

          // Unset form errors.
          unset($drupal_errors['error'][$key]);
        }
      }

      // Rebuild drupal errors.
      foreach ($drupal_errors['error'] as $message) {
        drupal_set_message($message, 'error');
      }
    }
    if (!empty($form_errors)) {
      foreach ($form_errors as $error_path => $error_msg) {
        if (strpos($error_path, $form_path) === 0) {
          $element_paths = explode('][', $error_path);
          $element_paths = array_slice($element_paths, count($element['#parents']));
          $empty_endpoints = TRUE;
          if ('targets' != $element_paths[1]) {

            // Find the field which has an error.
            $sub_element =& $element;
            foreach ($element_paths as $element_path) {
              if (isset($sub_element[$element_path])) {
                $sub_element =& $sub_element[$element_path];
              }
            }
            if (isset($sub_element['#required']) && $sub_element['#required']) {
              $required_msg = t('!name field is required.', array(
                '!name' => $sub_element['#title'],
              ));
              if ($required_msg == $error_msg) {
                $field_values = drupal_array_get_nested_value($form_state['values'], $element['#parents']);
                foreach ($field_values['relation_options']['targets'] as $target) {
                  if (!empty($target)) {
                    $empty_endpoints = FALSE;
                    break;
                  }
                }
              }
              if ($empty_endpoints) {
                continue;
              }
            }
          }
        }
        form_set_error($error_path, $error_msg);
      }
    }
  }
}

/**
 * Implements hook_field_insert().
 */
function relation_add_field_insert($entity_type, $entity, $field, $instance, $langcode, &$items) {
  relation_add_field_update($entity_type, $entity, $field, $instance, $langcode, $items);
}

/**
 * Implements hook_field_update().
 */
function relation_add_field_update($entity_type, $entity, $field, $instance, $langcode, &$items) {
  foreach ($items as $key => $item) {
    if (isset($item['relation_options'])) {
      if (isset($item['delete']) && $item['delete']) {
        if (isset($item['relation_options']['rid']) && $item['relation_options']['rid']) {
          relation_delete($item['relation_options']['rid']);
          cache_clear_all('field', 'cache_field', TRUE);
        }
        continue;
      }
      $type_array = explode(':', $item['relation_type']);
      $type = $type_array[0];
      $relation_reverse = isset($type_array[1]) && $type_array[1] == 'reverse';
      $entity_label = entity_label($entity_type, $entity);
      list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity);
      $relation_add = $entity_label . ' [' . $entity_type . ':' . $id . ']';
      $entity_strings = array();
      if (isset($item['relation_options']['targets'])) {
        $targets =& $item['relation_options']['targets'];
        for ($i = 2; $i; $i++) {
          if (isset($targets['target_' . $i]) && !empty($targets['target_' . $i])) {
            $entity_strings[] = $targets['target_' . $i];
          }
          else {

            // Break loop.
            $i = FALSE;
          }
        }
      }
      if (!isset($item['relation_options']['targets']) || is_array($entity_strings) && count($entity_strings)) {

        // Add the current entity to the endpoints array.
        if ($relation_reverse) {

          // For reverse relations, add the "current entity"
          // to the end of the array, else to the start.
          array_push($entity_strings, $relation_add);
        }
        else {
          array_unshift($entity_strings, $relation_add);
        }
        $entity_keys = array();
        $i = 0;
        foreach ($entity_strings as $r_index => $entity_string) {
          $matches = array();
          preg_match('/(.*)\\[([\\w\\d]+):(\\d+)\\]/', $entity_string, $matches);
          if ($matches) {
            $entity_keys[] = array(
              'entity_label' => $matches[1],
              'entity_type' => $matches[2],
              'entity_id' => $matches[3],
              'r_index' => $r_index,
            );
            if ('redhen_' == substr($entity_keys[$i]['entity_type'], 0, 7)) {
              unset($entity_keys[$i]['entity_label']);
            }
            ++$i;
          }
        }
        if (isset($item['relation_options']['rid'])) {
          if ($relation = relation_load($item['relation_options']['rid'])) {
            if ($relation->relation_type == $type) {
              $relation->endpoints[LANGUAGE_NONE] = $entity_keys;
              $relation->is_new = FALSE;
            }
            else {

              // Different relation type.
              relation_delete($item['relation_options']['rid']);
              $relation = relation_create($type, $entity_keys);
            }
          }
          else {

            // Failed load the relation.
            $relation = relation_create($type, $entity_keys);
          }
        }
        else {
          $relation = relation_create($type, $entity_keys);
        }
        $form = $form_state = array();
        $relation_instances = field_info_instances('relation', $relation->relation_type);
        foreach ($item['relation_options'] as $relation_field_name => $relation_field) {
          if (isset($relation_instances[$relation_field_name])) {
            $relation_keys = array_keys($relation_field);
            $langcode = array_shift($relation_keys);
            $relation_field_items = array_shift($relation_field);
            $relation_field = field_info_field($relation_field_name);
            foreach ($relation_field_items as $delta => $relation_field_item) {
              if (!is_numeric($delta)) {
                unset($relation_field_items[$delta]);
              }
            }
            field_default_submit('relation', $relation, $relation_field, $relation_instances[$relation_field_name], $langcode, $relation_field_items, $form, $form_state);
            $relation->{$relation_field_name}[$langcode] = $relation_field_items;
          }
          else {
            $relation->{$relation_field_name} = $relation_field;
          }
        }
        relation_save($relation);
        $items[$key] = (array) $relation;
      }
      elseif (isset($item['relation_options']['rid'])) {
        relation_delete($item['relation_options']['rid']);
        unset($items[$key]);
      }
    }
    else {
      if (isset($item['relation_type']) && in_array($item['relation_type'], $instance['settings']['relation_type'])) {
        $type_array = explode(':', $item['relation_type']);
        $type = $type_array[0];
        $relation_reverse = isset($type_array[1]) && $type_array[1] == 'reverse';
        if (!isset($item['rid']) || empty($item['rid'])) {
          $relation_type = relation_type_load($type);
          $new_relation = (array) relation_create($type, array());
          $item += $new_relation;
          list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity);
          $add_entpoint = TRUE;
          foreach ($item['endpoints'][LANGUAGE_NONE] as $endpoint) {
            if ($endpoint['entity_type'] == $entity_type && $endpoint['entity_id'] == $id) {
              $add_entpoint = FALSE;
              break;
            }
          }
          if ($add_entpoint) {
            $new_endpoint = array(
              'entity_type' => $entity_type,
              'entity_id' => $id,
            );
            if ($relation_type->directional && !$relation_reverse) {
              array_unshift($item['endpoints'][LANGUAGE_NONE], $new_endpoint);
            }
            else {
              $item['endpoints'][LANGUAGE_NONE][] = $new_endpoint;
            }
          }
        }
        else {
          $item['relation_type'] = $type;
          if (isset($item['is_new'])) {
            $item['is_new'] = FALSE;
          }
        }
        $relation = (object) $item;
        relation_save($relation);
        if (isset($relation->is_new)) {
          unset($relation->is_new);
        }
        $items[$key] = (array) $relation;
      }
    }
  }
}

/**
 * Implements hook_field_formatter_info().
 */
function relation_add_field_formatter_info() {
  return array(
    'relation_add_endpoints_and_fields' => array(
      'label' => t('Endpoints and fields'),
      'field types' => array(
        'relation_add',
      ),
    ),
  );
}

/**
 * Implements hook_field_formatter_info_alter().
 */
function relation_add_field_formatter_info_alter(&$info) {
  $relation_dummy_formaters = array(
    'relation_default',
    'relation_otherendpoint',
    'relation_natural',
  );
  foreach ($relation_dummy_formaters as $dummy_formater) {
    if (isset($info[$dummy_formater])) {
      $info[$dummy_formater]['field types'][] = 'relation_add';
    }
  }
}

/**
 * Implements hook_field_formatter_view().
 */
function relation_add_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
  $element = array();
  list($entity_id) = entity_extract_ids($entity_type, $entity);
  switch ($display['type']) {
    case 'relation_add_endpoints_and_fields':
      foreach ($items as $delta => $item) {
        $links = array();
        $relation = (object) $item;
        if (count($relation->endpoints[LANGUAGE_NONE]) > 1) {
          foreach (array_filter($relation->endpoints[LANGUAGE_NONE]) as $endpoint) {
            $related_entities = entity_load($endpoint['entity_type'], array(
              $endpoint['entity_id'],
            ));
            $related_entity = reset($related_entities);
            if (!($endpoint['entity_type'] == $entity_type && $endpoint['entity_id'] == $entity_id)) {
              $link = entity_uri($endpoint['entity_type'], $related_entity);
              $link['href'] = $link['path'];
              $link['title'] = entity_label($endpoint['entity_type'], $related_entity);
              $links[] = $link;
            }
          }
          $endpoint_title = '';
          switch ($instance['widget']['settings']['relation_endpoint_label']) {
            case 'endpoint':
              $relation_instance = field_info_instance('relation', 'endpoints', $relation->relation_type);

              // @codingStandardsIgnoreStart
              $endpoint_title = t(check_plain($relation_instance['label']));

              // @codingStandardsIgnoreEnd
              break;
            case 'custom':

              // @codingStandardsIgnoreStart
              $endpoint_title = t($instance['widget']['settings']['relation_endpoint_custom_label']);

              // @codingStandardsIgnoreEnd
              break;
          }
          $endpoint_title .= $instance['widget']['settings']['relation_endpoint_label_delta'] ? ' ' . ($delta + 1) : '';

          // @codingStandardsIgnoreStart
          $element[$delta]['relation']['heading']['#markup'] = t(check_plain($endpoint_title));

          // @codingStandardsIgnoreEnd
          $element[$delta]['relation']['links'] = array(
            '#theme' => 'links',
            '#links' => $links,
          );
        }
        $relation_view = relation_view($relation);
        $relation_instances = field_info_instances('relation', $relation->relation_type);
        foreach (array_keys($relation_instances) as $relation_field_name) {
          if ($relation_field_name !== 'endpoints') {
            if (isset($relation_view[$relation_field_name])) {
              $element[$delta]['relation']['fields'][] = $relation_view[$relation_field_name];
            }
          }
        }
      }
      break;
  }
  return $element;
}

/**
 * Implements hook_entity_presave().
 */
function relation_add_entity_presave($entity, $entity_type) {
  if ('relation' == $entity_type) {
    _relation_add_field_cache_clear($entity);
  }
}

/**
 * Implements hook_entity_delete().
 */
function relation_add_entity_delete($entity, $entity_type) {
  if ('relation' == $entity_type) {
    _relation_add_field_cache_clear($entity);
  }
}

/**
 * Clear the field cache for relation endpoints.
 *
 * @param object $relation
 *   The relation to clear endpoints for.
 */
function _relation_add_field_cache_clear($relation) {
  foreach ($relation->endpoints[LANGUAGE_NONE] as $endpoint) {
    $cid = "field:{$endpoint['entity_type']}:{$endpoint['entity_id']}";
    cache_clear_all($cid, 'cache_field');
  }
}

/**
 * Entity property info getter callback for the relations.
 */
function relation_add_get_relation($entity, array $options, $property_name, $entity_type, $info) {
  $field = field_info_field($property_name);
  $langcode = field_language($entity_type, $entity, $property_name, isset($options['language']) ? $options['language']->language : NULL);
  $values = array();
  if (isset($entity->{$property_name}[$langcode])) {
    foreach ($entity->{$property_name}[$langcode] as $delta => $data) {

      // Wrappers do not support multiple entity references being revisions or
      // not yet saved entities. In the case of a single reference we can return
      // the entity object though.
      if ($field['cardinality'] == 1) {
        $values[$delta] = relation_load($data);
      }
      elseif (isset($data['rid'])) {
        $values[$delta] = $data['rid'];
      }
    }
  }

  // For an empty single-valued field, we have to return NULL.
  return $field['cardinality'] == 1 ? $values ? reset($values) : NULL : $values;
}

/**
 * Implements hook_entity_property_info_alter().
 */
function relation_add_entity_property_info_alter(&$info) {
  $fields = field_read_fields(array(
    'type' => 'relation_add',
  ));
  foreach ($fields as $field_name => $field) {
    $field_type = field_info_field_types($field['type']);
    $field_type['property_type'] = 'relation';
    $field_info = field_info_field($field_name);
    foreach ($field_info['bundles'] as $entity_type => $entity_bundles) {
      foreach ($entity_bundles as $bundle) {
        $instance = field_info_instance($entity_type, $field_name, $bundle);
        entity_metadata_field_default_property_callback($info, $entity_type, $field, $instance, $field_type);
        $info[$entity_type]['bundles'][$bundle]['properties'][$field_name]['getter callback'] = 'relation_add_get_relation';
      }
    }
  }
}

Functions

Namesort descending Description
relation_add_autocomplete Autocomplete page for listing entities appropriate for a given relation type.
relation_add_entity_delete Implements hook_entity_delete().
relation_add_entity_presave Implements hook_entity_presave().
relation_add_entity_property_info_alter Implements hook_entity_property_info_alter().
relation_add_field_formatter_info Implements hook_field_formatter_info().
relation_add_field_formatter_info_alter Implements hook_field_formatter_info_alter().
relation_add_field_formatter_view Implements hook_field_formatter_view().
relation_add_field_info Implements hook_field_info().
relation_add_field_insert Implements hook_field_insert().
relation_add_field_instance_settings_form Implements hook_field_instance_settings_form().
relation_add_field_is_empty Implements hook_field_is_empty().
relation_add_field_load Implements hook_field_load().
relation_add_field_required_validate It is called when the relation has required fields.
relation_add_field_update Implements hook_field_update().
relation_add_field_widget_form Implements hook_field_widget_form().
relation_add_field_widget_info Implements hook_field_widget_info().
relation_add_field_widget_settings_form Implements hook_field_widget_settings_form().
relation_add_get_relation Entity property info getter callback for the relations.
relation_add_item_is_empty Determines whether an item is empty.
relation_add_menu Implements hook_menu().
relation_add_permission Implements hook_permission().
relation_add_settings_ajax Ajax callback for the bundle type settings form.
relation_add_widget_ajax AJAX callback for widget form.
_relation_add_field_cache_clear Clear the field cache for relation endpoints.
_relation_add_field_instance_settings_form Process callback for relation_add_field_instance_settings_form().
_relation_add_field_instance_settings_validate Validate for relation_add_field_instance_settings_form().