Skip to content

Instantly share code, notes, and snippets.

@andypost
Created March 24, 2016 23:18
Show Gist options
  • Save andypost/44f3b1bb5147d563a983 to your computer and use it in GitHub Desktop.
Save andypost/44f3b1bb5147d563a983 to your computer and use it in GitHub Desktop.
<?php
/**
* @file
* Contains \Drupal\idevels_proconsort_utility\Plugin\Field\FieldWidget\InlineEntityFormCustom.
*/
namespace Drupal\idevels_proconsort_utility\Plugin\Field\FieldWidget;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Render\Element;
use Drupal\inline_entity_form\Plugin\Field\FieldWidget\InlineEntityFormComplex;
/**
* Custom inline widget.
*
* @FieldWidget(
* id = "inline_entity_form_custom",
* label = @Translation("Inline entity form - Complex - custom"),
* field_types = {
* "entity_reference"
* },
* multiple_values = true
* )
*/
class InlineEntityFormCustom extends InlineEntityFormComplex implements ContainerFactoryPluginInterface {
/**
* {@inheritdoc}
*/
public static function defaultSettings() {
$defaults = parent::defaultSettings();
$defaults += [
'new_button' => '',
'existing_button' => '',
'delete_dialog' => '',
];
return $defaults;
}
/**
* {@inheritdoc}
*/
public function settingsForm(array $form, FormStateInterface $form_state) {
$element = parent::settingsForm($form, $form_state);
$states_prefix = 'fields[' . $this->fieldDefinition->getName() . '][settings_edit_form][settings]';
$weight = 1;
$element['allow_new']['#weight'] = $weight;
$element['new_button'] = [
'#type' => 'textfield',
'#title' => $this->t('Title for create new button.'),
'#default_value' => $this->getSetting('new_button'),
'#states' => [
'visible' => [
':input[name="' . $states_prefix . '[allow_new]"]' => array('checked' => TRUE),
],
],
'#weight' => ++$weight,
];
$element['allow_existing']['#weight'] = ++$weight;
$element['existing_button'] = [
'#type' => 'textfield',
'#title' => $this->t('Title for create existing button.'),
'#default_value' => $this->getSetting('existing_button'),
'#states' => [
'visible' => [
':input[name="' . $states_prefix . '[allow_existing]"]' => array('checked' => TRUE),
],
],
'#weight' => ++$weight,
];
$element['match_operator']['#weight'] = ++$weight;
$element['delete_dialog'] = [
'#type' => 'textfield',
'#title' => $this->t('Delete dialog text.'),
'#default_value' => $this->getSetting('delete_dialog'),
'#weight' => ++$weight,
];
return $element;
}
/**
* {@inheritdoc}
*/
public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
if (!$this->canBuildForm($form_state)) {
return $element;
}
$settings = $this->getSettings();
$target_type = $this->getFieldSetting('target_type');
// Get the entity type labels for the UI strings.
$labels = $this->getEntityTypeLabels();
// Build a parents array for this element's values in the form.
$parents = array_merge($element['#field_parents'], array(
$items->getName(),
'form',
));
// 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.
$this->setIefId(sha1(implode('-', $parents)));
// Get the langcode of the parent entity.
$parent_langcode = $items->getParent()->getValue()->language()->getId();
// Determine the wrapper ID for the entire element.
$wrapper = 'inline-entity-form-' . $this->getIefId();
$element = array(
'#type' => 'fieldset',
'#tree' => TRUE,
'#description' => NULL,
'#prefix' => '<div id="' . $wrapper . '">',
'#suffix' => '</div>',
'#ief_id' => $this->getIefId(),
'#ief_root' => TRUE,
) + $element;
$element['#attached']['library'][] = 'inline_entity_form/widget';
// Initialize the IEF array in form state.
if (!$form_state->has(['inline_entity_form', $this->getIefId(), 'settings'])) {
$form_state->set(['inline_entity_form', $this->getIefId(), 'settings'], $this->getFieldSettings());
}
if (!$form_state->has(['inline_entity_form', $this->getIefId(), 'instance'])) {
$form_state->set(['inline_entity_form', $this->getIefId(), 'instance'], $this->fieldDefinition);
}
if (!$form_state->has(['inline_entity_form', $this->getIefId(), 'form'])) {
$form_state->set(['inline_entity_form', $this->getIefId(), 'form'], NULL);
}
if (!$form_state->has(['inline_entity_form', $this->getIefId(), 'array_parents'])) {
$form_state->set(['inline_entity_form', $this->getIefId(), 'array_parents'], $parents);
}
$entities = $form_state->get(['inline_entity_form', $this->getIefId(), 'entities']);
if (!isset($entities)) {
// Load the entities from the $items array and store them in the form
// state for further manipulation.
$form_state->set(['inline_entity_form', $this->getIefId(), 'entities'], array());
if (count($items)) {
foreach ($items as $delta => $item) {
if ($item->entity && is_object($item->entity)) {
$form_state->set(['inline_entity_form', $this->getIefId(), 'entities', $delta], array(
'entity' => $item->entity,
'_weight' => $delta,
'form' => NULL,
'needs_save' => FALSE,
));
}
}
}
$entities = $form_state->get(['inline_entity_form', $this->getIefId(), 'entities']);
}
// Remove any leftover data from removed entity references.
foreach ($entities as $key => $value) {
if (!isset($value) || !isset($value['entity'])) {
unset($entities[$key]);
}
}
// Build the "Multiple value" widget.
// TODO - does this belong in #element_validate?
$element['#element_validate'][] = [get_class($this), 'updateRowWeights'];
// Add the required element marker & validation.
if ($element['#required']) {
$element['#element_validate'][] = [get_class($this), 'requiredField'];
}
$element['entities'] = array(
'#tree' => TRUE,
'#theme' => 'inline_entity_form_entity_table',
'#entity_type' => $target_type,
);
// Get the fields that should be displayed in the table.
$target_bundles = $this->getTargetBundles();
$fields = $this->inlineFormHandler->getTableFields($target_bundles);
$context = array(
'parent_entity_type' => $this->fieldDefinition->getTargetEntityTypeId(),
'parent_bundle' => $this->fieldDefinition->getTargetBundle(),
'field_name' => $this->fieldDefinition->getName(),
'entity_type' => $target_type,
'allowed_bundles' => $target_bundles,
);
$this->moduleHandler->alter('inline_entity_form_table_fields', $fields, $context);
$element['entities']['#table_fields'] = $fields;
$weight_delta = max(ceil(count($entities) * 1.2), 50);
foreach ($entities as $key => $value) {
// Data used by theme_inline_entity_form_entity_table().
/** @var \Drupal\Core\Entity\EntityInterface $entity */
$entity = $value['entity'];
$element['entities'][$key]['#label'] = $this->inlineFormHandler->getEntityLabel($value['entity']);
$element['entities'][$key]['#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]['title'] = array();
$element['entities'][$key]['delta'] = array(
'#type' => 'value',
'#value' => $value['_weight'],
);
// Add the appropriate form.
if ($value['form'] == 'edit') {
$element['entities'][$key]['form'] = [
'#type' => 'container',
'#attributes' => ['class' => ['ief-form', 'ief-form-row']],
'inline_entity_form' => $this->getInlineEntityForm(
$value['form'],
$entity->bundle(),
$parent_langcode,
$key,
array_merge($parents, ['inline_entity_form', 'entities', $key, 'form']),
$entity
),
];
$element['entities'][$key]['form']['inline_entity_form']['#process'] = [
['\Drupal\inline_entity_form\Element\InlineEntityForm', 'processEntityForm'],
[get_class($this), 'addIefSubmitCallbacks'],
[get_class($this), 'buildEntityFormActions'],
];
}
elseif ($value['form'] == 'remove') {
$element['entities'][$key]['form'] = [
'#type' => 'container',
'#attributes' => ['class' => ['ief-form', 'ief-form-row']],
// Used by Field API and controller methods to find the relevant
// values in $form_state.
'#parents' => array_merge($parents, ['entities', $key, 'form']),
// Store the entity on the form, later modified in the controller.
'#entity' => $entity,
// Identifies the IEF widget to which the form belongs.
'#ief_id' => $this->getIefId(),
// Identifies the table row to which the form belongs.
'#ief_row_delta' => $key,
];
$this->buildRemoveForm($element['entities'][$key]['form']);
}
}
else {
$row = &$element['entities'][$key];
$row['title'] = array();
$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.
$entity_id = $entity->id();
if (empty($entity_id) || $entity->access('update')) {
$row['actions']['ief_entity_edit'] = array(
'#type' => 'submit',
'#value' => $this->t('Edit'),
'#name' => 'ief-' . $this->getIefId() . '-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',
);
}
// 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) || $settings['allow_existing'] || $entity->access('delete')) {
$row['actions']['ief_entity_remove'] = array(
'#type' => 'submit',
'#value' => $this->t('Remove'),
'#name' => 'ief-' . $this->getIefId() . '-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',
);
}
}
}
$entities_count = count($entities);
$cardinality = $this->fieldDefinition->getFieldStorageDefinition()->getCardinality();
if ($cardinality > 1) {
// Add a visual cue of cardinality count.
$message = $this->t('You have added @entities_count out of @cardinality_count allowed @label.', array(
'@entities_count' => $entities_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 > 0 && $entities_count == $cardinality) {
return $element;
}
$target_bundles_count = count($target_bundles);
$hide_cancel = FALSE;
// If the field is required and empty try to open one of the forms.
if (empty($entities) && $this->fieldDefinition->isRequired()) {
if ($settings['allow_existing'] && !$settings['allow_new']) {
$form_state->set(['inline_entity_form', $this->getIefId(), 'form'], 'ief_add_existing');
$hide_cancel = TRUE;
}
elseif ($target_bundles_count == 1 && $settings['allow_new'] && !$settings['allow_existing']) {
$bundle = reset($target_bundles);
// The parent entity type and bundle must not be the same as the inline
// entity type and bundle, to prevent recursion.
$parent_entity_type = $this->fieldDefinition->getTargetEntityTypeId();
$parent_bundle = $this->fieldDefinition->getTargetBundle();
if ($parent_entity_type != $target_type || $parent_bundle != $bundle) {
$form_state->set(['inline_entity_form', $this->getIefId(), 'form'], 'add');
$form_state->set(['inline_entity_form', $this->getIefId(), 'form settings'], array(
'bundle' => $bundle,
));
$hide_cancel = TRUE;
}
}
}
// If no form is open, show buttons that open one.
$open_form = $form_state->get(['inline_entity_form', $this->getIefId(), 'form']);
if (empty($open_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 ($settings['allow_new'] && $target_bundles_count) {
// Let the user select the bundle, if multiple are available.
if ($target_bundles_count > 1) {
$bundles = array();
foreach ($this->entityTypeBundleInfo->getBundleInfo($target_type) as $bundle_name => $bundle_info) {
if (in_array($bundle_name, $target_bundles)) {
$bundles[$bundle_name] = $bundle_info['label'];
}
}
$element['actions']['bundle'] = array(
'#type' => 'select',
'#options' => $bundles,
);
}
else {
$element['actions']['bundle'] = array(
'#type' => 'value',
'#value' => reset($target_bundles),
);
}
$element['actions']['ief_add'] = array(
'#type' => 'submit',
'#value' => empty($settings['new_button']) ? $this->t('Add new @type_singular', array('@type_singular' => $labels['singular'])) : $settings['new_button'],
'#name' => 'ief-' . $this->getIefId() . '-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 ($settings['allow_existing']) {
$element['actions']['ief_add_existing'] = array(
'#type' => 'submit',
'#value' => empty($settings['existing_button']) ? $this->t('Add existing @type_singular', array('@type_singular' => $labels['singular'])) : $settings['existing_button'],
'#name' => 'ief-' . $this->getIefId() . '-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.
if ($form_state->get(['inline_entity_form', $this->getIefId(), 'form']) == 'add') {
$element['form'] = [
'#type' => 'fieldset',
'#attributes' => ['class' => ['ief-form', 'ief-form-bottom']],
'inline_entity_form' => $this->getInlineEntityForm(
'add',
$this->determineBundle($form_state),
$parent_langcode,
NULL,
array_merge($parents, ['inline_entity_form'])
)
];
$element['form']['inline_entity_form']['#process'] = [
['\Drupal\inline_entity_form\Element\InlineEntityForm', 'processEntityForm'],
[get_class($this), 'addIefSubmitCallbacks'],
[get_class($this), 'buildEntityFormActions'],
];
}
elseif ($form_state->get(['inline_entity_form', $this->getIefId(), 'form']) == 'ief_add_existing') {
$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' => $this->getIefId(),
// Used by Field API and controller methods to find the relevant
// values in $form_state.
'#parents' => array_merge($parents),
// Pass the current entity type.
'#entity_type' => $target_type,
// Pass the langcode of the parent entity,
'#parent_language' => $parent_langcode,
// Pass the widget specific labels.
'#ief_labels' => $this->getEntityTypeLabels(),
);
$element['form'] += inline_entity_form_reference_form($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 ($open_form == 'add') {
$process_element = &$element['form']['inline_entity_form'];
}
elseif ($open_form == 'ief_add_existing') {
$process_element = &$element['form'];
}
$process_element['#process'][] = [get_class($this), 'hideCancel'];
}
// No entities have been added. Remove the outer fieldset to reduce
// visual noise caused by having two titles.
if (empty($entities)) {
$element['#type'] = 'container';
}
}
return $element;
}
/**
* Builds remove form.
*
* @param array $form
* Form array structure.
*/
protected function buildRemoveForm(&$form) {
/** @var \Drupal\Core\Entity\EntityInterface $entity */
$entity = $form['#entity'];
$entity_id = $entity->id();
$entity_label = $this->inlineFormHandler->getEntityLabel($entity);
$labels = $this->getEntityTypeLabels();
$settings = $this->getSettings();
if ($entity_label) {
$message = $this->t('Are you sure you want to remove %label?', ['%label' => $entity_label]);
}
else {
$message = $this->t('Are you sure you want to remove this %entity_type?', ['%entity_type' => $labels['singular']]);
}
$form['message'] = [
'#theme_wrappers' => ['container'],
'#markup' => $message,
];
if (!empty($entity_id) && $this->getSetting('allow_existing') && $entity->access('delete')) {
$form['delete'] = [
'#type' => 'checkbox',
'#title' => empty($settings['delete_dialog']) ? $this->t('Delete this @type_singular from the system.', array('@type_singular' => $labels['singular'])) : $settings['delete_dialog'],
];
}
// Build a deta suffix that's appended to button #name keys for uniqueness.
$delta = $form['#ief_id'] . '-' . $form['#ief_row_delta'];
// Add actions to the form.
$form['actions'] = [
'#type' => 'container',
'#weight' => 100,
];
$form['actions']['ief_remove_confirm'] = [
'#type' => 'submit',
'#value' => $this->t('Remove'),
'#name' => 'ief-remove-confirm-' . $delta,
'#limit_validation_errors' => [$form['#parents']],
'#ajax' => [
'callback' => 'inline_entity_form_get_element',
'wrapper' => 'inline-entity-form-' . $form['#ief_id'],
],
'#submit' => [[get_class($this), 'submitConfirmRemove']],
'#ief_row_delta' => $form['#ief_row_delta'],
];
$form['actions']['ief_remove_cancel'] = [
'#type' => 'submit',
'#value' => $this->t('Cancel'),
'#name' => 'ief-remove-cancel-' . $delta,
'#limit_validation_errors' => [],
'#ajax' => [
'callback' => 'inline_entity_form_get_element',
'wrapper' => 'inline-entity-form-' . $form['#ief_id'],
],
'#submit' => [[get_class($this), 'submitCloseRow']],
'#ief_row_delta' => $form['#ief_row_delta'],
];
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment