You are here

inline_entity_form.module in Inline Entity Form 7

Same filename and directory in other branches
  1. 8 inline_entity_form.module

Provides a widget for inline management (creation, modification, removal) of referenced entities. The primary use case is the parent -> children one (for example, order -> line items), where the child entities are never managed outside the parent form.

File

inline_entity_form.module
View source
<?php

/**
 * @file
 * Provides a widget for inline management (creation, modification, removal) of
 * referenced entities. The primary use case is the parent -> children one
 * (for example, order -> line items), where the child entities are never
 * managed outside the parent form.
 */

/**
 * Flag indicating that the entity should be unlinked.
 */
define('IEF_ENTITY_UNLINK', 1);

/**
 * Flag indicating that the entity should be unlinked and deleted.
 */
define('IEF_ENTITY_UNLINK_DELETE', 2);

/**
 * Implements hook_menu().
 */
function inline_entity_form_menu() {
  $items = array();
  $items['inline_entity_form/autocomplete'] = array(
    'title' => 'Inline Entity Form Autocomplete',
    'page callback' => 'inline_entity_form_autocomplete',
    'access callback' => TRUE,
    'type' => MENU_CALLBACK,
  );
  return $items;
}

/**
 * Returns output for inline entity form autocompletes.
 *
 * Supports commerce_product_reference and entityreference fields.
 */
function inline_entity_form_autocomplete($entity_type, $field_name, $bundle, $string = '') {
  $field = field_info_field($field_name);
  $instance = field_info_instance($entity_type, $field_name, $bundle);
  $settings = inline_entity_form_settings($field, $instance);
  $controller = inline_entity_form_get_controller($instance);

  // The current entity type is not supported, or the string is empty.
  // strlen() is used instead of empty() since '0' is a valid value.
  if (!$field || !$instance || !$controller || !strlen($string)) {
    return MENU_ACCESS_DENIED;
  }
  $results = array();
  if ($field['type'] == 'commerce_product_reference') {
    $match_operator = strtolower($controller
      ->getSetting('match_operator'));
    $products = commerce_product_match_products($field, $instance, $string, $match_operator, array(), 10, TRUE);

    // Loop through the products and convert them into autocomplete output.
    foreach ($products as $product_id => $data) {
      $results[] = t('@label (!entity_id)', array(
        '@label' => $data['title'],
        '!entity_id' => $product_id,
      ));
    }
  }
  elseif ($field['type'] == 'entityreference') {
    $handler = entityreference_get_selection_handler($field, $instance, $settings['entity_type']);
    $entity_labels = $handler
      ->getReferencableEntities($string, $controller
      ->getSetting('match_operator'), 10);
    foreach ($entity_labels as $bundle => $labels) {

      // Loop through each entity type, and autocomplete with its titles.
      foreach ($labels as $entity_id => $label) {

        // entityreference has already check_plain-ed the title.
        $results[] = t('!label (!entity_id)', array(
          '!label' => $label,
          '!entity_id' => $entity_id,
        ));
      }
    }
  }
  $matches = array();
  foreach ($results as $result) {

    // Strip things like starting/trailing white spaces, line breaks and tags.
    $key = preg_replace('/\\s\\s+/', ' ', str_replace("\n", '', trim(decode_entities(strip_tags($result)))));
    $matches[$key] = '<div class="reference-autocomplete">' . $result . '</div>';
  }
  drupal_json_output($matches);
}

/**
 * Returns the Inline Entity Form controller for the passed-in reference field.
 *
 * @param $instance
 *   The instance array of the reference field.
 *
 * @return
 *   An instantiated controller object, or FALSE if none found.
 */
function inline_entity_form_get_controller($instance) {
  $field = field_info_field($instance['field_name']);
  $type_settings = $instance['widget']['settings']['type_settings'];
  $ief_settings = inline_entity_form_settings($field, $instance);
  $entity_info = entity_get_info($ief_settings['entity_type']);

  // The current entity type is not supported, execution can't continue.
  if (!isset($entity_info['inline entity form'])) {
    return FALSE;
  }
  return new $entity_info['inline entity form']['controller']($ief_settings['entity_type'], $type_settings);
}

/**
 * Implements hook_entity_info_alter().
 */
function inline_entity_form_entity_info_alter(&$entity_info) {
  $entity_info['node']['inline entity form'] = array(
    'controller' => 'NodeInlineEntityFormController',
  );
  if (isset($entity_info['taxonomy_term'])) {
    $entity_info['taxonomy_term']['inline entity form'] = array(
      'controller' => 'TaxonomyTermInlineEntityFormController',
    );
  }
  if (isset($entity_info['commerce_product'])) {
    $entity_info['commerce_product']['inline entity form'] = array(
      'controller' => 'CommerceProductInlineEntityFormController',
    );
  }
  if (isset($entity_info['commerce_line_item'])) {
    $entity_info['commerce_line_item']['inline entity form'] = array(
      'controller' => 'CommerceLineItemInlineEntityFormController',
    );
  }
}

/**
 * Implements hook_entity_delete().
 *
 * Deletes referenced entities if needed.
 *
 * @todo Remove when there's a stable contrib module for this.
 */
function inline_entity_form_entity_delete($entity, $type) {
  $entity_info = entity_get_info($type);
  list(, , $bundle) = entity_extract_ids($type, $entity);
  foreach (field_info_instances($type, $bundle) as $field_name => $instance) {
    if (strpos($instance['widget']['type'], 'inline_entity_form') === 0) {
      $controller = inline_entity_form_get_controller($instance);

      // The controller specified that referenced entities should be deleted.
      if ($controller && $controller
        ->getSetting('delete_references')) {
        $items = field_get_items($type, $entity, $field_name);
        if ($items) {
          $field = field_info_field($field_name);
          $ief_settings = inline_entity_form_settings($field, $instance);
          $ids = array();
          foreach ($items as $item) {
            $ids[] = $item[$ief_settings['column']];
          }
          $context = array(
            'parent_entity_type' => $type,
            'parent_entity' => $entity,
          );
          $controller
            ->delete($ids, $context);
        }
      }
    }
  }
}

/**
 * Attaches theme specific CSS files.
 *
 * @param $theme_css
 *  An array of all CSS files that should be considered.
 * @param $css
 *   The $form['#attached']['css'] array, modified by reference.
 */
function _inline_entity_form_attach_css($theme_css, &$css) {
  if (empty($theme_css)) {
    return;
  }

  // Add the base CSS file, if provided.
  if (!empty($theme_css['base'])) {
    $css[] = $theme_css['base'];
  }

  // Add the theme specific CSS file, if provided.
  $theme_key = $GLOBALS['theme'];
  if (!empty($theme_css[$theme_key])) {
    $css[] = $theme_css[$theme_key];
  }
}

/**
 * Implements hook_theme().
 */
function inline_entity_form_theme() {
  return array(
    'inline_entity_form_entity_table' => array(
      'render element' => 'form',
    ),
  );
}

/**
 * Implements hook_field_widget_info().
 */
function inline_entity_form_field_widget_info() {
  $widgets = array();
  $widgets['inline_entity_form_single'] = array(
    'label' => t('Inline entity form - Single value'),
    'field types' => array(
      'commerce_line_item_reference',
      'commerce_product_reference',
      'entityreference',
    ),
    'settings' => array(
      'fields' => array(),
      'type_settings' => array(),
    ),
    'behaviors' => array(
      'multiple values' => FIELD_BEHAVIOR_CUSTOM,
      'default value' => FIELD_BEHAVIOR_NONE,
    ),
  );
  $widgets['inline_entity_form'] = array(
    'label' => t('Inline entity form - Multiple values'),
    'field types' => array(
      'commerce_line_item_reference',
      'commerce_product_reference',
      'entityreference',
    ),
    'settings' => array(
      'fields' => array(),
      'type_settings' => array(),
    ),
    'behaviors' => array(
      'multiple values' => FIELD_BEHAVIOR_CUSTOM,
      'default value' => FIELD_BEHAVIOR_NONE,
    ),
  );
  return $widgets;
}

/**
 * Implements hook_field_widget_settings_form().
 */
function inline_entity_form_field_widget_settings_form($field, $instance) {
  $widget = $instance['widget'];
  $settings = $widget['settings'];
  $controller = inline_entity_form_get_controller($instance);

  // The current entity type is not supported, execution can't continue.
  if (!$controller) {
    return array();
  }
  $element = array();

  // The fields are not editable from the UI for now.
  $element['fields'] = array(
    '#type' => 'value',
    '#value' => $settings['fields'],
  );

  // Add entity type specific settings if they exist.
  $settings_form = $controller
    ->settingsForm($field, $instance);
  if (!empty($settings_form)) {
    $entity_info = entity_get_info($controller
      ->entityType());
    $element['type_settings'] = array(
      '#type' => 'fieldset',
      '#title' => t('Inline Entity Form: %type', array(
        '%type' => $entity_info['label'],
      )),
    );
    $element['type_settings'] += $settings_form;
  }
  return $element;
}

/**
 * Implements hook_field_widget_properties_alter().
 *
 * IEF is not suited for usage within VBO's "modify entity values" action.
 * If that context is detected, switch the widget to the default one specified
 * by the field type.
 */
function inline_entity_form_field_widget_properties_alter(&$widget, $context) {
  if (strpos($widget['type'], 'inline_entity_form') !== 0) {

    // Not an IEF widget.
    return;
  }
  if (!module_exists('views') || !module_exists('views_bulk_operations')) {

    // Views or VBO not installed.
    return;
  }
  if ($view = views_get_current_view()) {
    $vbo = _views_bulk_operations_get_field($view);
    if ($vbo) {
      $field = $context['field'];
      $field_type = field_info_field_types($field['type']);
      $widget_type = field_info_widget_types($field_type['default_widget']);
      $widget_type += array(
        'settings',
      );
      $widget['type'] = $field_type['default_widget'];
      $widget['module'] = $widget_type['module'];
      $widget['settings'] = $widget_type['settings'];
    }
  }
}

/**
 * Introspects field and instance settings, and determines the correct settings
 * for the functioning of the widget.
 *
 * Settings:
 *   - entity_type - The entity_type being managed.
 *   - bundles - Bundles of entities that the user is allowed to create.
 *   - column - The name of the ref. field column that stores the entity id.
 */
function inline_entity_form_settings($field, $instance) {
  $settings = array(
    'entity_type' => NULL,
    'bundles' => array(),
    'create_bundles' => array(),
    'column' => NULL,
  );
  if ($field['type'] == 'commerce_product_reference') {
    $settings['entity_type'] = 'commerce_product';
    $settings['column'] = 'product_id';

    // The product reference field has its bundle setting, use it.
    $types = array_filter($instance['settings']['referenceable_types']);
    if (!empty($types)) {
      $settings['bundles'] = array_values($types);
    }
  }
  elseif ($field['type'] == 'entityreference') {
    $settings['entity_type'] = $field['settings']['target_type'];
    $settings['column'] = 'target_id';
    if (!empty($field['settings']['handler_settings']['target_bundles'])) {
      $bundles = array_filter($field['settings']['handler_settings']['target_bundles']);
      if (!empty($bundles)) {
        $settings['bundles'] = array_values($bundles);
      }
    }
  }
  elseif ($field['type'] == 'commerce_line_item_reference') {
    $settings['entity_type'] = 'commerce_line_item';
    $settings['column'] = 'line_item_id';
  }

  // Allow other modules to alter the settings.
  drupal_alter('inline_entity_form_settings', $settings, $field, $instance);

  // If no specific bundle has been selected, assume all are available.
  if (empty($settings['bundles'])) {
    $info = entity_get_info($settings['entity_type']);
    foreach ($info['bundles'] as $bundle_name => $bundle_info) {
      $settings['bundles'][] = $bundle_name;
    }
  }

  // Now create a filtered list of bundles that the user has access to.
  foreach ($settings['bundles'] as $bundle) {
    $new_entity = inline_entity_form_create_entity($settings['entity_type'], $bundle);
    if (entity_access('create', $settings['entity_type'], $new_entity)) {
      $settings['create_bundles'][] = $bundle;
    }
  }
  return $settings;
}

/**
 * Implements hook_field_widget_form().
 */
function inline_entity_form_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
  $widget = $instance['widget'];
  $settings = inline_entity_form_settings($field, $instance);
  $entity_info = entity_get_info($settings['entity_type']);
  $controller = inline_entity_form_get_controller($instance);

  // The current entity type is not supported, execution can't continue.
  if (!$controller) {
    return array();
  }

  // Get the entity type labels for the UI strings.
  $labels = $controller
    ->labels();

  // Build a parents array for this element's values in the form.
  $parents = array_merge($element['#field_parents'], array(
    $element['#field_name'],
    $element['#language'],
  ));

  // Get the langcode of the parent entity.
  $parent_langcode = entity_language($element['#entity_type'], $element['#entity']);

  // Assign a unique identifier to each IEF widget.
  // Since $parents can get quite long, sha1() ensures that every id has
  // a consistent and relatively short length while maintaining uniqueness.
  $ief_id = sha1(implode('-', $parents));

  // Determine the wrapper ID for the entire element.
  $wrapper = 'inline-entity-form-' . $ief_id;
  $element = array(
    '#type' => 'fieldset',
    '#tree' => TRUE,
    '#description' => filter_xss_admin($instance['description']),
    '#prefix' => '<div id="' . $wrapper . '">',
    '#suffix' => '</div>',
    '#attached' => array(
      'css' => array(),
    ),
    '#ief_id' => $ief_id,
    '#ief_root' => TRUE,
  ) + $element;
  if (module_exists('file')) {

    // file.js triggers uploads when the main Submit button is clicked.
    $element['#attached']['js'] = array(
      drupal_get_path('module', 'file') . '/file.js',
      drupal_get_path('module', 'inline_entity_form') . '/inline_entity_form.js',
    );
  }
  $base_css = array(
    'base' => drupal_get_path('module', 'inline_entity_form') . '/theme/inline_entity_form.css',
    'seven' => drupal_get_path('module', 'inline_entity_form') . '/theme/inline_entity_form.seven.css',
  );

  // Add the base module CSS.
  _inline_entity_form_attach_css($base_css, $element['#attached']['css']);

  // Add entity type specific CSS.
  _inline_entity_form_attach_css($controller
    ->css(), $element['#attached']['css']);

  // Initialize the IEF array in form state.
  if (empty($form_state['inline_entity_form'][$ief_id])) {
    $form_state['inline_entity_form'][$ief_id] = array(
      'form' => NULL,
      'settings' => $settings,
      'instance' => $instance,
    );

    // Load the entities from the $items array and store them in the form
    // state for further manipulation.
    $form_state['inline_entity_form'][$ief_id]['entities'] = array();
    $entity_ids = array();
    foreach ($items as $item) {
      $entity_ids[] = $item[$settings['column']];
    }
    $delta = 0;
    foreach (entity_load($settings['entity_type'], $entity_ids) as $entity) {
      $form_state['inline_entity_form'][$ief_id]['entities'][$delta] = array(
        'entity' => $entity,
        'weight' => $delta,
        'form' => NULL,
        'needs_save' => FALSE,
      );
      $delta++;
    }
  }

  // Prepare cardinality information.
  $cardinality = $field['cardinality'];
  $entity_count = count($form_state['inline_entity_form'][$ief_id]['entities']);
  $cardinality_reached = $cardinality > 0 && $entity_count == $cardinality;

  // Build the appropriate widget.
  // The "Single value" widget assumes it is operating on a required single
  // value reference field with 1 allowed bundle.
  if ($widget['type'] == 'inline_entity_form_single') {

    // Intentionally not using $settings['create_bundles'] here because this
    // widget doesn't care about permissions because of its use case.
    $bundle = reset($settings['bundles']);

    // Uh oh, the parent entity type and bundle are the same as the inline
    // entity type and bundle. We have recursion. Abort.
    if ($element['#entity_type'] == $settings['entity_type'] && $element['#bundle'] == $bundle) {
      return array();
    }
    $form_state['inline_entity_form'][$ief_id]['form settings'] = array(
      'bundle' => $bundle,
    );
    $element['form'] = array(
      '#type' => 'container',
      '#op' => 'add',
      // Used by Field API and controller methods to find the relevant
      // values in $form_state.
      '#parents' => array_merge($parents, array(
        'form',
      )),
      // Pass the current entity type.
      '#entity_type' => $settings['entity_type'],
      // Pass the langcode of the parent entity,
      '#parent_language' => $parent_langcode,
      // Identifies the IEF widget to which the form belongs.
      '#ief_id' => $ief_id,
    );
    if (!empty($form_state['inline_entity_form'][$ief_id]['entities'])) {
      $element['form']['#op'] = 'edit';
      $element['form']['#entity'] = $form_state['inline_entity_form'][$ief_id]['entities'][0]['entity'];
      $element['form']['#ief_row_delta'] = 0;
    }
    $element['form'] = inline_entity_form_entity_form($controller, $element['form'], $form_state);

    // Hide all actions, the widget form behaves like a part of the main form.
    $element['form']['actions']['#access'] = FALSE;
  }
  else {

    // Build the "Multiple value" widget.
    $element['#element_validate'] = array(
      'inline_entity_form_update_row_weights',
    );

    // Add the required element marker & validation.
    if ($element['#required']) {
      $element['#title'] .= ' ' . theme('form_required_marker', array(
        'element' => $element,
      ));
      $element['#element_validate'][] = 'inline_entity_form_required_field';
    }
    $element['entities'] = array(
      '#tree' => TRUE,
      '#theme' => 'inline_entity_form_entity_table',
      '#entity_type' => $settings['entity_type'],
      '#cardinality' => (int) $cardinality,
    );

    // Get the fields that should be displayed in the table.
    $fields = $controller
      ->tableFields($settings['bundles']);
    $context = array(
      'parent_entity_type' => $instance['entity_type'],
      'parent_bundle' => $instance['bundle'],
      'field_name' => $instance['field_name'],
      'entity_type' => $settings['entity_type'],
      'allowed_bundles' => $settings['bundles'],
    );
    drupal_alter('inline_entity_form_table_fields', $fields, $context);
    $element['entities']['#table_fields'] = $fields;
    $weight_delta = max(ceil(count($form_state['inline_entity_form'][$ief_id]['entities']) * 1.2), 50);
    foreach ($form_state['inline_entity_form'][$ief_id]['entities'] as $key => $value) {

      // Data used by theme_inline_entity_form_entity_table().
      $element['entities'][$key]['#entity'] = $entity = $value['entity'];
      $element['entities'][$key]['#needs_save'] = $value['needs_save'];

      // Handle row weights.
      $element['entities'][$key]['#weight'] = $value['weight'];

      // First check to see if this entity should be displayed as a form.
      if (!empty($value['form'])) {
        $element['entities'][$key]['delta'] = array(
          '#type' => 'value',
          '#value' => $value['weight'],
        );
        $element['entities'][$key]['form'] = array(
          '#type' => 'container',
          '#attributes' => array(
            'class' => array(
              'ief-form',
              'ief-form-row',
            ),
          ),
          '#op' => $value['form'],
          // Used by Field API and controller methods to find the relevant
          // values in $form_state.
          '#parents' => array_merge($parents, array(
            'entities',
            $key,
            'form',
          )),
          // Store the entity on the form, later modified in the controller.
          '#entity' => $entity,
          '#entity_type' => $settings['entity_type'],
          // Pass the langcode of the parent entity,
          '#parent_language' => $parent_langcode,
          // Identifies the IEF widget to which the form belongs.
          '#ief_id' => $ief_id,
          // Identifies the table row to which the form belongs.
          '#ief_row_delta' => $key,
        );

        // Prepare data for the form callbacks.
        $form =& $element['entities'][$key]['form'];

        // Add the appropriate form.
        if ($value['form'] == 'edit') {
          $form += inline_entity_form_entity_form($controller, $form, $form_state);
        }
        elseif ($value['form'] == 'remove') {
          $form += inline_entity_form_remove_form($controller, $form, $form_state);
        }
      }
      else {
        $row =& $element['entities'][$key];
        $row['delta'] = array(
          '#type' => 'weight',
          '#delta' => $weight_delta,
          '#default_value' => $value['weight'],
          '#attributes' => array(
            'class' => array(
              'ief-entity-delta',
            ),
          ),
        );

        // Add an actions container with edit and delete buttons for the entity.
        $row['actions'] = array(
          '#type' => 'container',
          '#attributes' => array(
            'class' => array(
              'ief-entity-operations',
            ),
          ),
        );

        // Make sure entity_access is not checked for unsaved entities.
        list($entity_id) = entity_extract_ids($controller
          ->entityType(), $entity);
        if (empty($entity_id) || entity_access('update', $controller
          ->entityType(), $entity)) {
          $row['actions']['ief_entity_edit'] = array(
            '#type' => 'submit',
            '#value' => t('Edit'),
            '#name' => 'ief-' . $ief_id . '-entity-edit-' . $key,
            '#limit_validation_errors' => array(),
            '#ajax' => array(
              'callback' => 'inline_entity_form_get_element',
              'wrapper' => $wrapper,
            ),
            '#submit' => array(
              'inline_entity_form_open_row_form',
            ),
            '#ief_row_delta' => $key,
            '#ief_row_form' => 'edit',
          );
        }

        // Add the clone button, if allowed.
        // The clone form follows the same semantics as the create form, so
        // it's opened below the table.
        if ($controller
          ->getSetting('allow_clone') && !$cardinality_reached && entity_access('create', $controller
          ->entityType(), $entity)) {
          $row['actions']['ief_entity_clone'] = array(
            '#type' => 'submit',
            '#value' => t('Clone'),
            '#name' => 'ief-' . $ief_id . '-entity-clone-' . $key,
            '#limit_validation_errors' => array(
              array_merge($parents, array(
                'actions',
              )),
            ),
            '#ajax' => array(
              'callback' => 'inline_entity_form_get_element',
              'wrapper' => $wrapper,
            ),
            '#submit' => array(
              'inline_entity_form_open_form',
            ),
            '#ief_row_delta' => $key,
            '#ief_form' => 'clone',
          );
        }

        // If 'allow_existing' is on, the default removal operation is unlink
        // and the access check for deleting happens inside the controller
        // removeForm() method.
        if (empty($entity_id) || $controller
          ->getSetting('allow_existing') || entity_access('delete', $controller
          ->entityType(), $entity)) {
          $row['actions']['ief_entity_remove'] = array(
            '#type' => 'submit',
            '#value' => t('Remove'),
            '#name' => 'ief-' . $ief_id . '-entity-remove-' . $key,
            '#limit_validation_errors' => array(),
            '#ajax' => array(
              'callback' => 'inline_entity_form_get_element',
              'wrapper' => $wrapper,
            ),
            '#submit' => array(
              'inline_entity_form_open_row_form',
            ),
            '#ief_row_delta' => $key,
            '#ief_row_form' => 'remove',
          );
        }
      }
    }
    if ($cardinality > 1) {

      // Add a visual cue of cardinality count.
      $message = t('You have added @entities_count out of @cardinality_count allowed @label.', array(
        '@entities_count' => $entity_count,
        '@cardinality_count' => $cardinality,
        '@label' => $labels['plural'],
      ));
      $element['cardinality_count'] = array(
        '#markup' => '<div class="ief-cardinality-count">' . $message . '</div>',
      );
    }

    // Do not return the rest of the form if cardinality count has been reached.
    if ($cardinality_reached) {
      return $element;
    }
    $hide_cancel = FALSE;

    // If the field is required and empty try to open one of the forms.
    if (empty($form_state['inline_entity_form'][$ief_id]['entities']) && $instance['required']) {
      if ($controller
        ->getSetting('allow_existing') && !$controller
        ->getSetting('allow_new')) {
        $form_state['inline_entity_form'][$ief_id]['form'] = 'ief_add_existing';
        $hide_cancel = TRUE;
      }
      elseif (count($settings['create_bundles']) == 1 && $controller
        ->getSetting('allow_new') && !$controller
        ->getSetting('allow_existing')) {
        $bundle = reset($settings['create_bundles']);

        // The parent entity type and bundle must not be the same as the inline
        // entity type and bundle, to prevent recursion.
        if ($element['#entity_type'] != $settings['entity_type'] || $element['#bundle'] != $bundle) {
          $form_state['inline_entity_form'][$ief_id]['form'] = 'add';
          $form_state['inline_entity_form'][$ief_id]['form settings'] = array(
            'bundle' => $bundle,
          );
          $hide_cancel = TRUE;
        }
      }
    }

    // If no form is open, show buttons that open one.
    if (empty($form_state['inline_entity_form'][$ief_id]['form'])) {
      $element['actions'] = array(
        '#attributes' => array(
          'class' => array(
            'container-inline',
          ),
        ),
        '#type' => 'container',
        '#weight' => 100,
      );

      // The user is allowed to create an entity of at least one bundle.
      if (count($settings['create_bundles'])) {

        // Let the user select the bundle, if multiple are available.
        if (count($settings['create_bundles']) > 1) {
          $bundles = array();
          foreach ($entity_info['bundles'] as $bundle_name => $bundle_info) {
            if (in_array($bundle_name, $settings['create_bundles'])) {
              $bundles[$bundle_name] = $bundle_info['label'];
            }
          }
          asort($bundles);
          $element['actions']['bundle'] = array(
            '#type' => 'select',
            '#options' => $bundles,
          );
        }
        else {
          $element['actions']['bundle'] = array(
            '#type' => 'value',
            '#value' => reset($settings['create_bundles']),
          );
        }
        if ($controller
          ->getSetting('allow_new')) {
          $element['actions']['ief_add'] = array(
            '#type' => 'submit',
            '#value' => t('Add new @type_singular', array(
              '@type_singular' => $labels['singular'],
            )),
            '#name' => 'ief-' . $ief_id . '-add',
            '#limit_validation_errors' => array(
              array_merge($parents, array(
                'actions',
              )),
            ),
            '#ajax' => array(
              'callback' => 'inline_entity_form_get_element',
              'wrapper' => $wrapper,
            ),
            '#submit' => array(
              'inline_entity_form_open_form',
            ),
            '#ief_form' => 'add',
          );
        }
      }
      if ($controller
        ->getSetting('allow_existing')) {
        $element['actions']['ief_add_existing'] = array(
          '#type' => 'submit',
          '#value' => t('Add existing @type_singular', array(
            '@type_singular' => $labels['singular'],
          )),
          '#name' => 'ief-' . $ief_id . '-add-existing',
          '#limit_validation_errors' => array(
            array_merge($parents, array(
              'actions',
            )),
          ),
          '#ajax' => array(
            'callback' => 'inline_entity_form_get_element',
            'wrapper' => $wrapper,
          ),
          '#submit' => array(
            'inline_entity_form_open_form',
          ),
          '#ief_form' => 'ief_add_existing',
        );
      }
    }
    else {

      // There's a form open, show it.
      $element['form'] = array(
        '#type' => 'fieldset',
        '#attributes' => array(
          'class' => array(
            'ief-form',
            'ief-form-bottom',
          ),
        ),
        // Identifies the IEF widget to which the form belongs.
        '#ief_id' => $ief_id,
        // Used by Field API and controller methods to find the relevant
        // values in $form_state.
        '#parents' => array_merge($parents, array(
          'form',
        )),
        // Pass the current entity type.
        '#entity_type' => $settings['entity_type'],
        // Pass the langcode of the parent entity,
        '#parent_language' => $parent_langcode,
      );
      if ($form_state['inline_entity_form'][$ief_id]['form'] == 'add') {
        $element['form']['#op'] = 'add';
        $element['form'] += inline_entity_form_entity_form($controller, $element['form'], $form_state);
      }
      elseif ($form_state['inline_entity_form'][$ief_id]['form'] == 'ief_add_existing') {
        $element['form'] += inline_entity_form_reference_form($controller, $element['form'], $form_state);
      }
      elseif ($form_state['inline_entity_form'][$ief_id]['form'] == 'clone') {
        $element['form']['#op'] = 'clone';
        $element['form'] += inline_entity_form_entity_form($controller, $element['form'], $form_state);
      }

      // Pre-opened forms can't be closed in order to force the user to
      // add / reference an entity.
      if ($hide_cancel) {
        if (isset($element['form']['actions']['ief_add_cancel'])) {
          $element['form']['actions']['ief_add_cancel']['#access'] = FALSE;
        }
        elseif (isset($element['form']['actions']['ief_reference_cancel'])) {
          $element['form']['actions']['ief_reference_cancel']['#access'] = FALSE;
        }
      }

      // No entities have been added. Remove the outer fieldset to reduce
      // visual noise caused by having two titles.
      if (empty($form_state['inline_entity_form'][$ief_id]['entities'])) {
        $element['#type'] = 'container';
      }
    }
  }
  return $element;
}

/**
 * Implements hook_form_alter().
 *
 * Adds the IEF submit function and the #ief_submit_all flag to the main submit
 * button of a form that contains an IEF widget.
 * Needs to be done in an alter hook because many forms add the submit button
 * after inline_entity_form_field_widget_form() is called.
 */
function inline_entity_form_form_alter(&$form, &$form_state, $form_id) {
  if (!empty($form_state['inline_entity_form'])) {

    // Try to find the main submit button in the most common places.
    $submit_element = NULL;
    if (!empty($form['submit'])) {
      $submit_element =& $form['submit'];
    }
    elseif (!empty($form['actions']['submit'])) {
      $submit_element =& $form['actions']['submit'];
    }
    if ($submit_element) {

      // Merge the IEF submit handler with the button level submit handlers if
      // available. Otherwise use the form level submit handlers.
      if (!empty($submit_element['#submit'])) {
        $submit = array_merge(array(
          'inline_entity_form_trigger_submit',
        ), (array) $submit_element['#submit']);
      }
      else {
        $submit = array_merge(array(
          'inline_entity_form_trigger_submit',
        ), (array) $form['#submit']);
      }

      // Reduce any duplicates on the off chance the IEF submit handler was
      // already a part of the array used.
      $submit_element['#submit'] = array_unique($submit);
      $submit_element['#ief_submit_all'] = TRUE;
    }
  }
}

/**
 * Updates entity weights based on their weights in the widget.
 */
function inline_entity_form_update_row_weights($element, &$form_state, $form) {
  $ief_id = $element['#ief_id'];

  // Loop over the submitted delta values and update the weight of the entities
  // in the form state.
  foreach (element_children($element['entities']) as $key) {
    $form_state['inline_entity_form'][$ief_id]['entities'][$key]['weight'] = $element['entities'][$key]['delta']['#value'];
  }
}

/**
 * Wraps and returns the entity form provided by the passed-in controller.
 *
 * @param $controller
 *   The inline entity form controller.
 * @param $entity_form
 *   The form array that will receive the entity form.
 * @param $form_state
 *   The form state of the parent form.
 *
 * @return
 *   The form array containing the embedded entity form.
 */
function inline_entity_form_entity_form($controller, $entity_form, &$form_state) {
  $labels = $controller
    ->labels();

  // Build a deta suffix that's appended to button #name keys for uniqueness.
  $delta = $entity_form['#ief_id'];
  if ($entity_form['#op'] == 'edit') {
    $delta .= '-' . $entity_form['#ief_row_delta'];
    $save_label = t('Update @type_singular', array(
      '@type_singular' => $labels['singular'],
    ));
  }
  elseif ($entity_form['#op'] == 'add') {

    // Create a new entity that will be passed to the form.
    $form_settings = $form_state['inline_entity_form'][$entity_form['#ief_id']]['form settings'];
    $entity_form['#entity'] = inline_entity_form_create_entity($entity_form['#entity_type'], $form_settings['bundle'], $entity_form['#parent_language']);
    $entity_form['#title'] = t('Add new @type_singular', array(
      '@type_singular' => $labels['singular'],
    ));
    $save_label = t('Create @type_singular', array(
      '@type_singular' => $labels['singular'],
    ));
  }
  elseif ($entity_form['#op'] == 'clone') {

    // Clone the entity.
    $form_settings = $form_state['inline_entity_form'][$entity_form['#ief_id']]['form settings'];
    $entity = $form_state['inline_entity_form'][$entity_form['#ief_id']]['entities'][$form_settings['source']]['entity'];
    $entity_form['#entity'] = $controller
      ->createClone($entity);
    $entity_form['#title'] = t('Clone @type_singular', array(
      '@type_singular' => $labels['singular'],
    ));
    $save_label = t('Clone @type_singular', array(
      '@type_singular' => $labels['singular'],
    ));
  }

  // Retrieve the form provided by the controller.
  $entity_form = $controller
    ->entityForm($entity_form, $form_state);

  // Add the actions
  $entity_form['actions'] = array(
    '#type' => 'container',
    '#weight' => 100,
  );
  $entity_form['actions']['ief_' . $entity_form['#op'] . '_save'] = array(
    '#type' => 'submit',
    '#value' => $save_label,
    '#name' => 'ief-' . $entity_form['#op'] . '-submit-' . $delta,
    '#limit_validation_errors' => array(
      $entity_form['#parents'],
    ),
    '#attributes' => array(
      'class' => array(
        'ief-entity-submit',
      ),
    ),
    '#ajax' => array(
      'callback' => 'inline_entity_form_get_element',
      'wrapper' => 'inline-entity-form-' . $entity_form['#ief_id'],
    ),
  );
  $entity_form['actions']['ief_' . $entity_form['#op'] . '_cancel'] = array(
    '#type' => 'submit',
    '#value' => t('Cancel'),
    '#name' => 'ief-' . $entity_form['#op'] . '-cancel-' . $delta,
    '#limit_validation_errors' => array(),
    '#ajax' => array(
      'callback' => 'inline_entity_form_get_element',
      'wrapper' => 'inline-entity-form-' . $entity_form['#ief_id'],
    ),
  );

  // Add the appropriate submit handlers and their related data.
  if (in_array($entity_form['#op'], array(
    'add',
    'clone',
  ))) {
    $entity_form['actions']['ief_' . $entity_form['#op'] . '_save']['#submit'] = array(
      'inline_entity_form_trigger_submit',
      'inline_entity_form_close_child_forms',
      'inline_entity_form_close_form',
    );
    $entity_form['actions']['ief_' . $entity_form['#op'] . '_cancel']['#submit'] = array(
      'inline_entity_form_close_child_forms',
      'inline_entity_form_close_form',
      'inline_entity_form_cleanup_form_state',
    );
  }
  else {
    $entity_form['actions']['ief_edit_save']['#ief_row_delta'] = $entity_form['#ief_row_delta'];
    $entity_form['actions']['ief_edit_cancel']['#ief_row_delta'] = $entity_form['#ief_row_delta'];
    $entity_form['actions']['ief_edit_save']['#submit'] = array(
      'inline_entity_form_trigger_submit',
      'inline_entity_form_close_child_forms',
      'inline_entity_form_close_row_form',
    );
    $entity_form['actions']['ief_edit_cancel']['#submit'] = array(
      'inline_entity_form_close_child_forms',
      'inline_entity_form_close_row_form',
      'inline_entity_form_cleanup_row_form_state',
    );
  }
  $entity_form['#element_validate'][] = 'inline_entity_form_entity_form_validate';
  $entity_form['#ief_element_submit'][] = 'inline_entity_form_entity_form_submit';

  // Add the pre_render callback that powers the #fieldset form element key,
  // which moves the element to the specified fieldset without modifying its
  // position in $form_state['values'].
  $entity_form['#pre_render'][] = 'inline_entity_form_pre_render_add_fieldset_markup';

  // Allow other modules to alter the form.
  drupal_alter('inline_entity_form_entity_form', $entity_form, $form_state);
  return $entity_form;
}

/**
 * Validates an entity form.
 *
 * @param $entity_form
 *  The form of the entity being managed inline.
 * @param $form_state
 *   The form state of the parent form.
 */
function inline_entity_form_entity_form_validate(&$entity_form, &$form_state) {
  $ief_id = $entity_form['#ief_id'];
  $instance = $form_state['inline_entity_form'][$ief_id]['instance'];

  // Instantiate the controller and validate the form.
  $controller = inline_entity_form_get_controller($instance);
  $controller
    ->entityFormValidate($entity_form, $form_state);

  // Unset untriggered conditional fields errors
  $errors = form_get_errors();
  if ($errors && !empty($form_state['conditional_fields_untriggered_dependents'])) {
    foreach ($form_state['conditional_fields_untriggered_dependents'] as $untriggered_dependents) {
      if (!empty($untriggered_dependents['errors'])) {
        foreach (array_keys($untriggered_dependents['errors']) as $key) {
          unset($errors[$key]);
        }
      }
    }
  }
}

/**
 * Submits an entity form.
 *
 * Note that at this point the entity is not yet saved, since the user might
 * still decide to cancel the parent form.
 *
 * @param $entity_form
 *  The form of the entity being managed inline.
 * @param $form_state
 *   The form state of the parent form.
 */
function inline_entity_form_entity_form_submit($entity_form, &$form_state) {
  $ief_id = $entity_form['#ief_id'];
  $instance = $form_state['inline_entity_form'][$ief_id]['instance'];

  // Instantiate the controller and validate the form.
  $controller = inline_entity_form_get_controller($instance);
  $controller
    ->entityFormSubmit($entity_form, $form_state);
  inline_entity_form_cleanup_entity_form_state($entity_form, $form_state);
  if (in_array($entity_form['#op'], array(
    'add',
    'clone',
  ))) {

    // Determine the correct weight of the new element.
    $weight = 0;
    if (!empty($form_state['inline_entity_form'][$ief_id]['entities'])) {
      $weight = max(array_keys($form_state['inline_entity_form'][$ief_id]['entities'])) + 1;
    }

    // Add the entity to form state, mark it for saving, and close the form.
    $form_state['inline_entity_form'][$ief_id]['entities'][] = array(
      'entity' => $entity_form['#entity'],
      'weight' => $weight,
      'form' => NULL,
      'needs_save' => TRUE,
    );
  }
  else {
    $delta = $entity_form['#ief_row_delta'];
    $form_state['inline_entity_form'][$ief_id]['entities'][$delta]['entity'] = $entity_form['#entity'];
    $form_state['inline_entity_form'][$ief_id]['entities'][$delta]['needs_save'] = TRUE;
  }
}

/**
 * Provides the form for adding existing entities through an autocomplete field.
 *
 * @param $entity_form
 *   The form array that will receive the form.
 * @param $form_state
 *   The form state of the parent form.
 *
 * @return
 *   The form array containing the embedded form.
 */
function inline_entity_form_reference_form($controller, $reference_form, &$form_state) {
  $labels = $controller
    ->labels();
  $ief_id = $reference_form['#ief_id'];
  $instance = $form_state['inline_entity_form'][$ief_id]['instance'];
  $autocomplete_path = 'inline_entity_form/autocomplete/' . $instance['entity_type'];
  $autocomplete_path .= '/' . $instance['field_name'] . '/' . $instance['bundle'];
  $reference_form['#title'] = t('Add existing @type_singular', array(
    '@type_singular' => $labels['singular'],
  ));
  $reference_form['entity_id'] = array(
    '#type' => 'textfield',
    '#title' => t('@label', array(
      '@label' => ucwords($labels['singular']),
    )),
    '#autocomplete_path' => $autocomplete_path,
    '#element_validate' => array(
      '_inline_entity_form_autocomplete_validate',
    ),
    '#required' => TRUE,
    '#maxlength' => 255,
  );

  // Add the actions
  $reference_form['actions'] = array(
    '#type' => 'container',
    '#weight' => 100,
  );
  $reference_form['actions']['ief_reference_save'] = array(
    '#type' => 'submit',
    '#value' => t('Add @type_singular', array(
      '@type_singular' => $labels['singular'],
    )),
    '#name' => 'ief-reference-submit-' . $reference_form['#ief_id'],
    '#limit_validation_errors' => array(
      $reference_form['#parents'],
    ),
    '#attributes' => array(
      'class' => array(
        'ief-entity-submit',
      ),
    ),
    '#ajax' => array(
      'callback' => 'inline_entity_form_get_element',
      'wrapper' => 'inline-entity-form-' . $reference_form['#ief_id'],
    ),
    '#submit' => array(
      'inline_entity_form_trigger_submit',
      'inline_entity_form_close_form',
    ),
  );
  $reference_form['actions']['ief_reference_cancel'] = array(
    '#type' => 'submit',
    '#value' => t('Cancel'),
    '#name' => 'ief-reference-cancel-' . $reference_form['#ief_id'],
    '#limit_validation_errors' => array(),
    '#ajax' => array(
      'callback' => 'inline_entity_form_get_element',
      'wrapper' => 'inline-entity-form-' . $reference_form['#ief_id'],
    ),
    '#submit' => array(
      'inline_entity_form_close_form',
    ),
  );
  $reference_form['#element_validate'][] = 'inline_entity_form_reference_form_validate';
  $reference_form['#ief_element_submit'][] = 'inline_entity_form_reference_form_submit';

  // Allow other modules to alter the form.
  drupal_alter('inline_entity_form_reference_form', $reference_form, $form_state);
  return $reference_form;
}

/**
 * #element_validate callback for the IEF autocomplete field.
 */
function _inline_entity_form_autocomplete_validate($element, &$form_state, $form) {
  $value = '';
  if (!empty($element['#value'])) {

    // Take "label (entity id)', match the id from parenthesis.
    if (preg_match("/.+\\((\\d+)\\)/", $element['#value'], $matches)) {
      $value = $matches[1];
    }
  }
  form_set_value($element, $value, $form_state);
}

/**
 * Validates the form for adding existing entities.
 *
 * @param $reference_form
 *  The reference entity form.
 * @param $form_state
 *   The form state of the parent form.
 */
function inline_entity_form_reference_form_validate(&$reference_form, &$form_state) {
  $ief_id = $reference_form['#ief_id'];
  $entity_type = $reference_form['#entity_type'];
  $parents_path = implode('][', $reference_form['#parents']);

  // Instantiate controller to access labels
  $instance = $form_state['inline_entity_form'][$ief_id]['instance'];
  $controller = inline_entity_form_get_controller($instance);
  $labels = $controller
    ->labels();
  $form_values = drupal_array_get_nested_value($form_state['values'], $reference_form['#parents']);
  $attach_entity = entity_load_single($entity_type, $form_values['entity_id']);

  // Check to see if entity is already referenced by current IEF widget
  if (!empty($attach_entity)) {
    foreach ($form_state['inline_entity_form'][$ief_id]['entities'] as $key => $value) {
      if ($value['entity'] == $attach_entity) {
        form_set_error($parents_path . '][existing_entity', t('The selected @label has already been added.', array(
          '@label' => $labels['singular'],
        )));
        break;
      }
    }
  }
  else {
    form_set_error($parents_path . '][existing_entity', t('The selected @label is not valid.', array(
      '@label' => $labels['singular'],
    )));
  }
}

/**
 * Submits the form for adding existing entities.
 *
 * Adds the specified entity to the IEF form state.
 *
 * @param $reference_form
 *  The reference entity form.
 * @param $form_state
 *   The form state of the parent form.
 */
function inline_entity_form_reference_form_submit($reference_form, &$form_state) {
  $ief_id = $reference_form['#ief_id'];
  $entity_type = $reference_form['#entity_type'];
  $form_values = drupal_array_get_nested_value($form_state['values'], $reference_form['#parents']);
  $attach_entity = entity_load_single($entity_type, $form_values['entity_id']);

  // Determine the correct weight of the new element.
  $weight = 0;
  if (!empty($form_state['inline_entity_form'][$ief_id]['entities'])) {
    $weight = max(array_keys($form_state['inline_entity_form'][$ief_id]['entities'])) + 1;
  }
  $form_state['inline_entity_form'][$ief_id]['entities'][] = array(
    'entity' => $attach_entity,
    'weight' => $weight,
    'form' => NULL,
    'needs_save' => FALSE,
  );
}

/**
 * Wraps and returns the remove form provided by the passed-in controller.
 *
 * @param $controller
 *   The inline entity form controller.
 * @param $remove_form
 *   The form array that will receive the entity form.
 * @param $form_state
 *   The form state of the parent form.
 *
 * @return
 *   The form array containing the embedded remove form.
 */
function inline_entity_form_remove_form($controller, $remove_form, &$form_state) {

  // Build a deta suffix that's appended to button #name keys for uniqueness.
  $delta = $remove_form['#ief_id'] . '-' . $remove_form['#ief_row_delta'];

  // Retrieve the form provided by the controller.
  $remove_form = $controller
    ->removeForm($remove_form, $form_state);

  // Add the actions
  $remove_form['actions'] = array(
    '#type' => 'container',
    '#weight' => 100,
  );
  $remove_form['actions']['ief_remove_confirm'] = array(
    '#type' => 'submit',
    '#value' => t('Remove'),
    '#name' => 'ief-remove-confirm-' . $delta,
    '#limit_validation_errors' => array(
      $remove_form['#parents'],
    ),
    '#ajax' => array(
      'callback' => 'inline_entity_form_get_element',
      'wrapper' => 'inline-entity-form-' . $remove_form['#ief_id'],
    ),
    '#submit' => array(
      'inline_entity_form_remove_confirm',
    ),
    '#ief_row_delta' => $remove_form['#ief_row_delta'],
  );
  $remove_form['actions']['ief_remove_cancel'] = array(
    '#type' => 'submit',
    '#value' => t('Cancel'),
    '#name' => 'ief-remove-cancel-' . $delta,
    '#limit_validation_errors' => array(),
    '#ajax' => array(
      'callback' => 'inline_entity_form_get_element',
      'wrapper' => 'inline-entity-form-' . $remove_form['#ief_id'],
    ),
    '#submit' => array(
      'inline_entity_form_close_row_form',
    ),
    '#ief_row_delta' => $remove_form['#ief_row_delta'],
  );
  return $remove_form;
}

/**
 * Remove form submit callback.
 *
 * The row is identified by #ief_row_delta stored on the triggering
 * element.
 * This isn't an #element_validate callback to avoid processing the
 * remove form when the main form is submitted.
 *
 * @param $form
 *   The complete parent form.
 * @param $form_state
 *   The form state of the parent form.
 */
function inline_entity_form_remove_confirm($form, &$form_state) {
  $form_state['rebuild'] = TRUE;
  $element = inline_entity_form_get_element($form, $form_state);
  $ief_id = $element['#ief_id'];
  $instance = $form_state['inline_entity_form'][$ief_id]['instance'];
  $delta = $form_state['triggering_element']['#ief_row_delta'];
  $remove_form = $element['entities'][$delta]['form'];

  // Instantiate the controller and submit the form.
  $controller = inline_entity_form_get_controller($instance);
  $status = $controller
    ->removeFormSubmit($remove_form, $form_state);
  if ($status == IEF_ENTITY_UNLINK_DELETE) {
    $settings = $form_state['inline_entity_form'][$ief_id]['settings'];
    list($entity_id) = entity_extract_ids($settings['entity_type'], $remove_form['#entity']);
    $form_state['inline_entity_form'][$ief_id]['delete'][] = $entity_id;
    unset($form_state['inline_entity_form'][$ief_id]['entities'][$delta]);
  }
  elseif ($status == IEF_ENTITY_UNLINK) {
    unset($form_state['inline_entity_form'][$ief_id]['entities'][$delta]);
  }
}

/**
 * Button #submit callback: Triggers submission of entity forms.
 *
 * @param $form
 *   The complete parent form.
 * @param $form_state
 *   The form state of the parent form.
 */
function inline_entity_form_trigger_submit($form, &$form_state) {
  if (!empty($form_state['triggering_element']['#ief_submit_all'])) {

    // The parent form was submitted, process all IEFs and their children.
    inline_entity_form_submit($form, $form_state);
  }
  else {

    // A specific entity form was submitted, process it and all of its children.
    $array_parents = $form_state['triggering_element']['#array_parents'];
    $array_parents = array_slice($array_parents, 0, -2);
    $element = drupal_array_get_nested_value($form, $array_parents);
    inline_entity_form_submit($element, $form_state);
  }
}

/**
 * Submits entity forms by calling their #ief_element_submit callbacks.
 *
 * #ief_element_submit is the submit version of #element_validate.
 *
 * @param $elements
 *   An array of form elements containing entity forms.
 * @param $form_state
 *   The form state of the parent form.
 */
function inline_entity_form_submit($elements, &$form_state) {

  // Recurse through all children.
  foreach (element_children($elements) as $key) {
    if (!empty($elements[$key])) {
      inline_entity_form_submit($elements[$key], $form_state);
    }
  }

  // If there are callbacks on this level, run them.
  if (!empty($elements['#ief_element_submit'])) {
    foreach ($elements['#ief_element_submit'] as $function) {
      $function($elements, $form_state);
    }
  }
}

/**
 * Button #submit callback: Opens a form in the IEF widget.
 *
 * The form is shown below the entity table, at the bottom of the widget.
 *
 * @param $form
 *   The complete parent form.
 * @param $form_state
 *   The form state of the parent form.
 */
function inline_entity_form_open_form($form, &$form_state) {
  $element = inline_entity_form_get_element($form, $form_state);
  $ief_id = $element['#ief_id'];
  $form_state['rebuild'] = TRUE;

  // Get the current form values.
  $parents = array_merge($element['#field_parents'], array(
    $element['#field_name'],
    $element['#language'],
  ));
  $form_values = drupal_array_get_nested_value($form_state['values'], $parents);
  $form_state['inline_entity_form'][$ief_id]['form'] = $form_state['triggering_element']['#ief_form'];
  $form_state['inline_entity_form'][$ief_id]['form settings'] = array();

  // Special code for the add form.
  if (!empty($form_values['actions']['bundle'])) {
    $form_state['inline_entity_form'][$ief_id]['form settings']['bundle'] = $form_values['actions']['bundle'];
  }

  // Special code for the clone form.
  if (isset($form_state['triggering_element']['#ief_row_delta'])) {
    $form_state['inline_entity_form'][$ief_id]['form settings']['source'] = $form_state['triggering_element']['#ief_row_delta'];
  }
}

/**
 * Button #submit callback: Closes a form in the IEF widget.
 *
 * @param $form
 *   The complete parent form.
 * @param $form_state
 *   The form state of the parent form.
 *
 * @see inline_entity_form_open_form().
 */
function inline_entity_form_close_form($form, &$form_state) {
  $element = inline_entity_form_get_element($form, $form_state);
  $ief_id = $element['#ief_id'];
  $form_state['rebuild'] = TRUE;
  $form_state['inline_entity_form'][$ief_id]['form'] = NULL;
}

/**
 * Button #submit callback: Cleans up form state for a closed entity form.
 *
 * @param $form
 *   The complete parent form.
 * @param $form_state
 *   The form state of the parent form.
 */
function inline_entity_form_cleanup_form_state($form, &$form_state) {
  $element = inline_entity_form_get_element($form, $form_state);
  inline_entity_form_cleanup_entity_form_state($element['form'], $form_state);
}

/**
 * Button #submit callback: Opens a row form in the IEF widget.
 *
 * The row is identified by #ief_row_delta stored on the triggering
 * element.
 *
 * @param $form
 *   The complete parent form.
 * @param $form_state
 *   The form state of the parent form.
 */
function inline_entity_form_open_row_form($form, &$form_state) {
  $element = inline_entity_form_get_element($form, $form_state);
  $ief_id = $element['#ief_id'];
  $delta = $form_state['triggering_element']['#ief_row_delta'];
  $form_state['rebuild'] = TRUE;
  $form_state['inline_entity_form'][$ief_id]['entities'][$delta]['form'] = $form_state['triggering_element']['#ief_row_form'];
}

/**
 * Button #submit callback: Closes a row form in the IEF widget.
 *
 * @param $form
 *   The complete parent form.
 * @param $form_state
 *   The form state of the parent form.
 *
 * @see inline_entity_form_open_row_form().
 */
function inline_entity_form_close_row_form($form, &$form_state) {
  $element = inline_entity_form_get_element($form, $form_state);
  $ief_id = $element['#ief_id'];
  $delta = $form_state['triggering_element']['#ief_row_delta'];
  $form_state['rebuild'] = TRUE;
  $form_state['inline_entity_form'][$ief_id]['entities'][$delta]['form'] = NULL;
}

/**
 * Button #submit callback:  Closes all open child forms in the IEF widget.
 *
 * Used to ensure that forms in nested IEF widgets are properly closed
 * when a parent IEF's form gets submitted or cancelled.
 */
function inline_entity_form_close_child_forms($form, &$form_state) {
  $element = inline_entity_form_get_element($form, $form_state);
  foreach (element_children($element) as $key) {
    if (!empty($element[$key])) {
      inline_entity_form_close_all_forms($element[$key], $form_state);
    }
  }
}

/**
 * Closes all open IEF forms.
 *
 * Recurses and closes open forms in nested IEF widgets as well.
 *
 * @param $elements
 *   An array of form elements containing entity forms.
 * @param $form_state
 *   The form state of the parent form.
 */
function inline_entity_form_close_all_forms($elements, &$form_state) {

  // Recurse through all children.
  foreach (element_children($elements) as $key) {
    if (!empty($elements[$key])) {
      inline_entity_form_close_all_forms($elements[$key], $form_state);
    }
  }
  if (!empty($elements['#ief_id'])) {
    $ief_id = $elements['#ief_id'];

    // Close the main form.
    $form_state['inline_entity_form'][$ief_id]['form'] = NULL;

    // Close the row forms.
    foreach ($form_state['inline_entity_form'][$ief_id]['entities'] as &$value) {
      $value['form'] = NULL;
    }
  }
}

/**
 * Button #submit callback: Cleans up form state for a closed entity row form.
 *
 * @param $form
 *   The complete parent form.
 * @param $form_state
 *   The form state of the parent form.
 */
function inline_entity_form_cleanup_row_form_state($form, &$form_state) {
  $element = inline_entity_form_get_element($form, $form_state);
  $delta = $form_state['triggering_element']['#ief_row_delta'];
  $entity_form = $element['entities'][$delta]['form'];
  inline_entity_form_cleanup_entity_form_state($entity_form, $form_state);
}

/**
 * IEF widget #element_validate callback: Required field validation.
 */
function inline_entity_form_required_field($element, &$form_state, $form) {
  $ief_id = $element['#ief_id'];
  $has_children = !empty($form_state['inline_entity_form'][$ief_id]['entities']);
  $form_open = !empty($form_state['inline_entity_form'][$ief_id]['form']);

  // If the add new / add existing form is open, its validation / submission
  // will do the job instead (either by preventing the parent form submission
  // or by adding a new referenced entity).
  if (!$has_children && !$form_open) {
    $instance = $form_state['inline_entity_form'][$ief_id]['instance'];
    form_error($element, t('!name field is required.', array(
      '!name' => $instance['label'],
    )));
  }
}

/**
 * Implements hook_field_attach_submit().
 */
function inline_entity_form_field_attach_submit($parent_entity_type, $parent_entity, $form, &$form_state) {
  list(, , $bundle_name) = entity_extract_ids($parent_entity_type, $parent_entity);
  foreach (field_info_instances($parent_entity_type, $bundle_name) as $instance_name => $instance) {
    if (isset($instance['widget']) && strpos($instance['widget']['type'], 'inline_entity_form') === 0) {
      $field_name = $instance['field_name'];
      if (!isset($form[$field_name])) {

        // The field wasn't found on this form, skip it.
        // Usually happens on stub entity forms that don't contain all fields.
        continue;
      }
      $langcode = $form[$field_name]['#language'];
      if (!isset($form[$field_name][$langcode]['#ief_id'])) {

        // The field is present on the form, but the IEF widget wasn't added,
        // usually due to inline_entity_form_field_widget_properties_alter().
        continue;
      }
      $ief_id = $form[$field_name][$langcode]['#ief_id'];
      if (empty($form_state['inline_entity_form'][$ief_id])) {

        // No data found, no need to do anything.
        continue;
      }
      $values = $form_state['inline_entity_form'][$ief_id];
      $entity_type = $values['settings']['entity_type'];
      $controller = inline_entity_form_get_controller($instance);
      $context = array(
        'parent_entity_type' => $parent_entity_type,
        'parent_entity' => $parent_entity,
      );

      // Delete any entities staged for deletion.
      if (!empty($values['delete'])) {
        $controller
          ->delete(array_values($values['delete']), $context);
      }

      // Respect the entity weights.
      uasort($values['entities'], 'drupal_sort_weight');

      // Go through the IEF data and assemble a list of ids.
      $entity_ids = array();
      $need_reset = FALSE;
      foreach ($values['entities'] as $item) {
        if ($item['needs_save']) {
          $controller
            ->save($item['entity'], $context);
          $need_reset = TRUE;
        }
        list($entity_id) = entity_extract_ids($entity_type, $item['entity']);
        $entity_ids[] = array(
          $values['settings']['column'] => $entity_id,
        );
      }

      // Prevent the entity from showing up in subsequent add forms.
      // @todo Investigate a cleaner fix.
      if (isset($form['#op']) && $form['#op'] == 'add' && $need_reset) {
        $form_state['inline_entity_form'][$ief_id]['entities'] = array();
      }
      if (!empty($entity_ids)) {

        // Set the list of ids as the field value.
        $parent_entity->{$field_name}[$langcode] = $entity_ids;
      }
    }
  }
}

/**
 * Cleans up the form state for a submitted entity form.
 *
 * After field_attach_submit() has run and the form has been closed, the form
 * state still contains field data in $form_state['field']. Unless that
 * data is removed, the next form with the same #parents (reopened add form,
 * for example) will contain data (i.e. uploaded files) from the previous form.
 *
 * @param $entity_form
 *   The entity form.
 * @param $form_state
 *   The form state of the parent form.
 */
function inline_entity_form_cleanup_entity_form_state($entity_form, &$form_state) {
  $info = entity_get_info($entity_form['#entity_type']);
  if (empty($info['fieldable'])) {

    // The entity type is not fieldable, nothing to cleanup.
    return;
  }
  list(, , $bundle) = entity_extract_ids($entity_form['#entity_type'], $entity_form['#entity']);
  $instances = field_info_instances($entity_form['#entity_type'], $bundle);
  foreach ($instances as $instance) {
    $field_name = $instance['field_name'];
    if (!empty($entity_form[$field_name]['#parents'])) {
      $parents = $entity_form[$field_name]['#parents'];
      array_pop($parents);
      $langcode = $entity_form[$field_name]['#language'];
      $field_state = array();
      field_form_set_state($parents, $field_name, $langcode, $form_state, $field_state);
    }
  }
}

/**
 * Returns an IEF widget nearest to the triggering element.
 */
function inline_entity_form_get_element($form, $form_state) {
  $element = array();
  $array_parents = $form_state['triggering_element']['#array_parents'];

  // Remove the action and the actions container.
  $array_parents = array_slice($array_parents, 0, -2);
  while (!isset($element['#ief_root'])) {
    $element = drupal_array_get_nested_value($form, $array_parents);
    array_pop($array_parents);
  }
  return $element;
}

/**
 * Creates a new entity of the given type and bundle.
 *
 * @param $entity_type
 *   Entity type.
 * @param $bundle
 *   Bundle.
 * @param $language
 *   (Optional) The language to set, if the entity has a language key.
 */
function inline_entity_form_create_entity($entity_type, $bundle, $language = NULL) {
  $entity_info = entity_get_info($entity_type);
  $bundle_key = $entity_info['entity keys']['bundle'];
  $default_values = array();

  // If the bundle key exists, it must always be set on an entity.
  if (!empty($bundle_key)) {
    $default_values[$bundle_key] = $bundle;
  }

  // Set the language if we have both a language key and a language value.
  if (isset($language) && !empty($entity_info['entity keys']['language'])) {
    $language_key = $entity_info['entity keys']['language'];
    $default_values[$language_key] = $language;
  }
  return entity_create($entity_type, $default_values);
}

/**
 * Themes the table showing existing entity references in the widget.
 *
 * @param $variables
 *   Contains the form element data from $element['entities'].
 */
function theme_inline_entity_form_entity_table($variables) {
  $form = $variables['form'];
  $entity_type = $form['#entity_type'];
  $fields = $form['#table_fields'];
  $has_tabledrag = inline_entity_form_has_tabledrag($form);

  // Sort the fields by weight.
  uasort($fields, 'drupal_sort_weight');
  $header = array();
  if ($has_tabledrag) {
    $header[] = array(
      'data' => '',
      'class' => array(
        'ief-tabledrag-header',
      ),
    );
    $header[] = array(
      'data' => t('Sort order'),
      'class' => array(
        'ief-sort-order-header',
      ),
    );
  }

  // Add header columns for each field.
  $first = TRUE;
  foreach ($fields as $field_name => $field) {
    $column = array(
      'data' => $field['label'],
    );

    // The first column gets a special class.
    if ($first) {
      $column['class'] = array(
        'ief-first-column-header',
      );
      $first = FALSE;
    }
    $header[] = $column;
  }
  $header[] = t('Operations');

  // Build an array of entity rows for the table.
  $rows = array();
  foreach (element_children($form) as $key) {
    $entity = $form[$key]['#entity'];
    list($entity_id) = entity_extract_ids($entity_type, $entity);

    // Many field formatters (such as the ones for files and images) need
    // certain data that might be missing on unsaved entities because the field
    // load hooks haven't run yet. Because of that, those hooks are invoked
    // explicitly. This is the same trick used by node_preview().
    if ($form[$key]['#needs_save']) {
      _field_invoke_multiple('load', $entity_type, array(
        $entity_id => $entity,
      ));
    }
    $row_classes = array(
      'ief-row-entity',
    );
    $cells = array();
    if ($has_tabledrag) {
      $cells[] = array(
        'data' => '',
        'class' => array(
          'ief-tabledrag-handle',
        ),
      );
      $cells[] = drupal_render($form[$key]['delta']);
      $row_classes[] = 'draggable';
    }

    // Add a special class to rows that have a form underneath, to allow
    // for additional styling.
    if (!empty($form[$key]['form'])) {
      $row_classes[] = 'ief-row-entity-form';
    }

    // Add fields that represent the entity.
    $wrapper = entity_metadata_wrapper($entity_type, $entity);
    foreach ($fields as $field_name => $field) {
      $data = '';
      if ($field['type'] == 'property') {
        $property = $wrapper->{$field_name};

        // label() returns human-readable versions of token and list properties.
        $data = $property
          ->label() ? $property
          ->label() : $property
          ->value();
        $data = empty($field['sanitized']) ? check_plain($data) : $data;
      }
      elseif ($field['type'] == 'field' && isset($entity->{$field_name})) {
        $display = array(
          'label' => 'hidden',
        ) + $field;

        // The formatter needs to be under the 'type' key.
        if (isset($display['formatter'])) {
          $display['type'] = $display['formatter'];
          unset($display['formatter']);
        }
        $renderable_data = field_view_field($entity_type, $entity, $field_name, $display);

        // The field has specified an exact delta to display.
        if (isset($field['delta'])) {
          if (!empty($renderable_data[$field['delta']])) {
            $renderable_data = $renderable_data[$field['delta']];
          }
          else {

            // The field has no value for the specified delta, show nothing.
            $renderable_data = array();
          }
        }
        $data = drupal_render($renderable_data);
      }
      elseif ($field['type'] == 'callback' && isset($field['render_callback']) && is_callable($field['render_callback'])) {
        $data = call_user_func($field['render_callback'], $entity_type, $entity);
      }
      $cells[] = array(
        'data' => $data,
        'class' => array(
          'inline-entity-form-' . $entity_type . '-' . $field_name,
        ),
      );
    }

    // Add the buttons belonging to the "Operations" column.
    $cells[] = drupal_render($form[$key]['actions']);

    // Create the row.
    $rows[] = array(
      'data' => $cells,
      'class' => $row_classes,
    );

    // If the current entity array specifies a form, output it in the next row.
    if (!empty($form[$key]['form'])) {
      $row = array(
        array(
          'data' => drupal_render($form[$key]['form']),
          'colspan' => count($fields) + 1,
        ),
      );
      $rows[] = array(
        'data' => $row,
        'class' => array(
          'ief-row-form',
        ),
        'no_striping' => TRUE,
      );
    }
  }
  if (!empty($rows)) {
    $id = 'ief-entity-table-' . $form['#id'];
    if ($has_tabledrag) {

      // Add the tabledrag JavaScript.
      drupal_add_tabledrag($id, 'order', 'sibling', 'ief-entity-delta');
    }

    // Return the themed table.
    $table_attributes = array(
      'id' => $id,
      'class' => array(
        'ief-entity-table',
      ),
    );
    return theme('table', array(
      'header' => $header,
      'rows' => $rows,
      'sticky' => FALSE,
      'attributes' => $table_attributes,
    ));
  }
}

/**
 * Returns whether tabledrag should be enabled for the given table.
 *
 * @param $element
 *   The form element representing the IEF table.
 *
 * @return
 *   TRUE if tabledrag should be enabled, FALSE otherwise.
 */
function inline_entity_form_has_tabledrag($element) {
  $children = element_children($element);

  // If there is only one row, disable tabletrag.
  if (count($children) == 1) {
    return FALSE;
  }

  // If one of the rows is in form context, disable tabledrag.
  foreach ($children as $key) {
    if (!empty($element[$key]['form'])) {
      return FALSE;
    }
  }
  return TRUE;
}

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

/**
 * Move form elements into fieldsets for presentation purposes.
 *
 * Inline forms use #tree = TRUE to keep their values in a hierarchy for
 * easier storage. Moving the form elements into fieldsets during form building
 * would break up that hierarchy, so it's not an option for Field API fields.
 * Therefore, we wait until the pre_render stage, where any changes we make
 * affect presentation only and aren't reflected in $form_state['values'].
 */
function inline_entity_form_pre_render_add_fieldset_markup($form) {
  $sort = array();
  foreach (element_children($form) as $key) {
    $element = $form[$key];

    // In our form builder functions, we added an arbitrary #fieldset property
    // to any element that belongs in a fieldset. If this form element has that
    // property, move it into its fieldset.
    if (isset($element['#fieldset']) && isset($form[$element['#fieldset']])) {
      $form[$element['#fieldset']][$key] = $element;

      // Remove the original element this duplicates.
      unset($form[$key]);

      // Mark the fieldset for sorting.
      if (!in_array($key, $sort)) {
        $sort[] = $element['#fieldset'];
      }
    }
  }

  // Sort all fieldsets, so that element #weight stays respected.
  foreach ($sort as $key) {
    uasort($form[$key], 'element_sort');
  }
  return $form;
}

Functions

Namesort descending Description
inline_entity_form_autocomplete Returns output for inline entity form autocompletes.
inline_entity_form_cleanup_entity_form_state Cleans up the form state for a submitted entity form.
inline_entity_form_cleanup_form_state Button #submit callback: Cleans up form state for a closed entity form.
inline_entity_form_cleanup_row_form_state Button #submit callback: Cleans up form state for a closed entity row form.
inline_entity_form_close_all_forms Closes all open IEF forms.
inline_entity_form_close_child_forms Button #submit callback: Closes all open child forms in the IEF widget.
inline_entity_form_close_form Button #submit callback: Closes a form in the IEF widget.
inline_entity_form_close_row_form Button #submit callback: Closes a row form in the IEF widget.
inline_entity_form_create_entity Creates a new entity of the given type and bundle.
inline_entity_form_entity_delete Implements hook_entity_delete().
inline_entity_form_entity_form Wraps and returns the entity form provided by the passed-in controller.
inline_entity_form_entity_form_submit Submits an entity form.
inline_entity_form_entity_form_validate Validates an entity form.
inline_entity_form_entity_info_alter Implements hook_entity_info_alter().
inline_entity_form_field_attach_submit Implements hook_field_attach_submit().
inline_entity_form_field_widget_error Implements hook_field_widget_error().
inline_entity_form_field_widget_form Implements hook_field_widget_form().
inline_entity_form_field_widget_info Implements hook_field_widget_info().
inline_entity_form_field_widget_properties_alter Implements hook_field_widget_properties_alter().
inline_entity_form_field_widget_settings_form Implements hook_field_widget_settings_form().
inline_entity_form_form_alter Implements hook_form_alter().
inline_entity_form_get_controller Returns the Inline Entity Form controller for the passed-in reference field.
inline_entity_form_get_element Returns an IEF widget nearest to the triggering element.
inline_entity_form_has_tabledrag Returns whether tabledrag should be enabled for the given table.
inline_entity_form_menu Implements hook_menu().
inline_entity_form_open_form Button #submit callback: Opens a form in the IEF widget.
inline_entity_form_open_row_form Button #submit callback: Opens a row form in the IEF widget.
inline_entity_form_pre_render_add_fieldset_markup Move form elements into fieldsets for presentation purposes.
inline_entity_form_reference_form Provides the form for adding existing entities through an autocomplete field.
inline_entity_form_reference_form_submit Submits the form for adding existing entities.
inline_entity_form_reference_form_validate Validates the form for adding existing entities.
inline_entity_form_remove_confirm Remove form submit callback.
inline_entity_form_remove_form Wraps and returns the remove form provided by the passed-in controller.
inline_entity_form_required_field IEF widget #element_validate callback: Required field validation.
inline_entity_form_settings Introspects field and instance settings, and determines the correct settings for the functioning of the widget.
inline_entity_form_submit Submits entity forms by calling their #ief_element_submit callbacks.
inline_entity_form_theme Implements hook_theme().
inline_entity_form_trigger_submit Button #submit callback: Triggers submission of entity forms.
inline_entity_form_update_row_weights Updates entity weights based on their weights in the widget.
theme_inline_entity_form_entity_table Themes the table showing existing entity references in the widget.
_inline_entity_form_attach_css Attaches theme specific CSS files.
_inline_entity_form_autocomplete_validate #element_validate callback for the IEF autocomplete field.

Constants

Namesort descending Description
IEF_ENTITY_UNLINK Flag indicating that the entity should be unlinked.
IEF_ENTITY_UNLINK_DELETE Flag indicating that the entity should be unlinked and deleted.