Commit 1578f7f1 authored by jsacksick's avatar jsacksick Committed by bojanz

Issue #2837777 by jsacksick: Provide a commerce_plugin_radios field widget

parent e7716126
......@@ -17,6 +17,25 @@ function commerce_toolbar_alter(&$items) {
$items['administration']['#attached']['library'][] = 'commerce/toolbar';
}
/**
* Implements hook_field_widget_info_alter().
*
* Exposes the commerce_plugin_item widgets for each of the field type's
* derivatives, since core does not do it automatically.
*/
function commerce_field_widget_info_alter(array &$info) {
foreach (['commerce_plugin_select', 'commerce_plugin_radios'] as $widget) {
if (isset($info[$widget])) {
$field_type_manager = \Drupal::service('plugin.manager.field.field_type');
foreach ($field_type_manager->getDefinitions() as $key => $definition) {
if ($definition['id'] == 'commerce_plugin_item') {
$info[$widget]['field_types'][] = $key;
}
}
}
}
}
/**
* Implements hook_field_widget_form_alter().
*
......
......@@ -36,6 +36,7 @@ class PluginSelect extends FormElement {
return [
'#input' => TRUE,
'#plugin_type' => NULL,
'#plugin_element_type' => 'select',
'#categories' => [],
'#title' => $this->t('Select plugin'),
'#process' => [
......@@ -56,6 +57,9 @@ class PluginSelect extends FormElement {
if (!$element['#plugin_type']) {
throw new \InvalidArgumentException('You must specify the plugin type ID.');
}
if (!in_array($element['#plugin_element_type'], ['radios', 'select'])) {
throw new \InvalidArgumentException('The commerce_plugin_select element only supports select/radios.');
}
$element['#tree'] = TRUE;
......@@ -63,21 +67,15 @@ class PluginSelect extends FormElement {
$plugin_manager = \Drupal::service('plugin.manager.' . $element['#plugin_type']);
$values = $element['#value'];
$target_plugin_id = !empty($values['target_plugin_id']) ? $values['target_plugin_id'] : '_none';
$ajax_wrapper_id = Html::getUniqueId('ajax-wrapper');
$ajax_settings = [
'callback' => [get_called_class(), 'pluginFormAjax'],
'wrapper' => $ajax_wrapper_id,
];
// Prefix and suffix used for Ajax replacement.
$element['#prefix'] = '<div id="' . $ajax_wrapper_id . '">';
$element['#suffix'] = '</div>';
// Store #array_parents in the form state, so we can get the elements from
// the complete form array by using only thes form state.
// the complete form array by using only the form state.
$element['array_parents'] = [
'#type' => 'value',
'#value' => $element['#array_parents'],
......@@ -87,21 +85,27 @@ class PluginSelect extends FormElement {
'#type' => 'value',
'#value' => $element['#plugin_type'],
];
$element['target_plugin_id'] = [
'#type' => 'select',
'#type' => $element['#plugin_element_type'],
'#title' => $element['#title'],
'#multiple' => FALSE,
'#options' => [
'_none' => t('None'),
'#ajax' => [
'callback' => [get_called_class(), 'pluginFormAjax'],
'wrapper' => $ajax_wrapper_id,
],
'#ajax' => $ajax_settings,
'#default_value' => $target_plugin_id,
'#ajax_array_parents' => $element['#array_parents'],
'#required' => $element['#required'],
];
// Add a "_none" option if the element is not required.
if (!$element['#required']) {
$element['target_plugin_id']['#options']['_none'] = t('None');
}
$categories = array_combine($element['#categories'], $element['#categories']);
$has_categories = !empty($categories);
$definitions = [];
foreach ($plugin_manager->getDefinitions() as $definition) {
// If categories have been specified, limit definitions based on them.
if ($has_categories && !isset($categories[$definition['category']])) {
......@@ -115,12 +119,23 @@ class PluginSelect extends FormElement {
else {
$element['target_plugin_id']['#options'][$definition['id']] = $definition['label'];
}
$definitions[] = $definition['id'];
}
// If the element is required, set the default value to the first plugin.
// definition available in the options array.
if ($element['#required']) {
if ($target_plugin_id == '_none' && !empty($element['target_plugin_id']['#options'])) {
$target_plugin_id = reset($definitions);
$values['target_plugin_configuration'] = [];
$element['target_plugin_id']['#default_value'] = $target_plugin_id;
}
}
if ($target_plugin_id != '_none') {
/** @var \Drupal\Core\Executable\ExecutableInterface $plugin */
$plugin = $plugin_manager->createInstance($target_plugin_id, $values['target_plugin_configuration']);
if ($plugin instanceof PluginFormInterface) {
if ($plugin instanceof PluginFormInterface) {
$element['target_plugin_configuration'] = [
'#tree' => TRUE,
];
......@@ -135,9 +150,13 @@ class PluginSelect extends FormElement {
* Ajax callback.
*/
public static function pluginFormAjax(&$form, FormStateInterface &$form_state, Request $request) {
// Retrieve the element to be rendered.
$triggering_element = $form_state->getTriggeringElement();
while (!isset($triggering_element['#ajax_array_parents'])) {
array_pop($triggering_element['#array_parents']);
$triggering_element = NestedArray::getValue($form, $triggering_element['#array_parents']);
}
$form_element = NestedArray::getValue($form, $triggering_element['#ajax_array_parents']);
return $form_element;
}
......
<?php
namespace Drupal\commerce\Event;
/**
* Defines events for the base Commerce module.
*
* Note that submodules have their own defined events.
*/
final class CommerceEvents {
/**
* Name of the event fired when altering the referenceable plugin types.
*
* @Event
*
* @see \Drupal\commerce\Event\ReferenceablePluginTypesEvent.php
*/
const REFERENCEABLE_PLUGIN_TYPES = 'commerce.referenceable_plugin_types';
}
<?php
namespace Drupal\commerce\Event;
use Symfony\Component\EventDispatcher\Event;
/**
* Defines the referenceable plugin types event.
*
* @see \Drupal\commerce\Event\CommerceEvents
*/
class ReferenceablePluginTypesEvent extends Event {
/**
* The plugin types, in the id => label format.
*
* @var array
*/
protected $pluginTypes;
/**
* Constructs a new ReferenceablePluginTypesEvent object.
*
* @param array $plugin_types
* The plugin types, in the id => label format.
*/
public function __construct(array $plugin_types) {
$this->pluginTypes = $plugin_types;
}
/**
* Gets the plugin types.
*
* @return array
* The plugin types, in the id => label format.
*/
public function getPluginTypes() {
return $this->pluginTypes;
}
/**
* Sets the plugin types.
*
* @param array $plugin_types
* The plugin types, in the id => label format.
*
* @return $this
*/
public function setPluginTypes(array $plugin_types) {
$this->pluginTypes = $plugin_types;
return $this;
}
}
......@@ -2,29 +2,70 @@
namespace Drupal\commerce\Plugin\Field\FieldType;
use Drupal\commerce\Event\CommerceEvents;
use Drupal\commerce\Event\ReferenceablePluginTypesEvent;
use Drupal\Component\Plugin\Derivative\DeriverBase;
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
/**
* Deriver for the executable plugin item field type.
* Deriver for the commerce_plugin_item field type.
*/
class PluginItemDeriver extends DeriverBase {
class PluginItemDeriver extends DeriverBase implements ContainerDeriverInterface {
use StringTranslationTrait;
/**
* The event dispatcher.
*
* @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
*/
protected $eventDispatcher;
/**
* Constructs a new PluginItemDeriver object.
*
* @param string $base_plugin_id
* The base plugin ID.
* @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
* The event dispatcher.
*/
public function __construct($base_plugin_id, EventDispatcherInterface $event_dispatcher) {
$this->basePluginId = $base_plugin_id;
$this->eventDispatcher = $event_dispatcher;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, $base_plugin_id) {
return new static(
$base_plugin_id,
$container->get('event_dispatcher')
);
}
/**
* {@inheritdoc}
*/
public function getDerivativeDefinitions($base_plugin_definition) {
$supported = [
$plugin_types = [
'condition' => $this->t('Conditions'),
'action' => $this->t('Action'),
'commerce_promotion_offer' => $this->t('Promotion offer'),
'commerce_promotion_condition' => $this->t('Promotion condition'),
];
// Core has no way to list plugin types, so each referenceable plugin
// type needs to register itself via the event.
$event = new ReferenceablePluginTypesEvent($plugin_types);
$this->eventDispatcher->dispatch(CommerceEvents::REFERENCEABLE_PLUGIN_TYPES, $event);
$plugin_types = $event->getPluginTypes();
foreach ($supported as $id => $label) {
$this->derivatives[$id] = [
'plugin_type' => $id,
foreach ($plugin_types as $plugin_type => $label) {
$this->derivatives[$plugin_type] = [
'plugin_type' => $plugin_type,
'label' => $label,
'category' => $this->t('Plugin'),
] + $base_plugin_definition;
......
<?php
namespace Drupal\commerce\Plugin\Field\FieldWidget;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Form\FormStateInterface;
/**
* Plugin implementation of the 'commerce_plugin_radios' widget.
*
* @FieldWidget(
* id = "commerce_plugin_radios",
* label = @Translation("Plugin radios"),
* field_types = {
* "commerce_plugin_item"
* },
* )
*/
class PluginRadiosWidget extends PluginSelectWidget {
/**
* {@inheritdoc}
*/
public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
list($field_type, $plugin_type) = explode(':', $this->fieldDefinition->getType());
return [
'#type' => 'commerce_plugin_select',
'#plugin_element_type' => 'radios',
'#plugin_type' => $plugin_type,
'#categories' => $this->fieldDefinition->getSetting('categories'),
'#default_value' => [
'target_plugin_id' => $items[$delta]->target_plugin_id,
'target_plugin_configuration' => $items[$delta]->target_plugin_configuration,
],
'#required' => $this->fieldDefinition->isRequired(),
'#title' => $this->fieldDefinition->getLabel(),
];
}
}
......@@ -34,6 +34,8 @@ class PluginSelectWidget extends WidgetBase {
'target_plugin_id' => $items[$delta]->target_plugin_id,
'target_plugin_configuration' => $items[$delta]->target_plugin_configuration,
],
'#required' => $this->fieldDefinition->isRequired(),
'#title' => $this->fieldDefinition->getLabel(),
];
}
......@@ -41,7 +43,6 @@ class PluginSelectWidget extends WidgetBase {
* {@inheritdoc}
*/
public function massageFormValues(array $values, array $form, FormStateInterface $form_state) {
// Iterate through the provided values and run the plugin configuration form
// through the plugin's submit configuration form method, if available.
foreach ($values as $delta => &$item_value) {
......@@ -50,7 +51,6 @@ class PluginSelectWidget extends WidgetBase {
}
$element = NestedArray::getValue($form, $item_value['array_parents']);
/** @var \Drupal\Core\Executable\ExecutableManagerInterface $plugin_manager */
$plugin_manager = \Drupal::service('plugin.manager.' . $item_value['target_plugin_type']);
$plugin = $plugin_manager->createInstance($item_value['target_plugin_id'], $item_value['target_plugin_configuration']);
......@@ -58,7 +58,6 @@ class PluginSelectWidget extends WidgetBase {
// If the plugin implements the PluginFormInterface, pass the values to
// its submit method for final processing.
if ($plugin instanceof PluginFormInterface) {
/** @var \Drupal\Component\Plugin\ConfigurablePluginInterface $plugin */
$plugin->submitConfigurationForm($element['target_plugin_configuration'], $form_state);
$item_value['target_plugin_configuration'] = $plugin->getConfiguration();
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment