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] = '
' . $result . '
';
}
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_property_info_alter().
*
* @todo Remove once Commerce 1.5 gets released with #1860248 committed.
*/
function inline_entity_form_entity_property_info_alter(&$info) {
if (!empty($info['commerce_product'])) {
$properties = &$info['commerce_product']['properties'];
$properties['status']['options list'] = 'inline_entity_form_product_status_options_list';
}
}
/**
* Returns the options list for the product status property.
*
* @todo Remove once Commerce 1.5 gets released with #1860248 committed.
*/
function inline_entity_form_product_status_options_list() {
return array(
0 => t('Disabled'),
1 => t('Active'),
);
}
/**
* 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' => '',
'#suffix' => '
',
'#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' => '' . $message . '
',
);
}
// 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;
}