From 1578f7f1356922b16d2d2f90bea40d10f4f4b3a7 Mon Sep 17 00:00:00 2001 From: jsacksick Date: Fri, 6 Jan 2017 01:52:37 +0100 Subject: [PATCH] Issue #2837777 by jsacksick: Provide a commerce_plugin_radios field widget --- commerce.module | 19 +++++++ src/Element/PluginSelect.php | 47 +++++++++++----- src/Event/CommerceEvents.php | 21 ++++++++ src/Event/ReferenceablePluginTypesEvent.php | 54 +++++++++++++++++++ .../Field/FieldType/PluginItemDeriver.php | 53 +++++++++++++++--- .../Field/FieldWidget/PluginRadiosWidget.php | 40 ++++++++++++++ .../Field/FieldWidget/PluginSelectWidget.php | 5 +- 7 files changed, 216 insertions(+), 23 deletions(-) create mode 100644 src/Event/CommerceEvents.php create mode 100644 src/Event/ReferenceablePluginTypesEvent.php create mode 100644 src/Plugin/Field/FieldWidget/PluginRadiosWidget.php diff --git a/commerce.module b/commerce.module index 8f33a3bbcf..b7c4582fef 100644 --- a/commerce.module +++ b/commerce.module @@ -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(). * diff --git a/src/Element/PluginSelect.php b/src/Element/PluginSelect.php index c340ae6f77..4e779ba2a7 100644 --- a/src/Element/PluginSelect.php +++ b/src/Element/PluginSelect.php @@ -36,6 +36,7 @@ public function getInfo() { return [ '#input' => TRUE, '#plugin_type' => NULL, + '#plugin_element_type' => 'select', '#categories' => [], '#title' => $this->t('Select plugin'), '#process' => [ @@ -56,6 +57,9 @@ public static function processPluginSelect(&$element, FormStateInterface $form_s 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 @@ public static function processPluginSelect(&$element, FormStateInterface $form_s $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'] = '
'; $element['#suffix'] = '
'; // 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 @@ public static function processPluginSelect(&$element, FormStateInterface $form_s '#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 @@ public static function processPluginSelect(&$element, FormStateInterface $form_s 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 @@ public static function processPluginSelect(&$element, FormStateInterface $form_s * 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; } diff --git a/src/Event/CommerceEvents.php b/src/Event/CommerceEvents.php new file mode 100644 index 0000000000..7e68e2f4e0 --- /dev/null +++ b/src/Event/CommerceEvents.php @@ -0,0 +1,21 @@ + 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; + } + +} diff --git a/src/Plugin/Field/FieldType/PluginItemDeriver.php b/src/Plugin/Field/FieldType/PluginItemDeriver.php index ad01646fe9..00872c940f 100644 --- a/src/Plugin/Field/FieldType/PluginItemDeriver.php +++ b/src/Plugin/Field/FieldType/PluginItemDeriver.php @@ -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; diff --git a/src/Plugin/Field/FieldWidget/PluginRadiosWidget.php b/src/Plugin/Field/FieldWidget/PluginRadiosWidget.php new file mode 100644 index 0000000000..2ea3defee8 --- /dev/null +++ b/src/Plugin/Field/FieldWidget/PluginRadiosWidget.php @@ -0,0 +1,40 @@ +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(), + ]; + } + +} diff --git a/src/Plugin/Field/FieldWidget/PluginSelectWidget.php b/src/Plugin/Field/FieldWidget/PluginSelectWidget.php index 1b70c52b36..a6d6309570 100644 --- a/src/Plugin/Field/FieldWidget/PluginSelectWidget.php +++ b/src/Plugin/Field/FieldWidget/PluginSelectWidget.php @@ -34,6 +34,8 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen '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 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen * {@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 @@ public function massageFormValues(array $values, array $form, FormStateInterface } $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 @@ public function massageFormValues(array $values, array $form, FormStateInterface // 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();