commerce_inventory-8.x-1.0-alpha6/src/Element/InventoryAdjustment.php
src/Element/InventoryAdjustment.php
<?php
namespace Drupal\commerce_inventory\Element;
use Drupal\commerce_inventory\Entity\InventoryItemInterface;
use Drupal\commerce_inventory\Plugin\Commerce\InventoryAdjustmentType\InventoryAdjustmentTypeInterface;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\NestedArray;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\ReplaceCommand;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element\FormElement;
use Drupal\Core\Url;
/**
* Provides an Commerce Inventory Adjustment form element.
*
* The #default_value accepted by this element is either an entity object or an
* array of entity objects.
*
* @FormElement("commerce_inventory_adjustment")
*/
class InventoryAdjustment extends FormElement {
/**
* {@inheritdoc}
*/
public function getInfo() {
$class = get_class($this);
// List of InventoryAdjustment constants.
return [
'#field_title_format' => NULL,
'#ajax_submission' => FALSE,
'#hidden_fields' => [],
'#table_format' => NULL,
'#title_format' => NULL,
'#default_value' => NULL,
'#element_validate' => [
[$class, 'validateAdjustment'],
],
'#input' => TRUE,
'#multiple' => FALSE,
'#process' => [
[$class, 'processAdjustment'],
],
'#theme_wrappers' => ['fieldset'],
'#tree' => TRUE,
];
}
/**
* The Adjustment Type manager.
*
* @return \Drupal\commerce_inventory\InventoryAdjustmentTypeManager
* The Adjustment Type manager.
*/
public static function getAdjustmentTypeManager() {
return \Drupal::service('plugin.manager.commerce_inventory_adjustment_type');
}
/**
* The Inventory Adjustment storage.
*
* @return \Drupal\commerce_inventory\Entity\Storage\InventoryAdjustmentStorageInterface
* The Inventory Adjustment storage.
*/
public static function getAdjustmentStorage() {
return \Drupal::entityTypeManager()->getStorage('commerce_inventory_adjustment');
}
/**
* The Inventory Item storage.
*
* @return \Drupal\commerce_inventory\Entity\Storage\InventoryItemStorageInterface
* The Inventory Item storage.
*/
public static function getInventoryItemStorage() {
return \Drupal::entityTypeManager()->getStorage('commerce_inventory_item');
}
/**
* Ensures all keys are set on the provided value.
*
* @param array $value
* The value.
*
* @return array
* The modified value.
*/
public static function applyDefaultValues(array $value) {
$properties = [
'item' => NULL,
'quantity' => NULL,
'related_item' => NULL,
'type' => 'increase',
];
foreach ($properties as $property_key => $property) {
if (!isset($value[$property_key])) {
$value[$property_key] = $property;
}
}
return $value;
}
/**
* {@inheritdoc}
*/
public static function valueCallback(&$element, $input, FormStateInterface $form_state) {
// Ensure both the default value and the input have all keys set.
$element['#default_value'] = (isset($element['#default_value'])) ? (array) $element['#default_value'] : [];
$element['#default_value'] = self::applyDefaultValues($element['#default_value']);
if ($form_state->get('force_default_entity_reload')) {
if ($element['#default_value']['item'] instanceof InventoryItemInterface) {
$element['#default_value']['item'] = self::getInventoryItemStorage()->load($element['#default_value']['item']->id());
}
if ($element['#default_value']['related_item'] instanceof InventoryItemInterface) {
$element['#default_value']['related_item'] = self::getInventoryItemStorage()->load($element['#default_value']['related_item']->id());
}
$form_state->set('force_default_entity_reload', FALSE);
}
$element['#default_value']['item'] = self::getInventoryItemEntity($element['#default_value']['item']);
$element['#default_value']['related_item'] = self::getInventoryItemEntity($element['#default_value']['related_item']);
if (is_array($input) && array_key_exists('quantity', $input) && !is_null($input['quantity'])) {
$input['quantity'] = abs($input['quantity']);
}
// Apply defaults to input.
$input = (array) $input;
$input += $element['#default_value'];
$input['item'] = self::getInventoryItemEntity($input['item']);
$input['related_item'] = (!is_null($input['item'])) ? self::getInventoryItemEntity($input['related_item']) : NULL;
$input['type_plugin'] = self::getAdjustmentTypeManager()->createInstance($input['type']);
return is_array($input) ? $input : $element['#default_value'];
}
/**
* Submit callback for the "Adjust" button.
*
* @param array $form
* The form element to process.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*/
public static function makeAdjustment(array $form, FormStateInterface $form_state) {
$triggering_element = $form_state->getTriggeringElement();
$parents = array_slice($triggering_element['#parents'], 0, -1);
$element = NestedArray::getValue($form, $parents);
// Exit early if Adjust submission wasn't clicked.
if (!isset($triggering_element['#name']) || $triggering_element['#name'] !== $element['apply']['#name']) {
return;
}
// Get values.
$adjustment_type_id = &$element['#value']['type'];
/** @var \Drupal\commerce_inventory\Entity\InventoryItemInterface $inventory_item */
$inventory_item = &$element['#value']['item'];
/** @var \Drupal\commerce_inventory\Entity\InventoryItemInterface $related_item */
$related_item = &$element['#value']['related_item'];
$quantity = &$element['#value']['quantity'];
// Create adjustment.
self::getAdjustmentStorage()->createAdjustment($adjustment_type_id, $inventory_item, $quantity, [], $related_item);
// Clear the input and rebuilt form.
$user_input = &$form_state->getUserInput();
NestedArray::setValue($user_input, $element['#parents'], '');
$destination = \Drupal::destination();
if ($destination->get()) {
$path = \Drupal::destination()->get();
$url = Url::fromUserInput($path);
$form_state->setRedirectUrl($url);
}
else {
$form_state->setRebuild();
$form_state->set('force_default_entity_reload', TRUE);
}
}
/**
* Adds adjustment functionality to a form element.
*
* Properties include:
* - #field_title_format: String of the word format to use for each field
* label in the form element. Available options include 'basic',
* 'descriptive', 'sentence', and 'default' (NULL).
* - #handle_submission: Boolean whether the element should handle
* submission and create and save a new Inventory Adjustment. Defaults to
* FALSE.
* - #title_format: String of the word format to use for the form element's
* label. Available options include 'hidden', 'sentence',
* and 'default' (NULL).
* - #table_format: String of the table display format to use. Available
* options include 'hidden', and 'default' (NULL).
* - #hidden_fields: An array of which fields to hide on the form. Available
* options include 'item', 'quantity', 'related_item', 'type'.
*
* Example usage:
* @code
* $form['element'] = [
* '#type' => 'commerce_inventory_adjustment',
* '#default_value' => [
* 'item' => $inventory_item_entity,
* 'type' => 'move_to',
* ],
* '#field_title_format' => 'sentence',
* '#hidden_fields' => ['type', 'item'],
* '#title_format' => 'hidden',
* '#table_format' => 'hidden'
* ];
* @endcode
*
* @param array $element
* The form element to process.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
* @param array $complete_form
* The complete form structure.
*
* @return array
* The form element.
*
* @throws \InvalidArgumentException
* Exception thrown when the #provider is missing or invalid.
*/
public static function processAdjustment(array &$element, FormStateInterface $form_state, array &$complete_form) {
// Default value shortcuts.
/** @var \Drupal\commerce_inventory\Entity\InventoryItemInterface $adjustment_item */
$adjustment_item = &$element['#value']['item'];
/** @var \Drupal\commerce_inventory\Plugin\Commerce\InventoryAdjustmentType\InventoryAdjustmentTypeInterface $adjustment_type */
$adjustment_type = &$element['#value']['type_plugin'];
/** @var \Drupal\commerce_inventory\Entity\InventoryItemInterface $related_item */
$related_item = &$element['#value']['related_item'];
$quantity = &$element['#value']['quantity'];
// Initialize element settings.
$element_wrapper_id = self::getElementId($element['#parents'], 'adjustment-ajax-wrapper');
$element += [
'#prefix' => '<div id="' . $element_wrapper_id . '">',
'#suffix' => '</div>',
'#adjustment_settings' => [],
'#wrapper_id' => $element_wrapper_id,
// @todo add class based on title and field format
];
// Format element title.
switch ($element['#title_format']) {
case 'sentence':
$title_replacements = [
'@item' => t('(Select item)'),
'@location' => t("(Location)"),
'@purchasable_entity' => t("(Purchasable item)"),
'@related_location' => t('(Select location)'),
'@adjustment_verb' => $adjustment_type->getVerbLabel(),
'@adjustment_preposition' => $adjustment_type->getPrepositionLabel(),
'@related_preposition' => $adjustment_type->getRelatedPrepositionLabel(),
];
if ($adjustment_item instanceof InventoryItemInterface) {
$title_replacements['@item'] = $adjustment_item->label();
$title_replacements['@location'] = $adjustment_item->getLocation()->label();
$title_replacements['@purchasable_entity'] = $adjustment_item->getPurchasableEntity()->label();
}
if ($adjustment_type->hasRelatedAdjustmentType() && $related_item instanceof InventoryItemInterface) {
$title_replacements['@related_location'] = $related_item->getLocation()->label();
}
$element_label = t($adjustment_type->getSentenceLabelTemplate(), $title_replacements);
$element['#title'] = Unicode::ucfirst($element_label);
break;
case 'hidden':
break;
default:
$element['#title'] = Unicode::ucfirst($adjustment_type->getLabel());
break;
}
// @todo throw warning if either adjustment sends total quantity into the negative
// @todo add comment field?
// Add adjustment-item field.
self::addItemField($element, $form_state);
// Add related-item field.
self::addRelatedItemField($element, $form_state);
// Add quantity field.
self::addQuantityField($element, $form_state);
// Add submit field.
self::addSubmitField($element, $form_state);
// Add adjustment-type field.
self::addTypeField($element, $form_state);
// Add adjustment table.
self::addTable($element, $form_state);
return $element;
}
/**
* Validation callback for an inventory adjustment element.
*
* @param array $element
* The element being processed.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
* @param array $complete_form
* The complete form structure.
*/
public static function validateAdjustment(array &$element, FormStateInterface $form_state, array &$complete_form) {
/** @var \Drupal\commerce_inventory\Entity\InventoryItemInterface $adjustment_item */
$adjustment_item = &$element['#value']['item'];
/** @var \Drupal\commerce_inventory\Plugin\Commerce\InventoryAdjustmentType\InventoryAdjustmentTypeInterface $adjustment_type */
$adjustment_type = &$element['#value']['type_plugin'];
/** @var \Drupal\commerce_inventory\Entity\InventoryItemInterface $related_item */
$related_item = &$element['#value']['related_item'];
$quantity = &$element['#value']['quantity'];
// Validate quantity is not zero.
if ($quantity == 0) {
$quantity_path = implode('][', $element['quantity']['#parents']);
$form_state->setErrorByName($quantity_path, t('Adjustment quantity required.'));
}
// Adjustment Inventory Item required. Error set by required validation.
if (!$adjustment_item instanceof InventoryItemInterface) {
return;
}
// Validate that the adjustment type is valid.
if (!$adjustment_type instanceof InventoryAdjustmentTypeInterface) {
$type_path = implode('][', $element['type']['#parents']);
$form_state->setErrorByName($type_path, t('Invalid adjustment type selected.'));
return;
}
// Do related adjustment validation.
if ($adjustment_type->hasRelatedAdjustmentType()) {
// Related Inventory Item required. Error set by required validation.
if (!$related_item instanceof InventoryItemInterface) {
return;
}
// Validate that both Inventory Items use the same purchasable entity.
if ($adjustment_item->getPurchasableEntityTypeId() !== $related_item->getPurchasableEntityTypeId() ||
$adjustment_item->getPurchasableEntityId() !== $related_item->getPurchasableEntityId()) {
$related_item_path = implode('][', $element['related_item']['#parents']);
$form_state->setErrorByName($related_item_path, t('Invalid related item selected.'));
return;
}
}
}
/**
* Add the Inventory Item field to the element.
*
* @param array $element
* The form element to process.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*/
protected static function addItemField(array &$element, FormStateInterface $form_state) {
/** @var \Drupal\commerce_inventory\Plugin\Commerce\InventoryAdjustmentType\InventoryAdjustmentTypeInterface $adjustment_type */
$adjustment_type = &$element['#value']['type_plugin'];
/** @var \Drupal\commerce_inventory\Entity\InventoryItemInterface $adjustment_item */
$adjustment_item = &$element['#default_value']['item'];
/** @var \Drupal\commerce_inventory\Entity\InventoryItemInterface $related_item */
$related_item = &$element['#value']['related_item'];
// Set field.
if (!in_array('item', $element['#hidden_fields'])) {
// Format field label.
switch ($element['#field_title_format']) {
case 'basic':
$field_label = t('Inventory item');
break;
case 'descriptive':
$field_label = Unicode::ucfirst($adjustment_type->getLabel());
break;
case 'sentence':
$field_label = Unicode::ucfirst($adjustment_type->getPrepositionLabel());
break;
default:
$field_label = Unicode::ucfirst(t('@adjustment_preposition item', [
'@adjustment_preposition' => $adjustment_type->getPrepositionLabel(),
]));
break;
}
// Add entity reference field.
$element['item'] = [
'#type' => 'entity_autocomplete',
'#title' => $field_label,
'#ajax' => [
'callback' => [get_called_class(), 'ajaxRefresh'],
'disable-refocus' => TRUE,
'event' => 'blur',
'wrapper' => $element['#wrapper_id'],
],
'#default_value' => $adjustment_item,
'#required' => TRUE,
'#selection_settings' => [],
'#target_type' => 'commerce_inventory_item',
'#weight' => 6,
];
}
elseif (is_null($adjustment_item)) {
throw new \InvalidArgumentException('Missing required adjustment item parameter.');
}
else {
$element['item'] = [
'#type' => 'value',
'#value' => $adjustment_item,
];
}
}
/**
* Add the Quantity field to the element.
*
* @param array $element
* The form element to process.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*/
protected static function addQuantityField(array &$element, FormStateInterface $form_state) {
/** @var \Drupal\commerce_inventory\Plugin\Commerce\InventoryAdjustmentType\InventoryAdjustmentTypeInterface $adjustment_type */
$adjustment_type = &$element['#value']['type_plugin'];
$adjustment_quantity = $element['#default_value']['quantity'];
if (!is_null($adjustment_quantity)) {
$adjustment_quantity = abs($adjustment_quantity);
}
// Format field label.
switch ($element['#field_title_format']) {
case 'sentence':
$field_label = Unicode::ucfirst($adjustment_type->getVerbLabel());
break;
default:
$field_label = t('Quantity');
}
// Add float field.
$element['quantity'] = [
'#type' => 'number',
'#title' => $field_label,
'#ajax' => [
'callback' => [get_called_class(), 'ajaxTableRefresh'],
'event' => 'keyup',
'effect' => 'fade',
'speed' => 'fast',
],
'#default_value' => $adjustment_quantity,
'#min' => 0,
'#required' => TRUE,
'#step' => 'any',
'#size' => 4,
'#weight' => 5,
'#attached' => [
'library' => [
'commerce_inventory/adjustment_ajax'
]
]
];
}
/**
* Add the related Inventory Item field to the element.
*
* @param array $element
* The form element to process.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*/
protected static function addRelatedItemField(array &$element, FormStateInterface $form_state) {
/** @var \Drupal\commerce_inventory\Entity\InventoryItemInterface $adjustment_item */
$adjustment_item = &$element['#value']['item'];
/** @var \Drupal\commerce_inventory\Plugin\Commerce\InventoryAdjustmentType\InventoryAdjustmentTypeInterface $adjustment_type */
$adjustment_type = &$element['#value']['type_plugin'];
/** @var \Drupal\commerce_inventory\Entity\InventoryItemInterface $related_item */
$related_item = &$element['#default_value']['related_item'];
// Exit early if there is no related adjustment.
if (!$adjustment_type->hasRelatedAdjustmentType()) {
return;
}
// Set field.
if (!in_array('related_item', $element['#hidden_fields'])) {
// Format field label.
switch ($element['#field_title_format']) {
case 'basic':
$field_label = t('Related adjustment location');
break;
case 'descriptive':
$field_label = Unicode::ucfirst(t('@adjustment_verb @related_preposition location', [
'@adjustment_verb' => $adjustment_type->getVerbLabel(),
'@related_preposition' => $adjustment_type->getRelatedPrepositionLabel(),
]));
break;
case 'sentence':
$field_label = Unicode::ucfirst($adjustment_type->getRelatedPrepositionLabel());
break;
default:
$field_label = Unicode::ucfirst(t('@related_preposition location', [
'@related_preposition' => $adjustment_type->getRelatedPrepositionLabel(),
]));
break;
}
// Add entity reference field.
$element['related_item'] = [
'#type' => 'entity_autocomplete',
'#title' => $field_label,
'#ajax' => [
'callback' => [get_called_class(), 'ajaxRefresh'],
'disable-refocus' => TRUE,
'event' => 'blur',
'wrapper' => $element['#wrapper_id'],
],
'#default_value' => $related_item,
'#disabled' => (!$adjustment_item instanceof InventoryItemInterface),
'#required' => TRUE,
'#selection_handler' => 'commerce_inventory_item',
'#selection_settings' => [
'entity' => $adjustment_item,
'label_field' => 'location_id',
'restrict_by' => 'purchasable_entity'
],
'#target_type' => 'commerce_inventory_item',
'#weight' => 6,
];
}
elseif (is_null($related_item)) {
throw new \InvalidArgumentException('Missing required related-adjustment item parameter.');
}
else {
$element['related_item'] = [
'#type' => 'value',
'#value' => $related_item,
];
}
}
/**
* Add the submit adjustment field to the element.
*
* @param array $element
* The form element to process.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*/
protected static function addSubmitField(array &$element, FormStateInterface $form_state) {
// Only add the submit field if this element handles adjustment creation.
if (in_array('apply', $element['#hidden_fields'])) {
return;
}
$element['apply'] = [
'#type' => 'submit',
'#access' => (!in_array('apply', $element['#hidden_fields'])),
'#limit_validation_errors' => [
$element['#parents'],
],
'#submit' => [
[get_called_class(), 'makeAdjustment'],
],
'#value' => t('Adjust'),
'#weight' => 7,
];
if ($element['#ajax_submission'] == TRUE) {
$element['apply']['#ajax'] = [
'callback' => [get_called_class(), 'ajaxRefresh'],
'wrapper' => $element['#wrapper_id'],
'event' => 'mousedown',
'keypress' => TRUE,
'prevent' => 'click'
];
}
}
/**
* Add the Adjustment Type field to the element.
*
* @param array $element
* The form element to process.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*/
protected static function addTypeField(array &$element, FormStateInterface $form_state) {
if (!in_array('type', $element['#hidden_fields'])) {
$element['type'] = [
'#type' => 'select',
'#title' => t('Adjustment'),
'#default_value' => $element['#default_value']['type'],
'#options' => array_map(function ($definition) {
return $definition['label'];
}, self::getAdjustmentTypeManager()->getDefinitions(TRUE)),
'#weight' => 4,
'#ajax' => [
'callback' => [get_called_class(), 'ajaxRefresh'],
'wrapper' => $element['#wrapper_id'],
],
];
}
else {
$element['type'] = [
'#type' => 'value',
'#value' => $element['#default_value']['type'],
];
}
}
/**
* Add the table to the element.
*
* @param array $element
* The form element to process.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*/
protected static function addTable(array &$element, FormStateInterface $form_state) {
// Exit early if table is hidden.
if ($element['#table_format'] == 'hidden') {
return;
}
// Default value shortcuts.
/** @var \Drupal\commerce_inventory\Entity\InventoryItemInterface $adjustment_item */
$adjustment_item = &$element['#value']['item'];
/** @var \Drupal\commerce_inventory\Plugin\Commerce\InventoryAdjustmentType\InventoryAdjustmentTypeInterface $adjustment_type */
$adjustment_type = &$element['#value']['type_plugin'];
/** @var \Drupal\commerce_inventory\Entity\InventoryItemInterface $related_item */
$related_item = &$element['#value']['related_item'];
$quantity = &$element['#value']['quantity'];
// Initialize table.
$table = [
'#type' => 'table',
'#caption' => NULL,
'#header' => [
'title' => t('Location'),
'current' => t('Current'),
'adjustment' => t('Adjustment'),
'updated' => t('Updated'),
],
'#sticky' => FALSE,
'#tree' => TRUE,
'#rows' => [],
'#weight' => 10,
];
if ($adjustment_item instanceof InventoryItemInterface && $adjustment_type instanceof InventoryAdjustmentTypeInterface) {
self::addTableRow($table, $adjustment_item, $adjustment_type->adjustQuantity($quantity, $adjustment_item->getQuantity(FALSE)));
}
if ($related_item instanceof InventoryItemInterface && $adjustment_type instanceof InventoryAdjustmentTypeInterface && $adjustment_type->hasRelatedAdjustmentType()) {
self::addTableRow($table, $related_item, $adjustment_type->getRelatedAdjustmentType()->adjustQuantity($quantity, $related_item->getQuantity(FALSE)));
}
$element['table'] = $table;
}
/**
* Add a row to the adjustment description table.
*
* @param array $table
* The table to add the row to.
* @param \Drupal\commerce_inventory\Entity\InventoryItemInterface $inventory_item
* The Inventory Item entity that is being adjusted.
* @param float $quantity_adjustment
* The numerical adjustment being made.
*
* @todo possible future table formats
* FULL
* | | Current | Adjustment | Updated |
* | -------------- Move from Item name -------------- |
* | On-Hand | 2 | -3 | 5 |
* | Available | 1 | -3 | 4 |
* | ----------- Move to Another Item name ----------- |
* | On-Hand | 1 | +3 | 4 |
* | Available | 0 | +3 | 3 |
*
* Default -> has mouse-over which shows availability
* | Location | Current | Adjustment | Updated |
* | Item Name | 2 | +3 | 5 |
*/
protected static function addTableRow(array &$table, InventoryItemInterface $inventory_item, $quantity_adjustment) {
$quantity_on_hand = $inventory_item->getQuantity(FALSE);
$row_id_on_hand = $inventory_item->uuid() . '-on-hand';
$text_adjustment_on_hand = ($quantity_adjustment > 0) ? '+' . $quantity_adjustment : strval($quantity_adjustment);
$table['#rows'][$row_id_on_hand] = [
'class' => [$row_id_on_hand],
'data' => [
'title' => [
'class' => ['title'],
'data' => $inventory_item->getLocation()->label(),
'header' => TRUE,
],
'current' => self::addTableCell($quantity_on_hand, ['current']),
'adjustment' => self::addTableCell($text_adjustment_on_hand, ['adjustment']),
'updated' => self::addTableCell($quantity_on_hand + $quantity_adjustment, ['updated']),
],
];
}
/**
* Add a cell in a table.
*
* @param string $markup
* The inner-markup of the table cell.
* @param array $classes
* An array of html classes to add to the table cell.
*
* @return array
* The created table-cell render data.
*/
protected static function addTableCell($markup, array $classes = []) {
return [
'class' => $classes,
'data' => [
'#markup' => $markup,
'#prefix' => '<div>',
'#suffix' => '</div>',
],
];
}
/**
* Extracts the entity ID from the autocompletion result.
*
* @param string $input
* The input coming from the autocompletion result.
*
* @return mixed|null
* An entity ID or NULL if the input does not contain one.
*/
public static function extractEntityIdFromAutocompleteInput($input) {
$match = NULL;
// Take "label (entity id)', match the ID from inside the parentheses.
// @todo Add support for entities containing parentheses in their ID.
// @see https://www.drupal.org/node/2520416
if (preg_match("/.+\s\(([^\)]+)\)/", $input, $matches)) {
$match = $matches[1];
}
return $match;
}
/**
* Gets the current element subform.
*
* @param array $form
* The complete form structure.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*
* @return array
* The element subform.
*/
protected static function getElementForm(array $form, FormStateInterface $form_state) {
$triggering_element = $form_state->getTriggeringElement();
$parents = $triggering_element['#array_parents'];
$triggering_element_name = array_pop($parents);
return NestedArray::getValue($form, $parents);
}
/**
* Generate a unique element ID based on an element's parent elements.
*
* @param array $parents
* The element's parents.
* @param string $suffix
* The suffix to append to the ID, describing the element or action.
*
* @return string
* The element ID.
*/
protected static function getElementId(array $parents, $suffix) {
return Html::getUniqueId(implode('-', $parents) . '-' . $suffix);
}
/**
* Process value for Inventory Item entity.
*
* @param mixed $value
* The value to validate and process.
*
* @return \Drupal\commerce_inventory\Entity\InventoryItemInterface|null
* The Inventory Item entity if found.
*/
public static function getInventoryItemEntity($value) {
// Exit early if there is nothing to do.
if ($value instanceof InventoryItemInterface || is_null($value)) {
return $value;
}
// Get value from 'Label (id)' entity_reference field format.
if (is_string($value)) {
$item_id = self::extractEntityIdFromAutocompleteInput($value);
if (is_null($item_id) && $value = intval($value)) {
$item_id = $value;
}
}
// Get value from [0][target_id] array format.
elseif (is_array($value)) {
$item_id = NestedArray::getValue($value, [0, 'target_id']);
}
// Get value from entity ID passed in directly.
elseif (is_int($value)) {
$item_id = $value;
}
// Exit if invalid value.
else {
return NULL;
}
return self::getInventoryItemStorage()->load($item_id);
}
/**
* A generic ajax refresh for use with the full form.
*
* @param array $form
* The complete form structure.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*
* @return array
* The refreshed element form.
*/
public static function ajaxRefresh(array $form, FormStateInterface $form_state) {
return self::getElementForm($form, $form_state);
}
/**
* Ajax callback for the "Add another item" button.
*
* This returns the new page content to replace the page content made obsolete
* by the form submission.
*
* @param array $form
* The complete form structure.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*
* @return \Drupal\Core\Ajax\AjaxResponse
* The ajax response.
*/
public static function ajaxTableRefresh(array $form, FormStateInterface $form_state) {
$element = self::getElementForm($form, $form_state);
$response = new AjaxResponse();
/** @var \Drupal\commerce_inventory\Entity\InventoryItemInterface $adjustment_item */
$adjustment_item = &$element['#value']['item'];
/** @var \Drupal\commerce_inventory\Plugin\Commerce\InventoryAdjustmentType\InventoryAdjustmentTypeInterface $adjustment_type */
$adjustment_type = &$element['#value']['type_plugin'];
/** @var \Drupal\commerce_inventory\Entity\InventoryItemInterface $related_item */
$related_item = &$element['#value']['related_item'];
if ($adjustment_type instanceof InventoryAdjustmentTypeInterface) {
// Update adjustment item quantity values.
if ($adjustment_item instanceof InventoryItemInterface) {
$item_on_hand_id = $adjustment_item->uuid() . '-on-hand';
$item_on_hand_class = '.' . $item_on_hand_id;
$item_on_hand_current = $element['table']['#rows'][$item_on_hand_id]['data']['current']['data']['#markup'];
$item_on_hand_adjustment = $element['table']['#rows'][$item_on_hand_id]['data']['adjustment']['data']['#markup'];
$item_on_hand_updated = $element['table']['#rows'][$item_on_hand_id]['data']['updated']['data']['#markup'];
$response->addCommand(new ReplaceCommand($item_on_hand_class . ' .current div', $item_on_hand_current));
$response->addCommand(new ReplaceCommand($item_on_hand_class . ' .adjustment div', $item_on_hand_adjustment));
$response->addCommand(new ReplaceCommand($item_on_hand_class . ' .updated div', $item_on_hand_updated));
}
// Update related item quantity values.
if ($related_item instanceof InventoryItemInterface && $adjustment_type->hasRelatedAdjustmentType()) {
$related_on_hand_id = $related_item->uuid() . '-on-hand';
$related_on_hand_class = '.' . $related_on_hand_id;
$related_on_hand_current = $element['table']['#rows'][$related_on_hand_id]['data']['current']['data']['#markup'];
$related_on_hand_adjustment = $element['table']['#rows'][$related_on_hand_id]['data']['adjustment']['data']['#markup'];
$related_on_hand_updated = $element['table']['#rows'][$related_on_hand_id]['data']['updated']['data']['#markup'];
$response->addCommand(new ReplaceCommand($related_on_hand_class . ' .current div', $related_on_hand_current));
$response->addCommand(new ReplaceCommand($related_on_hand_class . ' .adjustment div', $related_on_hand_adjustment));
$response->addCommand(new ReplaceCommand($related_on_hand_class . ' .updated div', $related_on_hand_updated));
}
}
return $response;
}
}
