Skip to content

Commit 1af9599

Browse files
committed
Issue #2859423 by mglaman, mikeNCM, bmcclure, bojanz: PluginSelect element creates plugin using raw form values for configuration
1 parent 2b5900a commit 1af9599

File tree

9 files changed

+190
-38
lines changed

9 files changed

+190
-38
lines changed

modules/promotion/src/Plugin/Commerce/PromotionCondition/OrderTotalPrice.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,15 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta
4949
return $form;
5050
}
5151

52+
/**
53+
* {@inheritdoc}
54+
*/
55+
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
56+
$values = $form_state->getValue($form['#parents']);
57+
$this->configuration['amount'] = $values['amount'];
58+
parent::submitConfigurationForm($form, $form_state);
59+
}
60+
5261
/**
5362
* {@inheritdoc}
5463
*/

modules/promotion/src/Plugin/Commerce/PromotionCondition/PromotionConditionBase.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
namespace Drupal\commerce_promotion\Plugin\Commerce\PromotionCondition;
44

55
use Drupal\Core\Condition\ConditionPluginBase;
6+
use Drupal\Core\Form\FormStateInterface;
7+
use Drupal\Core\Form\SubformState;
68

79
/**
810
* Base class for Promotion Condition plugins.
@@ -31,4 +33,11 @@ public function execute() {
3133
return $this->isNegated() ? !$result : $result;
3234
}
3335

36+
/**
37+
* {@inheritdoc}
38+
*/
39+
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
40+
return parent::submitConfigurationForm($form, SubformState::createForSubform($form, $form_state->getCompleteForm(), $form_state));
41+
}
42+
3443
}

modules/promotion/tests/src/FunctionalJavascript/PromotionTest.php

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -41,29 +41,37 @@ public function testCreatePromotion() {
4141

4242
// Check the integrity of the form.
4343
$this->assertSession()->fieldExists('name[0][value]');
44+
$name = $this->randomMachineName(8);
45+
$this->getSession()->getPage()->fillField('name[0][value]', $name);
4446

4547
$this->getSession()->getPage()->fillField('offer[0][target_plugin_id]', 'commerce_promotion_product_percentage_off');
46-
$this->getSession()->wait(2000, "jQuery('.ajax-progress').length === 0");
48+
$this->waitForAjaxToFinish();
49+
$this->getSession()->getPage()->fillField('offer[0][target_plugin_configuration][amount]', '10.0');
4750

48-
$name = $this->randomMachineName(8);
49-
$edit = [
50-
'name[0][value]' => $name,
51-
'offer[0][target_plugin_configuration][amount]' => '10.0',
52-
];
51+
// Change, assert any values reset.
52+
$this->getSession()->getPage()->fillField('offer[0][target_plugin_id]', 'commerce_promotion_product_percentage_off');
53+
$this->waitForAjaxToFinish();
54+
$this->assertSession()->fieldValueNotEquals('offer[0][target_plugin_configuration][amount]', '10.0');
55+
$this->getSession()->getPage()->fillField('offer[0][target_plugin_configuration][amount]', '10.0');
5356

5457
$this->getSession()->getPage()->fillField('conditions[0][target_plugin_id]', 'commerce_promotion_order_total_price');
55-
$this->getSession()->wait(2000, "jQuery('.ajax-progress').length === 0");
56-
57-
$edit['conditions[0][target_plugin_configuration][amount][number]'] = '50.00';
58+
$this->waitForAjaxToFinish();
59+
$this->getSession()->getPage()->fillField('conditions[0][target_plugin_configuration][amount][number]', '50.00');
60+
$this->getSession()->getPage()->checkField('conditions[0][target_plugin_configuration][negate]');
5861

59-
$this->submitForm($edit, t('Save'));
62+
$this->submitForm([], t('Save'));
6063
$this->assertSession()->pageTextContains("Saved the $name promotion.");
6164
$promotion_count = $this->getSession()->getPage()->find('xpath', '//table/tbody/tr/td[text()="' . $name . '"]');
6265
$this->assertEquals(count($promotion_count), 1, 'promotions exists in the table.');
6366

6467
/** @var \Drupal\commerce\Plugin\Field\FieldType\PluginItem $offer_field */
6568
$offer_field = Promotion::load(1)->get('offer')->first();
6669
$this->assertEquals('0.10', $offer_field->target_plugin_configuration['amount']);
70+
71+
/** @var \Drupal\commerce\Plugin\Field\FieldType\PluginItem $condition_field */
72+
$condition_field = Promotion::load(1)->get('conditions')->first();
73+
$this->assertEquals('50.00', $condition_field->target_plugin_configuration['amount']['number']);
74+
$this->assertEquals(TRUE, $condition_field->target_plugin_configuration['negate']);
6775
}
6876

6977
/**

src/Element/PluginSelect.php

Lines changed: 46 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ public function getInfo() {
4343
[$class, 'processPluginSelect'],
4444
[$class, 'processAjaxForm'],
4545
],
46+
'#after_build' => [
47+
[$class, 'clearValues'],
48+
],
4649
'#element_validate' => [
4750
[$class, 'validateElementSubmit'],
4851
[$class, 'validatePlugin'],
@@ -72,18 +75,17 @@ public static function processPluginSelect(&$element, FormStateInterface $form_s
7275
// Prefix and suffix used for Ajax replacement.
7376
$element['#prefix'] = '<div id="' . $ajax_wrapper_id . '">';
7477
$element['#suffix'] = '</div>';
75-
7678
$element['#tree'] = TRUE;
79+
7780
$element['target_plugin_id'] = [
7881
'#type' => $element['#plugin_element_type'],
7982
'#title' => $element['#title'],
8083
'#multiple' => FALSE,
8184
'#ajax' => [
82-
'callback' => [get_called_class(), 'pluginFormAjax'],
85+
'callback' => [get_called_class(), 'ajaxRefresh'],
8386
'wrapper' => $ajax_wrapper_id,
8487
],
8588
'#default_value' => $target_plugin_id,
86-
'#ajax_array_parents' => $element['#array_parents'],
8789
'#required' => $element['#required'],
8890
];
8991
// Add a "_none" option if the element is not required.
@@ -102,8 +104,8 @@ public static function processPluginSelect(&$element, FormStateInterface $form_s
102104
continue;
103105
}
104106

105-
// Group categorized plugins.
106-
if (isset($definition['category'])) {
107+
// Group categorized plugins, and if using a select element.
108+
if (isset($definition['category']) && $element['#plugin_element_type'] == 'select') {
107109
$element['target_plugin_id']['#options'][(string) $definition['category']][$definition['id']] = $definition['label'];
108110
}
109111
else {
@@ -122,10 +124,13 @@ public static function processPluginSelect(&$element, FormStateInterface $form_s
122124
$element['target_plugin_configuration'] = [
123125
'#type' => 'container',
124126
];
125-
if ($target_plugin_id != '_none') {
127+
if (!empty($target_plugin_id) && $target_plugin_id != '_none') {
126128
/** @var \Drupal\Core\Executable\ExecutableInterface $plugin */
127129
$plugin = $plugin_manager->createInstance($target_plugin_id, $values['target_plugin_configuration']);
128130
if ($plugin instanceof PluginFormInterface) {
131+
$element['target_plugin_configuration'] = [
132+
'#tree' => TRUE,
133+
];
129134
$element['target_plugin_configuration'] = $plugin->buildConfigurationForm($element['target_plugin_configuration'], $form_state);
130135
}
131136
}
@@ -136,15 +141,14 @@ public static function processPluginSelect(&$element, FormStateInterface $form_s
136141
/**
137142
* Ajax callback.
138143
*/
139-
public static function pluginFormAjax(&$form, FormStateInterface $form_state, Request $request) {
140-
$triggering_element = $form_state->getTriggeringElement();
141-
while (!isset($triggering_element['#ajax_array_parents'])) {
142-
array_pop($triggering_element['#array_parents']);
143-
$triggering_element = NestedArray::getValue($form, $triggering_element['#array_parents']);
144-
}
145-
$element = NestedArray::getValue($form, $triggering_element['#ajax_array_parents']);
144+
public static function ajaxRefresh(&$form, FormStateInterface $form_state, Request $request) {
145+
$target_plugin_id_element = $form_state->getTriggeringElement();
146146

147-
return $element;
147+
// Radios are an extra parent deep compared to the select.
148+
$slice_length = ($target_plugin_id_element['#type'] == 'radio') ? -2 : -1;
149+
150+
$plugin_select_element = NestedArray::getValue($form, array_slice($target_plugin_id_element['#array_parents'], 0, $slice_length));
151+
return $plugin_select_element;
148152
}
149153

150154
/**
@@ -180,8 +184,9 @@ public static function validatePlugin(array &$element, FormStateInterface $form_
180184
// If a plugin was selected, create an instance and pass the configuration
181185
// values to its configuration form validation method.
182186
if ($target_plugin_id != '_none') {
187+
/** @var \Drupal\Component\Plugin\PluginManagerInterface $plugin_manager */
183188
$plugin_manager = \Drupal::service('plugin.manager.' . $element['#plugin_type']);
184-
$plugin = $plugin_manager->createInstance($target_plugin_id, $values['target_plugin_configuration']);
189+
$plugin = $plugin_manager->createInstance($target_plugin_id, $element['#default_value']['target_plugin_configuration']);
185190
if ($plugin instanceof PluginFormInterface) {
186191
$plugin->validateConfigurationForm($element['target_plugin_configuration'], $form_state);
187192
}
@@ -202,8 +207,9 @@ public static function submitPlugin(array &$element, FormStateInterface $form_st
202207
// If a plugin was selected, create an instance and pass the configuration
203208
// values to its configuration form submission method.
204209
if ($target_plugin_id != '_none') {
210+
/** @var \Drupal\Component\Plugin\PluginManagerInterface $plugin_manager */
205211
$plugin_manager = \Drupal::service('plugin.manager.' . $element['#plugin_type']);
206-
$plugin = $plugin_manager->createInstance($target_plugin_id, $values['target_plugin_configuration']);
212+
$plugin = $plugin_manager->createInstance($target_plugin_id, $element['#default_value']['target_plugin_configuration']);
207213
if ($plugin instanceof PluginFormInterface) {
208214
/** @var \Drupal\Component\Plugin\ConfigurablePluginInterface $plugin */
209215
$plugin->submitConfigurationForm($element['target_plugin_configuration'], $form_state);
@@ -213,4 +219,28 @@ public static function submitPlugin(array &$element, FormStateInterface $form_st
213219
}
214220
}
215221

222+
/**
223+
* Clears the plugin-specific form values when the target plugin changes.
224+
*
225+
* Implemented as an #after_build callback because #after_build runs before
226+
* validation, allowing the values to be cleared early enough to prevent the
227+
* "Illegal choice" error.
228+
*/
229+
public static function clearValues(array $element, FormStateInterface $form_state) {
230+
$triggering_element = $form_state->getTriggeringElement();
231+
if (!$triggering_element) {
232+
return $element;
233+
}
234+
235+
$triggering_element_name = end($triggering_element['#parents']);
236+
if ($triggering_element_name == 'target_plugin_id') {
237+
$input = &$form_state->getUserInput();
238+
$parents = array_merge($element['#parents'], ['target_plugin_configuration']);
239+
NestedArray::setValue($input, $parents, '');
240+
$element['target_plugin_configuration']['#value'] = '';
241+
}
242+
243+
return $element;
244+
}
245+
216246
}

src/Plugin/Field/FieldType/PluginItem.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,11 @@ public function getTargetInstance(array $contexts = []) {
136136
* {@inheritdoc}
137137
*/
138138
public function setValue($values, $notify = TRUE) {
139-
if (isset($values['target_plugin_configuration'])) {
139+
if (isset($values)) {
140+
$values += [
141+
'target_plugin_configuration' => [],
142+
];
143+
140144
// Single serialized values on shared tables for base fields are not
141145
// always unserialized. https://www.drupal.org/node/2788637
142146
if (is_string($values['target_plugin_configuration'])) {

src/Plugin/Field/FieldWidget/PluginRadiosWidget.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen
3131
'#categories' => $this->fieldDefinition->getSetting('categories'),
3232
'#default_value' => [
3333
'target_plugin_id' => $items[$delta]->target_plugin_id,
34-
'target_plugin_configuration' => $items[$delta]->target_plugin_configuration,
34+
'target_plugin_configuration' => $items[$delta]->target_plugin_configuration ?: [],
3535
],
3636
'#required' => $this->fieldDefinition->isRequired(),
3737
'#title' => $this->fieldDefinition->getLabel(),

src/Plugin/Field/FieldWidget/PluginSelectWidget.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,14 @@ class PluginSelectWidget extends WidgetBase {
2424
*/
2525
public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
2626
list($field_type, $derivative) = explode(':', $this->fieldDefinition->getType());
27+
2728
return [
2829
'#type' => 'commerce_plugin_select',
2930
'#plugin_type' => $derivative,
3031
'#categories' => $this->fieldDefinition->getSetting('categories'),
3132
'#default_value' => [
3233
'target_plugin_id' => $items[$delta]->target_plugin_id,
33-
'target_plugin_configuration' => $items[$delta]->target_plugin_configuration,
34+
'target_plugin_configuration' => $items[$delta]->target_plugin_configuration ?: [],
3435
],
3536
'#required' => $this->fieldDefinition->isRequired(),
3637
'#title' => $this->fieldDefinition->getLabel(),

tests/modules/commerce_test/src/Plugin/Condition/UserRole.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use Drupal\Core\Condition\ConditionPluginBase;
66
use Drupal\Core\Form\FormStateInterface;
7+
use Drupal\Core\Form\SubformState;
78

89
/**
910
* Provides a 'User Role' condition.
@@ -49,7 +50,7 @@ public function submitConfigurationForm(array &$form, FormStateInterface $form_s
4950
// Core plugins do not respect the #parents attribute passed in form.
5051
$values = $form_state->getValue($form['#parents']);
5152
$this->configuration['roles'] = array_filter($values['roles']);
52-
parent::submitConfigurationForm($form, $form_state);
53+
return parent::submitConfigurationForm($form, SubformState::createForSubform($form, $form_state->getCompleteForm(), $form_state));
5354
}
5455

5556
/**

0 commit comments

Comments
 (0)