You are here

entity_reference_multiple.module in Entity Reference Multiple 7

Same filename and directory in other branches
  1. 7.2 entity_reference_multiple.module

Primarily Drupal hooks.

This is the main module file for Entity Reference Multiple.

File

entity_reference_multiple.module
View source
<?php

/**
 * @file
 * Primarily Drupal hooks.
 *
 * This is the main module file for Entity Reference Multiple.
 */

/**
 * Implements hook_theme().
 */
function entity_reference_multiple_theme() {
  return array(
    'entity_reference_multiple_none' => array(
      'variables' => array(
        'instance' => NULL,
        'option' => NULL,
      ),
      'file' => 'theme/theme.inc',
    ),
  );
}

/**
 * Implements hook_flush_caches().
 */
function entity_reference_multiple_flush_caches() {

  // Because of the intricacies of the info hooks, we are forced to keep a
  // separate list of the base tables of each entities, so that we can use
  // it in entityreference_field_schema() without calling entity_get_info().
  // See http://drupal.org/node/1416558 for details.
  $base_tables = array();
  foreach (entity_get_info() as $entity_type => $entity_info) {
    if (!empty($entity_info['base table']) && !empty($entity_info['entity keys']['id'])) {
      $base_tables[$entity_type] = array(
        $entity_info['base table'],
        $entity_info['entity keys']['id'],
      );
    }
  }

  // We are using a variable because cache is going to be cleared right after
  // hook_flush_caches() is finished.
  variable_set('entity_reference_multiple:base-tables', $base_tables);
}

/**
 * Implements hook_field_info().
 */
function entity_reference_multiple_field_info() {
  return array(
    'entity_reference_multiple' => array(
      'label' => t('Entity Reference Multiple'),
      'description' => t('This field reference another entity.'),
      'settings' => array(
        'target_types' => array(
          'node' => 'node',
        ),
        'target_entities_bundles' => array(),
      ),
      'instance_settings' => array(),
      'default_widget' => 'entity_reference_multiple_selects',
      'default_formatter' => 'entity_reference_multiple_label',
      'property_type' => 'entity_reference_multiple',
      'property_callbacks' => array(
        'entity_reference_multiple_field_property_callback',
      ),
    ),
  );
}

/**
 * Implements hook_field_settings_form().
 */
function entity_reference_multiple_field_settings_form($field, $instance, $has_data) {

  // 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(
      '_entity_reference_multiple_field_settings_process',
      '_entity_reference_multiple_field_settings_ajax_process',
    ),
    '#element_validate' => array(
      '_entity_reference_multiple_field_settings_validate',
    ),
    '#field' => $field,
    '#instance' => $instance,
    '#has_data' => $has_data,
  );
  return $form;
}

/**
 * Implements hook_field_validate().
 */
function entity_reference_multiple_field_validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors) {
  $ids = array();
  foreach ($items as $delta => $item) {
    if (!entity_reference_multiple_field_is_empty($item, $field)) {
      $ids[$item['target_type']][$item['target_id']] = $delta;
    }
  }
  foreach ($ids as $target_type => $target_ids) {
    $valid_ids = _entity_reference_multiple_validate_referencable_entities($field, $target_type, array_keys($target_ids));
    if ($invalid_entities = array_diff_key($ids[$target_type], array_flip($valid_ids))) {
      foreach ($invalid_entities as $id => $delta) {
        $errors[$field['field_name']][$langcode][$delta][] = array(
          'error' => 'entity_reference_multiple_invalid_entity',
          'message' => t('The referenced entity (@type: @id) is invalid.', array(
            '@type' => $target_type,
            '@id' => $id,
          )),
        );
      }
    }
  }
}

/**
 * Implements hook_field_is_empty().
 */
function entity_reference_multiple_field_is_empty($item, $field) {
  $empty_target_type = !isset($item['target_type']) || !entity_get_info($item['target_type']);
  $empty_target_id = !isset($item['target_id']) || !is_numeric($item['target_id']);
  return $empty_target_type || $empty_target_id;
}

/**
 * Performs validation on form elements.
 *
 * @param array $form
 *   Nested array of form elements that comprise the form.
 * @param array $form_state
 *   A keyed array containing the current state of the form.
 */
function _entity_reference_multiple_field_settings_validate($form, &$form_state) {

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

/**
 * Add settings to a field settings form.
 *
 * @param array $form
 *   Nested array of form elements that comprise the form.
 * @param array $form_state
 *   A keyed array containing the current state of the form.
 *
 * @return array
 *   Nested array of form elements that comprise the form.
 */
function _entity_reference_multiple_field_settings_process($form, $form_state) {
  $field = isset($form_state['entity_reference_multiple']['field']) ? $form_state['entity_reference_multiple']['field'] : $form['#field'];
  $settings = $field['settings'];
  $has_data = $form['#has_data'];

  // Select the target entity type.
  $entity_type_options = array();
  foreach (entity_get_info() as $entity_type => $entity_info) {
    $entity_type_options[$entity_type] = $entity_info['label'];
  }
  $form['target_types'] = array(
    '#type' => 'checkboxes',
    '#title' => t('Target types'),
    '#options' => $entity_type_options,
    '#default_value' => $settings['target_types'],
    '#required' => TRUE,
    '#description' => t('The entity types that can be referenced through this field.'),
    '#disabled' => $has_data,
    '#size' => 1,
    '#ajax' => TRUE,
    '#limit_validation_errors' => array(),
  );
  foreach ($field['settings']['target_types'] as $entity_type) {
    if ($entity_type && ($entity_info = entity_get_info($entity_type))) {
      $form['target_entities_bundles'][$entity_type] = array(
        '#type' => 'fieldset',
        '#title' => $entity_info['label'],
        '#tree' => TRUE,
        '#collapsible' => TRUE,
        '#collapsed' => FALSE,
        '#access' => FALSE,
      );
      $form['target_entities_bundles'][$entity_type]['target_bundles'] = array(
        '#type' => 'value',
        '#value' => array(),
      );
      if (!empty($entity_info['entity keys']['bundle'])) {
        $bundles = array();
        foreach ($entity_info['bundles'] as $bundle_name => $bundle_info) {
          $bundles[$bundle_name] = $bundle_info['label'];
        }
        $form['target_entities_bundles'][$entity_type]['#access'] = TRUE;
        $form['target_entities_bundles'][$entity_type]['target_bundles'] = array(
          '#type' => 'checkboxes',
          '#title' => t('Target bundles'),
          '#options' => $bundles,
          '#default_value' => isset($settings['target_entities_bundles'][$entity_type]['target_bundles']) ? $settings['target_entities_bundles'][$entity_type]['target_bundles'] : array(),
          '#size' => 6,
          '#multiple' => TRUE,
          '#description' => t('The bundles of the entity type that can be referenced. Optional, leave empty for all bundles.'),
          '#element_validate' => array(
            '_entityreference_element_validate_filter',
          ),
        );
      }
    }
  }
  return $form;
}

/**
 * Form processing handler for the #ajax form property.
 *
 * @param array $form
 *   Nested array of form elements that comprise the form.
 * @param array $form_state
 *   A keyed array containing the current state of the form.
 *
 * @return array
 *   Nested array of form elements that comprise the form.
 */
function _entity_reference_multiple_field_settings_ajax_process($form, $form_state) {
  _entity_reference_multiple_field_settings_ajax_process_element($form, $form);
  return $form;
}

/**
 * Form element processing handler for the #ajax form property.
 *
 * @param array $element
 *   An associative array containing the properties of the element.
 * @param array $main_form
 *   Nested array of form elements that comprise the form.
 */
function _entity_reference_multiple_field_settings_ajax_process_element(&$element, $main_form) {
  if (isset($element['#ajax']) && $element['#ajax'] === TRUE) {
    $element['#ajax'] = array(
      'callback' => 'entity_reference_multiple_settings_ajax',
      'wrapper' => $main_form['#id'],
      'element' => $main_form['#array_parents'],
    );
  }
  foreach (element_children($element) as $key) {
    _entity_reference_multiple_field_settings_ajax_process_element($element[$key], $main_form);
  }
}

/**
 * Ajax callback for the handler settings form.
 */
function entity_reference_multiple_settings_ajax($form, $form_state) {
  $trigger = $form_state['triggering_element'];
  return drupal_array_get_nested_value($form, $trigger['#ajax']['element']);
}

/**
 * Submit handler for the non-JS case.
 */
function entity_reference_multiple_settings_ajax_submit($form, &$form_state) {
  $form_state['rebuild'] = TRUE;
}

/**
 * Validate that entities can be referenced by this field.
 *
 * @param array $field
 *   The field definition.
 * @param string $target_type
 *   The type of the entity.
 * @param array $target_ids
 *   The ids of the entities.
 *
 * @return array
 *   An array of entity ids that are valid.
 */
function _entity_reference_multiple_validate_referencable_entities($field, $target_type, $target_ids) {
  $query = _entity_reference_multiple_build_query($field, $target_type);
  $query
    ->entityCondition('entity_id', $target_ids, 'IN');
  $result = $query
    ->execute();
  if (!empty($result[$target_type])) {
    return array_keys($result[$target_type]);
  }
  return array();
}

/**
 * Build an EntityFieldQuery to get referencable entities.
 *
 * @param array $field
 *   The field definition.
 * @param string $target_type
 *   The type of the entity.
 *
 * @return EntityFieldQuery
 *   The built query.
 */
function _entity_reference_multiple_build_query($field, $target_type) {
  $query = new EntityFieldQuery();
  $query
    ->entityCondition('entity_type', $target_type);
  if (!empty($field['settings']['target_entities_bundles'][$target_type]) && ($bundles = _entity_reference_multiple_bundles_prepare($field['settings']['target_entities_bundles'][$target_type]))) {
    $query
      ->entityCondition('bundle', $bundles, 'IN');
  }

  // Add a generic entity access tag to the query.
  $query
    ->addTag($target_type . '_access');
  $query
    ->addMetaData('field', $field);

  // Add the sort option.
  $query
    ->propertyOrderBy('title');
  return $query;
}

/**
 * Property callback for the Entity Metadata framework.
 */
function entity_reference_multiple_field_property_callback(&$info, $entity_type, $field, $instance, $field_type) {
  $property =& $info[$entity_type]['bundles'][$instance['bundle']]['properties'][$field['field_name']];
  $property['getter callback'] = 'entity_metadata_field_verbatim_get';
  $property['setter callback'] = 'entity_metadata_field_verbatim_set';
  unset($property['query callback']);
  $property['property info']['entity'] = array(
    'type' => 'entity',
    'label' => t('The entity.'),
    'getter callback' => 'entity_reference_multiple_metadata_field_property_entity_get',
    'translatable' => !empty($field['translatable']),
    // Specify that this property stems from a field.
    'field' => TRUE,
    'required' => !empty($instance['required']),
  );
}

/**
 * Callback for getting referencable entity.
 */
function entity_reference_multiple_metadata_field_property_entity_get($item) {
  if ($entity = entity_load($item['target_type'], array(
    $item['target_id'],
  ))) {
    return entity_metadata_wrapper($item['target_type'], reset($entity));
  }
  return NULL;
}

/**
 * Implements hook_field_widget_info().
 */
function entity_reference_multiple_field_widget_info() {
  return array(
    'entity_reference_multiple_selects' => array(
      'label' => t('Entity Reference Multiple Selects'),
      'field types' => array(
        'entity_reference_multiple',
      ),
    ),
  );
}

/**
 * Implements hook_field_widget_form().
 */
function entity_reference_multiple_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
  $element['#attached']['css'][] = drupal_get_path('module', 'entity_reference_multiple') . '/css/entity-reference-multiple.css';
  $wrapper_id_array = array(
    $field['field_name'],
    $langcode,
    $delta,
  );
  $wrapper_id = str_replace('_', '-', implode('-', $wrapper_id_array));
  $element[$element['#field_name']] = array(
    '#type' => 'container',
    '#attributes' => array(
      'id' => $wrapper_id,
      'class' => array(
        'entity-reference-multiple-inline',
      ),
    ),
  );

  // Prepare the list of options.
  $column = 'target_type';
  $options = _entity_reference_multiple_get_options($field, $instance, $items, $element, $column);
  $target_type_value = isset($items[$delta][$column]) ? $items[$delta][$column] : NULL;
  $parents = array(
    $element['#field_name'],
    $langcode,
    $delta,
    $column,
  );
  if (!empty($form_state['values'])) {
    $target_type_value = drupal_array_get_nested_value($form_state['values'], $parents);
  }
  $element[$element['#field_name']][$column] = array(
    '#type' => 'select',
    '#parents' => $parents,
    '#title' => t('Entity type'),
    '#options' => $options,
    '#default_value' => $target_type_value,
    '#required' => $element['#required'],
    '#value_key' => $column,
    '#ajax' => array(
      'wrapper' => $wrapper_id,
      'callback' => '_entity_reference_multiple_field_widget_ajax_process',
    ),
  );

  // Prepare the list of options.
  $column = 'target_id';
  $options = _entity_reference_multiple_get_options($field, $instance, $items, $element, $column, $target_type_value);
  $target_id_value = isset($items[$delta][$column]) ? $items[$delta][$column] : NULL;
  $parents = array(
    $element['#field_name'],
    $langcode,
    $delta,
    $column,
  );
  if (!empty($form_state['triggering_element']) && $form_state['triggering_element']['#value_key'] == 'target_type') {
    $target_id_value = NULL;
    drupal_array_set_nested_value($form_state['input'], $parents, $target_id_value);
  }
  $element[$field['field_name']][$column] = array(
    '#type' => 'select',
    '#parents' => $parents,
    '#title' => t('Entity'),
    '#options' => $options,
    '#default_value' => $target_id_value,
    '#required' => !empty($target_type_value) && $target_type_value != '_none' || $element['#required'],
    '#value_key' => $column,
  );
  return $element;
}

/**
 * Implements hook_field_widget_error().
 */
function entity_reference_multiple_field_widget_error($element, $error, $form, &$form_state) {
  form_error($element, $error['message']);
}

/**
 * Form processing handler for the #ajax form property.
 *
 * @param array $form
 *   Nested array of form elements that comprise the form.
 * @param array $form_state
 *   A keyed array containing the current state of the form.
 *
 * @return array
 *   The new form element.
 */
function _entity_reference_multiple_field_widget_ajax_process($form, $form_state) {
  $ref =& $form;
  $parents = $form_state['triggering_element']['#array_parents'];
  array_pop($parents);
  foreach ($parents as $parent) {
    if (is_array($ref) && array_key_exists($parent, $ref)) {
      $ref =& $ref[$parent];
    }
  }
  return $ref;
}

/**
 * Collects the options for a field.
 */
function _entity_reference_multiple_get_options($field, $instance, $items, $element, $column, $target_type = NULL) {
  $has_value = isset($items[0][$column]);
  $properties = _options_properties('select', TRUE, $element['#required'], $has_value);
  $options = array();
  if ($column == 'target_type') {

    // Get the list of options for target_type column.
    $options = _entity_reference_multiple_target_type_allowed_values($field);
  }
  elseif ($column == 'target_id') {

    // Get the list of options for target_id column.
    $options = _entity_reference_multiple_target_id_allowed_values($field, $target_type);
  }

  // Sanitize the options.
  _options_prepare_options($options, $properties);
  if (!$properties['optgroups']) {
    $options = options_array_flatten($options);
  }
  if ($properties['empty_option'] && $column == 'target_type' || $properties['empty_option'] && (empty($target_type) || $target_type == '_none')) {
    $label = theme('entity_reference_multiple_none', array(
      'instance' => $instance,
      'option' => $properties['empty_option'],
    ));
    $options = array(
      '_none' => $label,
    ) + $options;
  }
  return $options;
}

/**
 * Returns the set of valid entity types for a entity_reference_multiple field.
 *
 * @param array $field
 *   The field definition.
 *
 * @return array
 *   The array of valid entity types for this field, keyed by entity type.
 */
function _entity_reference_multiple_target_type_allowed_values($field) {
  $entity_info = entity_get_info();
  $options = array();
  foreach (array_keys($field['settings']['target_entities_bundles']) as $target_type) {
    if (!empty($entity_info[$target_type])) {
      $options[$target_type] = $entity_info[$target_type]['label'];
    }
  }
  return $options;
}

/**
 * Returns the set of valid entities for a entity_reference_multiple field.
 *
 * @param array $field
 *   The field definition.
 * @param string $target_type
 *   The type of the entity.
 *
 * @return array
 *   The array of valid entities for this field, keyed by entity id and
 *   grouped by bundles.
 */
function _entity_reference_multiple_target_id_allowed_values($field, $target_type) {
  $entity_info = entity_get_info();
  $options = array();
  if (in_array($target_type, $field['settings']['target_types'], TRUE)) {
    $query = _entity_reference_multiple_build_query($field, $target_type);
    $results = $query
      ->execute();
    if (!empty($results[$target_type])) {
      $entities = entity_load($target_type, array_keys($results[$target_type]));
      foreach ($entities as $entity_id => $entity) {
        list(, , $bundle) = entity_extract_ids('node', $entity);
        $group = $entity_info[$target_type]['bundles'][$bundle]['label'];
        $options[$group][$entity_id] = check_plain(entity_label($target_type, $entity));
      }
    }
  }
  return $options;
}

/**
 * Get selected bundles.
 *
 * @param array $bundles
 *   An array contains all bundles.
 *
 * @return array
 *   An array contains selected bundles.
 */
function _entity_reference_multiple_bundles_prepare($bundles) {
  foreach ($bundles as $key => $bundle) {
    if (!$bundle) {
      unset($bundles[$key]);
    }
  }
  return $bundles;
}

/**
 * Implements hook_field_formatter_info().
 */
function entity_reference_multiple_field_formatter_info() {
  return array(
    'entity_reference_multiple_label' => array(
      'label' => t('Label'),
      'description' => t('Display the label of the referenced entities.'),
      'field types' => array(
        'entity_reference_multiple',
      ),
      'settings' => array(
        'link' => FALSE,
      ),
    ),
  );
}

/**
 * Implements hook_field_formatter_settings_form().
 */
function entity_reference_multiple_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) {
  $display = $instance['display'][$view_mode];
  $settings = $display['settings'];
  $element = array();
  if ($display['type'] == 'entity_reference_multiple_label') {
    $element['link'] = array(
      '#title' => t('Link label to the referenced entity'),
      '#type' => 'checkbox',
      '#default_value' => $settings['link'],
    );
  }
  return $element;
}

/**
 * Implements hook_field_formatter_settings_summary().
 */
function entity_reference_multiple_field_formatter_settings_summary($field, $instance, $view_mode) {
  $display = $instance['display'][$view_mode];
  $settings = $display['settings'];
  $summary = array();
  if ($display['type'] == 'entity_reference_multiple_label') {
    $summary[] = $settings['link'] ? t('Link to the referenced entity') : t('No link');
  }
  return implode('<br />', $summary);
}

/**
 * Implements hook_field_formatter_prepare_view().
 */
function entity_reference_multiple_field_formatter_prepare_view($entity_type, $entities, $field, $instances, $langcode, &$items, $displays) {
  $targets = array();

  // Collect every possible entity attached to any of the entities.
  foreach ($entities as $id => $entity) {
    foreach ($items[$id] as $delta => $item) {
      if (isset($item['target_id']) && isset($item['target_type'])) {
        $targets[$item['target_type']][] = $item['target_id'];
      }
    }
  }
  $target_entities = array();
  foreach ($targets as $target_type => $target_ids) {
    $target_entities[$target_type] = entity_load($target_type, $target_ids);
  }

  // Iterate through the fieldable entities again to attach the loaded data.
  foreach ($entities as $id => $entity) {
    $rekey = FALSE;
    foreach ($items[$id] as $delta => $item) {

      // Check whether the referenced entity could be loaded.
      if (isset($target_entities[$item['target_type']][$item['target_id']])) {

        // Replace the instance value with the term data.
        $items[$id][$delta]['entity'] = $target_entities[$item['target_type']][$item['target_id']];

        // Check whether the user has access to the referenced entity.
        $has_view_access = entity_access('view', $item['target_type'], $target_entities[$item['target_type']][$item['target_id']]);
        $has_update_access = entity_access('update', $item['target_type'], $target_entities[$item['target_type']][$item['target_id']]);
        $items[$id][$delta]['access'] = $has_view_access || $has_update_access;
      }
      else {
        unset($items[$id][$delta]);
        $rekey = TRUE;
      }
    }
    if ($rekey) {

      // Rekey the items array.
      $items[$id] = array_values($items[$id]);
    }
  }
}

/**
 * Implements hook_field_formatter_view().
 */
function entity_reference_multiple_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
  $result = array();

  // Rebuild the items list to contain only those with access.
  foreach ($items as $key => $item) {
    if (empty($item['access'])) {
      unset($items[$key]);
    }
  }
  switch ($display['type']) {
    case 'entity_reference_multiple_label':
      foreach ($items as $delta => $item) {
        $label = entity_label($item['target_type'], $item['entity']);

        // If the link is to be displayed and the entity has a uri,
        // display a link. Note the assignment ($url = ) here is intended to
        // be an assignment.
        if ($display['settings']['link'] && ($uri = entity_uri($item['target_type'], $item['entity']))) {
          $result[$delta] = array(
            '#markup' => l($label, $uri['path'], $uri['options']),
          );
        }
        else {
          $result[$delta] = array(
            '#markup' => check_plain($label),
          );
        }
      }
      break;
  }
  return $result;
}

Functions

Namesort descending Description
entity_reference_multiple_field_formatter_info Implements hook_field_formatter_info().
entity_reference_multiple_field_formatter_prepare_view Implements hook_field_formatter_prepare_view().
entity_reference_multiple_field_formatter_settings_form Implements hook_field_formatter_settings_form().
entity_reference_multiple_field_formatter_settings_summary Implements hook_field_formatter_settings_summary().
entity_reference_multiple_field_formatter_view Implements hook_field_formatter_view().
entity_reference_multiple_field_info Implements hook_field_info().
entity_reference_multiple_field_is_empty Implements hook_field_is_empty().
entity_reference_multiple_field_property_callback Property callback for the Entity Metadata framework.
entity_reference_multiple_field_settings_form Implements hook_field_settings_form().
entity_reference_multiple_field_validate Implements hook_field_validate().
entity_reference_multiple_field_widget_error Implements hook_field_widget_error().
entity_reference_multiple_field_widget_form Implements hook_field_widget_form().
entity_reference_multiple_field_widget_info Implements hook_field_widget_info().
entity_reference_multiple_flush_caches Implements hook_flush_caches().
entity_reference_multiple_metadata_field_property_entity_get Callback for getting referencable entity.
entity_reference_multiple_settings_ajax Ajax callback for the handler settings form.
entity_reference_multiple_settings_ajax_submit Submit handler for the non-JS case.
entity_reference_multiple_theme Implements hook_theme().
_entity_reference_multiple_build_query Build an EntityFieldQuery to get referencable entities.
_entity_reference_multiple_bundles_prepare Get selected bundles.
_entity_reference_multiple_field_settings_ajax_process Form processing handler for the #ajax form property.
_entity_reference_multiple_field_settings_ajax_process_element Form element processing handler for the #ajax form property.
_entity_reference_multiple_field_settings_process Add settings to a field settings form.
_entity_reference_multiple_field_settings_validate Performs validation on form elements.
_entity_reference_multiple_field_widget_ajax_process Form processing handler for the #ajax form property.
_entity_reference_multiple_get_options Collects the options for a field.
_entity_reference_multiple_target_id_allowed_values Returns the set of valid entities for a entity_reference_multiple field.
_entity_reference_multiple_target_type_allowed_values Returns the set of valid entity types for a entity_reference_multiple field.
_entity_reference_multiple_validate_referencable_entities Validate that entities can be referenced by this field.