diff --git a/modules/product/src/Plugin/Commerce/PromotionCondition/ProductEquals.php b/modules/product/src/Plugin/Commerce/PromotionCondition/ProductEquals.php
new file mode 100644
index 0000000000..7a28d1cc40
--- /dev/null
+++ b/modules/product/src/Plugin/Commerce/PromotionCondition/ProductEquals.php
@@ -0,0 +1,118 @@
+productStorage = $entity_type_manager->getStorage('commerce_product');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+ return new static(
+ $configuration,
+ $plugin_id,
+ $plugin_definition,
+ $container->get('entity_type.manager')
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function defaultConfiguration() {
+ return [
+ 'product_id' => NULL,
+ ] + parent::defaultConfiguration();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
+ $form = parent::buildConfigurationForm($form, $form_state);
+
+ $product = $this->productStorage->load($this->configuration['product_id']);
+ $form['product_id'] = [
+ '#type' => 'entity_autocomplete',
+ '#title' => $this->t('Product'),
+ '#default_value' => $product,
+ '#target_type' => 'commerce_product',
+ ];
+ return $form;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
+ $values = $form_state->getValue($form['#parents']);
+ $this->configuration['product_id'] = $values['product_id'];
+ parent::submitConfigurationForm($form, $form_state);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function evaluate() {
+ $product_id = $this->configuration['product_id'];
+ if (empty($product_id)) {
+ return FALSE;
+ }
+
+ /** @var \Drupal\commerce_product\Entity\ProductInterface $current_product */
+ $current_product = $this->getTargetEntity()->getPurchasedEntity()->getProduct();
+
+ return $current_product->id() == $product_id;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function summary() {
+ return $this->t('Compares the purchased product.');
+ }
+
+}
diff --git a/modules/product/src/Plugin/Commerce/PromotionCondition/ProductFieldEquals.php b/modules/product/src/Plugin/Commerce/PromotionCondition/ProductFieldEquals.php
new file mode 100644
index 0000000000..67cf3f5620
--- /dev/null
+++ b/modules/product/src/Plugin/Commerce/PromotionCondition/ProductFieldEquals.php
@@ -0,0 +1,186 @@
+ NULL,
+ 'field' => NULL,
+ ] + parent::defaultConfiguration();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
+ $form += parent::buildConfigurationForm($form, $form_state);
+ $ajax_wrapper_id = Html::getUniqueId('ajax-wrapper');
+ // Prefix and suffix used for Ajax replacement.
+ $form['#prefix'] = '
';
+ $form['#suffix'] = '
';
+
+ $selected_bundle = isset($this->configuration['bundle']) ? $this->configuration['bundle'] : NULL;
+ $bundles = \Drupal::service("entity_type.bundle.info")->getBundleInfo('commerce_product');
+ $bundle_options = [];
+ foreach ($bundles as $bundle => $label) {
+ $bundle_options[$bundle] = $label['label'];
+ }
+
+ $form['bundle'] = [
+ '#type' => 'select',
+ '#options' => $bundle_options,
+ '#title' => t('Product bundle'),
+ '#default_value' => $selected_bundle,
+ '#required' => TRUE,
+ '#ajax' => [
+ 'callback' => [$this, 'bundleAjaxCallback'],
+ 'wrapper' => $ajax_wrapper_id,
+ ],
+ ];
+ if (!$selected_bundle) {
+ return $form;
+ }
+
+ $fields = \Drupal::service("entity_field.manager")->getFieldDefinitions('commerce_product', $selected_bundle);
+ $selected_field = isset($this->configuration['field']) ? $this->configuration['field'] : NULL;
+
+ $filed_options = [];
+ foreach ($fields as $field_id => $field_definition) {
+ $filed_options[$field_id] = $field_definition->getLabel();
+ }
+
+ $form['field'] = [
+ '#type' => 'select',
+ '#title' => t('Field'),
+ '#options' => $filed_options,
+ '#default_value' => $selected_field,
+ '#required' => TRUE,
+ '#ajax' => [
+ 'callback' => [$this, 'bundleAjaxCallback'],
+ 'wrapper' => $ajax_wrapper_id,
+ ],
+ ];
+
+ if (!$selected_field) {
+ return $form;
+ }
+
+ //Create an empty representative entity
+ $commerce_product = \Drupal::service('entity_type.manager')->getStorage('commerce_product')->create(array(
+ 'type' => $selected_bundle,
+ $selected_field => $this->configuration[$selected_field],
+ )
+ );
+
+ //Get the EntityFormDisplay (i.e. the default Form Display) of this content type
+ $entity_form_display = \Drupal::service('entity_type.manager')->getStorage('entity_form_display')
+ ->load('commerce_product.' . $selected_bundle . '.default');
+
+ //Get the body field widget and add it to the form
+ if ($widget = $entity_form_display->getRenderer($selected_field)) { //Returns the widget class
+ $items = $commerce_product->get($selected_field); //Returns the FieldItemsList interface
+ $items->filterEmptyItems();
+ $form[$selected_field] = $widget->form($items, $form, $form_state); //Builds the widget form and attach it to your form
+ $form[$selected_field]['widget']['#required'] = TRUE;
+ }
+
+ return $form;
+ }
+
+
+ public function bundleAjaxCallback(array $form, FormStateInterface $form_state) {
+ $triggering_element = $form_state->getTriggeringElement();
+ $parents = array_slice($triggering_element['#array_parents'], 0, -1);
+ $form_element = NestedArray::getValue($form, $parents);
+ return $form_element;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function evaluate() {
+ $bundle_id = $this->configuration['bundle'];
+ if (empty($bundle_id)) {
+ return FALSE;
+ }
+ $field_id = $this->configuration['field'];
+ if (empty($field_id)) {
+ return FALSE;
+ }
+ /** @var OrderItemInterface $order_item */
+ $order_item = $this->getContextValue('commerce_order_item');
+
+ /** @var \Drupal\commerce_product\Entity\ProductInterface $current_product */
+ $current_product = $order_item->getPurchasedEntity()->getProduct();
+ if ($current_product->bundle() != $bundle_id) {
+ return FALSE;
+ }
+
+ if (!$current_product->hasField($field_id)) {
+ return FALSE;
+ }
+
+ $field_type = $current_product->get($field_id)->getFieldDefinition()->getType();
+ $target_type = NULL;
+ if ($field_type == 'entity_reference') {
+ $target_type = $current_product->get($field_id)->getFieldDefinition()->getFieldStorageDefinition()->getSetting('target_type');
+ }
+
+ if ($target_type == 'taxonomy_term') {
+ if ($current_product->get($field_id)->getValue() == $this->configuration[$field_id]) {
+ return TRUE;
+ }
+ else {
+ /** @var TermInterface $term */
+ $term = \Drupal::service('entity_type.manager')
+ ->getStorage("taxonomy_term")->load($this->configuration[$field_id][0]['target_id']);
+ $tree = \Drupal::service('entity_type.manager')
+ ->getStorage("taxonomy_term")
+ ->loadTree($term->getVocabularyId(), $term->id());
+ $found = FALSE;
+ foreach ($tree as $item) {
+ if ($item->tid == $current_product->get($field_id)->getValue()[0]['target_id']) {
+ $found = TRUE;
+ break;
+ }
+ }
+ return $found;
+ }
+ }
+ elseif ($current_product->get($field_id)->getValue() != $this->configuration[$field_id]) {
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function summary() {
+ return $this->t('Compares the product entity.');
+ }
+
+}
diff --git a/modules/product/src/Plugin/Commerce/PromotionCondition/ProductVariationEquals.php b/modules/product/src/Plugin/Commerce/PromotionCondition/ProductVariationEquals.php
new file mode 100644
index 0000000000..ee9e90f8ae
--- /dev/null
+++ b/modules/product/src/Plugin/Commerce/PromotionCondition/ProductVariationEquals.php
@@ -0,0 +1,119 @@
+productVariationStorage = $entity_type_manager->getStorage('commerce_product_variation');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+ return new static(
+ $configuration,
+ $plugin_id,
+ $plugin_definition,
+ $container->get('entity_type.manager')
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function defaultConfiguration() {
+ return [
+ 'variation_id' => NULL,
+ ] + parent::defaultConfiguration();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
+ $form = parent::buildConfigurationForm($form, $form_state);
+
+ $form['variation_id'] = [
+ '#type' => 'entity_autocomplete',
+ '#title' => t('Product variation'),
+ '#default_value' => $this->productVariationStorage->load($this->configuration['variation_id']),
+ '#target_type' => 'commerce_product_variation',
+ '#selection_handler' => 'default',
+ ];
+
+ return $form;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
+ $values = $form_state->getValue($form['#parents']);
+ $this->configuration['variation_id'] = $values['variation_id'];
+ parent::submitConfigurationForm($form, $form_state);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function evaluate() {
+ $variant_id = $this->configuration['variation_id'];
+ if (empty($variant_id)) {
+ return FALSE;
+ }
+
+ /** @var \Drupal\commerce_product\Entity\ProductVariationInterface $current_product_variation */
+ $current_product_variation = $this->getTargetEntity()->getPurchasedEntity();
+
+ $result = ($current_product_variation->id() == $variant_id);
+ return $this->isNegated() ? !$result : $result;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function summary() {
+ return $this->t('Compares the purchased product variation.');
+ }
+
+}
diff --git a/modules/product/src/Plugin/Commerce/PromotionCondition/ProductVariationFieldEquals.php b/modules/product/src/Plugin/Commerce/PromotionCondition/ProductVariationFieldEquals.php
new file mode 100644
index 0000000000..ecaec0834a
--- /dev/null
+++ b/modules/product/src/Plugin/Commerce/PromotionCondition/ProductVariationFieldEquals.php
@@ -0,0 +1,189 @@
+ NULL,
+ 'field' => NULL,
+ 'value' => NULL,
+ ] + parent::defaultConfiguration();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
+ $form += parent::buildConfigurationForm($form, $form_state);
+ $ajax_wrapper_id = Html::getUniqueId('ajax-wrapper');
+ // Prefix and suffix used for Ajax replacement.
+ $form['#prefix'] = '';
+ $form['#suffix'] = '
';
+
+// $entity_id = isset($this->configuration['entity']) ? $this->configuration['entity'] : NULL;
+ $selected_bundle = isset($this->configuration['bundle']) ? $this->configuration['bundle'] : NULL;
+ $bundles = \Drupal::service("entity_type.bundle.info")->getBundleInfo('commerce_product_variation');
+ $bundle_options = [];
+ foreach ($bundles as $bundle => $label) {
+ $bundle_options[$bundle] = $label['label'];
+ }
+
+ $form['bundle'] = [
+ '#type' => 'select',
+ '#options' => $bundle_options,
+ '#title' => t('Product variation bundle'),
+ '#default_value' => $selected_bundle,
+ '#required' => TRUE,
+ '#ajax' => [
+ 'callback' => [$this, 'bundleAjaxCallback'],
+ 'wrapper' => $ajax_wrapper_id,
+ ],
+ ];
+ if (!$selected_bundle) {
+ return $form;
+ }
+
+ $fields = \Drupal::service("entity_field.manager")->getFieldDefinitions('commerce_product_variation', $selected_bundle);
+ $selected_field = isset($this->configuration['field']) ? $this->configuration['field'] : NULL;
+
+ $filed_options = [];
+ foreach ($fields as $field_id => $field_definition) {
+ $filed_options[$field_id] = $field_definition->getLabel();
+ }
+
+ $form['field'] = [
+ '#type' => 'select',
+ '#title' => t('Field'),
+ '#options' => $filed_options,
+ '#default_value' => $selected_field,
+ '#required' => TRUE,
+ '#ajax' => [
+ 'callback' => [$this, 'bundleAjaxCallback'],
+ 'wrapper' => $ajax_wrapper_id,
+ ],
+ ];
+
+ if (!$selected_field) {
+ return $form;
+ }
+
+ //Create an empty representative entity
+ $commerce_product_variation = \Drupal::service('entity_type.manager')->getStorage('commerce_product_variation')->create(array(
+ 'type' => $selected_bundle,
+ $selected_field => $this->configuration[$selected_field],
+ )
+ );
+
+ //Get the EntityFormDisplay (i.e. the default Form Display) of this content type
+ $entity_form_display = \Drupal::service('entity_type.manager')->getStorage('entity_form_display')
+ ->load('commerce_product_variation.' . $selected_bundle . '.default');
+
+ //Get the body field widget and add it to the form
+ if ($widget = $entity_form_display->getRenderer($selected_field)) { //Returns the widget class
+ $items = $commerce_product_variation->get($selected_field); //Returns the FieldItemsList interface
+ $items->filterEmptyItems();
+ $form[$selected_field] = $widget->form($items, $form, $form_state); //Builds the widget form and attach it to your form
+ $form[$selected_field]['widget']['#required'] = TRUE;
+ }
+
+ return $form;
+ }
+
+ public function bundleAjaxCallback(array $form, FormStateInterface $form_state) {
+ $triggering_element = $form_state->getTriggeringElement();
+ $parents = array_slice($triggering_element['#array_parents'], 0, -1);
+ $form_element = NestedArray::getValue($form, $parents);
+ return $form_element;
+ }
+
+
+ /**
+ * {@inheritdoc}
+ */
+ public function evaluate() {
+ $bundle_id = $this->configuration['bundle'];
+ if (empty($bundle_id)) {
+ return FALSE;
+ }
+ $field_id = $this->configuration['field'];
+ if (empty($field_id)) {
+ return FALSE;
+ }
+ /** @var OrderItemInterface $order_item */
+ $order_item = $this->getContextValue('commerce_order_item');
+
+ /** @var ProductVariationInterface $current_product */
+ $current_product_variation = $order_item->getPurchasedEntity();
+ if ($current_product_variation->bundle() != $bundle_id) {
+ return FALSE;
+ }
+
+ if (!$current_product_variation->hasField($field_id)) {
+ return FALSE;
+ }
+
+ $field_type = $current_product_variation->get($field_id)->getFieldDefinition()->getType();
+ $target_type = NULL;
+ if ($field_type == 'entity_reference') {
+ $target_type = $current_product->get($field_id)->getFieldDefinition()->getFieldStorageDefinition()->getSetting('target_type');
+ }
+
+ if ($target_type == 'taxonomy_term') {
+ if ($current_product_variation->get($field_id)->getValue() == $this->configuration[$field_id]) {
+ return TRUE;
+ }
+ else {
+ /** @var TermInterface $term */
+ $term = \Drupal::service('entity_type.manager')
+ ->getStorage("taxonomy_term")->load($this->configuration[$field_id][0]['target_id']);
+ $tree = \Drupal::service('entity_type.manager')
+ ->getStorage("taxonomy_term")
+ ->loadTree($term->getVocabularyId(), $term->id());
+ $found = FALSE;
+ foreach ($tree as $item) {
+ if ($item->tid == $current_product_variation->get($field_id)->getValue()[0]['target_id']) {
+ $found = TRUE;
+ break;
+ }
+ }
+ return $found;
+ }
+ }
+ elseif ($current_product_variation->get($field_id)->getValue() != $this->configuration[$field_id]) {
+ return FALSE;
+ }
+
+ return TRUE;
+
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function summary() {
+ return $this->t('Compares the product variation entity.');
+ }
+
+}
diff --git a/modules/product/src/Plugin/EntityReferenceSelection/ProductVariationSelection.php b/modules/product/src/Plugin/EntityReferenceSelection/ProductVariationSelection.php
new file mode 100644
index 0000000000..598f63e395
--- /dev/null
+++ b/modules/product/src/Plugin/EntityReferenceSelection/ProductVariationSelection.php
@@ -0,0 +1,60 @@
+configuration['target_type'];
+
+ $query = $this->buildEntityQuery($match, $match_operator);
+ if ($limit > 0) {
+ $query->range(0, $limit);
+ }
+
+ $result = $query->execute();
+
+ if (empty($result)) {
+ return [];
+ }
+
+ /** @var \Drupal\commerce_product\Entity\ProductVariationTypeInterface[] $variation_types */
+ $variation_types = $this->entityManager->getStorage('commerce_product_variation_type')->loadMultiple();
+ $options = array();
+ $entities = $this->entityManager->getStorage($target_type)->loadMultiple($result);
+ foreach ($entities as $entity_id => $entity) {
+ $bundle = $variation_types[$entity->bundle()];
+ /** @var \Drupal\commerce_product\Entity\ProductVariationInterface $entity */
+ $entity = $this->entityManager->getTranslationFromContext($entity);
+
+ if ($bundle->shouldGenerateTitle()) {
+ $option_label = $entity->label();
+ }
+ else {
+ $option_label = $entity->getProduct()->label() . ': ' . $entity->label();
+ }
+
+ $options[$bundle->id()][$entity_id] = Html::escape($option_label);
+ }
+
+ return $options;
+ }
+
+}