Loading modules/cart/src/Form/AddToCartForm.php +73 −18 Original line number Diff line number Diff line Loading @@ -5,13 +5,15 @@ namespace Drupal\commerce_cart\Form; use Drupal\commerce\Context; use Drupal\commerce_cart\CartManagerInterface; use Drupal\commerce_cart\CartProviderInterface; use Drupal\commerce_order\Entity\OrderItemInterface; use Drupal\commerce_order\Resolver\OrderTypeResolverInterface; use Drupal\commerce_price\Resolver\ChainPriceResolverInterface; use Drupal\commerce_store\CurrentStoreInterface; use Drupal\commerce_store\SelectStoreTrait; use Drupal\Component\Datetime\TimeInterface; use Drupal\Core\Entity\ContentEntityForm; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\Display\EntityFormDisplayInterface; use Drupal\Core\Entity\Entity\EntityFormDisplay; use Drupal\Core\Entity\EntityRepositoryInterface; use Drupal\Core\Entity\EntityTypeBundleInfoInterface; use Drupal\Core\Form\FormStateInterface; Loading Loading @@ -124,14 +126,6 @@ class AddToCartForm extends ContentEntityForm implements AddToCartFormInterface ); } /** * {@inheritdoc} */ public function setEntity(EntityInterface $entity) { $this->entity = $entity; return $this; } /** * {@inheritdoc} */ Loading Loading @@ -167,16 +161,68 @@ class AddToCartForm extends ContentEntityForm implements AddToCartFormInterface * {@inheritdoc} */ public function buildForm(array $form, FormStateInterface $form_state) { $original_form = $form; $form = parent::buildForm($form, $form_state); /** @var \Drupal\commerce_order\Entity\OrderItemInterface $order_item */ $order_item = $this->entity; // The widgets are allowed to signal that the form should be hidden // (because there's no purchasable entity to select, for example). if ($form_state->get('hide_form')) { $form['#access'] = FALSE; } else { $selected_variation = $form_state->get('selected_variation'); // If the order item references a different variation than the one // currently selected, and the selected variation is supposed to use // a different order item type, rebuild the form. if ($selected_variation && $order_item->getPurchasedEntityId() != $selected_variation) { /** @var \Drupal\commerce_product\Entity\ProductVariationInterface $selected_variation */ $selected_variation = $this->entityTypeManager->getStorage('commerce_product_variation')->load($selected_variation); if ($selected_variation->getOrderItemTypeId() !== $order_item->bundle()) { /** @var \Drupal\commerce_order\OrderItemStorageInterface $order_item_storage */ $order_item_storage = $this->entityTypeManager->getStorage('commerce_order_item'); $order_item = $order_item_storage->createFromPurchasableEntity($selected_variation); $this->prepareOrderItem($order_item); $this->setEntity($order_item); $form_display = EntityFormDisplay::collectRenderDisplay($order_item, $this->operation); $this->setFormDisplay($form_display, $form_state); $form = $original_form; $form = parent::buildForm($form, $form_state); } } } return $form; } /** * {@inheritdoc} */ public function setFormDisplay(EntityFormDisplayInterface $form_display, FormStateInterface $form_state) { $component = $form_display->getComponent('purchased_entity'); // If the product references variations of a different type, fallback // to using the title widget as the attributes widget cannot properly // work. if ($component['type'] === 'commerce_product_variation_attributes') { $product = $form_state->get('product'); /** @var \Drupal\commerce_product\ProductVariationStorageInterface $product_variation_storage */ $product_variation_storage = $this->entityTypeManager->getStorage('commerce_product_variation'); $variations = $product_variation_storage->loadEnabled($product); $variation_types = []; foreach ($variations as $variation) { $variation_types[$variation->bundle()] = $variation->bundle(); if (count($variation_types) > 1) { $component['type'] = 'commerce_product_variation_title'; $form_display->setComponent('purchased_entity', $component); $this->setFormDisplay($form_display, $form_state); break; } } } return parent::setFormDisplay($form_display, $form_state); } /** * {@inheritdoc} */ Loading Loading @@ -221,23 +267,32 @@ class AddToCartForm extends ContentEntityForm implements AddToCartFormInterface public function buildEntity(array $form, FormStateInterface $form_state) { /** @var \Drupal\commerce_order\Entity\OrderItemInterface $entity */ $entity = parent::buildEntity($form, $form_state); $this->prepareOrderItem($entity); return $entity; } /** * Helper function used to prepare the order item for add to cart. * * @param \Drupal\commerce_order\Entity\OrderItemInterface $order_item * The order item to prepare. */ protected function prepareOrderItem(OrderItemInterface $order_item) { // Now that the purchased entity is set, populate the title and price. $purchased_entity = $entity->getPurchasedEntity(); $entity->setTitle($purchased_entity->getOrderItemTitle()); if (!$entity->isUnitPriceOverridden()) { $purchased_entity = $order_item->getPurchasedEntity(); $order_item->setTitle($purchased_entity->getOrderItemTitle()); if (!$order_item->isUnitPriceOverridden()) { $store = $this->selectStore($purchased_entity); $context = new Context($this->currentUser, $store); $resolved_price = $this->chainPriceResolver->resolve($purchased_entity, $entity->getQuantity(), $context); $entity->setUnitPrice($resolved_price); $resolved_price = $this->chainPriceResolver->resolve($purchased_entity, $order_item->getQuantity(), $context); $order_item->setUnitPrice($resolved_price); } $order_type_id = $this->orderTypeResolver->resolve($entity); $order_type_id = $this->orderTypeResolver->resolve($order_item); $store = $this->selectStore($purchased_entity); $cart = $this->cartProvider->getCart($order_type_id, $store); if ($cart) { $entity->set('order_id', $cart->id()); $order_item->set('order_id', $cart->id()); } return $entity; } } modules/cart/tests/src/Functional/MultipleCartMultipleVariationTypesTest.php +1 −0 Original line number Diff line number Diff line Loading @@ -191,6 +191,7 @@ class MultipleCartMultipleVariationTypesTest extends CartBrowserTestBase { 'id' => $id, 'label' => $label, 'variationType' => $variation_type->id(), 'variationTypes' => [], ]); $product_type->save(); } Loading modules/cart/tests/src/FunctionalJavascript/MultipleCartMultipleVariationTypesTest.php +43 −0 Original line number Diff line number Diff line Loading @@ -50,6 +50,17 @@ class MultipleCartMultipleVariationTypesTest extends CartWebDriverTestBase { $this->createProductAndVariationType('color_sizes', 'Colors and Sizes'); $this->createProductAndVariationType('colors', 'Colors'); $this->createProductAndVariationType('sizes', 'Sizes'); // Create a product type referencing multiple variation types. $product_type = ProductType::create([ 'id' => 'multiple', 'label' => 'Product type referencing multiple variation types', 'variationTypes' => [ 'colors', 'color_sizes', 'sizes', ], ]); $product_type->save(); // Create the attributes. $color_attribute = ProductAttribute::create([ Loading Loading @@ -140,6 +151,24 @@ class MultipleCartMultipleVariationTypesTest extends CartWebDriverTestBase { ['attribute_size' => $this->sizeAttributes['large']->id()], ], ], 'Multiple variation types' => [ 'type' => 'multiple', 'variations' => [ [ 'type' => 'sizes', 'attribute_size' => $this->sizeAttributes['medium']->id(), ], [ 'type' => 'color_sizes', 'attribute_color' => $this->colorAttributes['green']->id(), 'attribute_size' => $this->sizeAttributes['large']->id(), ], [ 'type' => 'colors', 'attribute_color' => $this->colorAttributes['blue']->id(), ] ], ], ]; foreach ($product_matrix as $product_title => $product_data) { /** @var \Drupal\commerce_product\Entity\ProductInterface $product */ Loading Loading @@ -180,13 +209,26 @@ class MultipleCartMultipleVariationTypesTest extends CartWebDriverTestBase { $forms[1]->pressButton('Add to cart'); $this->assertSession()->pageTextContains('My Colors and Sizes FIRST - Green, Medium added to your cart.'); $last_form = end($forms); // Assert the title widget is used when a product references variations of // a different type. $this->assertTrue($last_form->hasField('Please select')); $last_form->selectFieldOption('Please select', 'Multiple variation types - Blue'); $this->assertSession()->assertWaitOnAjaxRequest(); $last_form->pressButton('Add to cart'); $this->assertSession()->pageTextContains('Multiple variation types - Blue added to your cart.'); $this->container->get('entity_type.manager')->getStorage('commerce_order')->resetCache([$this->cart->id()]); $this->cart = Order::load($this->cart->id()); $order_items = $this->cart->getItems(); $this->assertCount(2, $order_items); /** @var \Drupal\commerce_product\Entity\ProductVariationInterface $variation */ $variation = $order_items[0]->getPurchasedEntity(); $this->assertEquals($this->colorAttributes['green']->id(), $variation->getAttributeValueId('attribute_color')); $this->assertEquals($this->sizeAttributes['medium']->id(), $variation->getAttributeValueId('attribute_size')); $variation = $order_items[1]->getPurchasedEntity(); $this->assertEquals($this->colorAttributes['blue']->id(), $variation->getAttributeValueId('attribute_color')); } /** Loading @@ -210,6 +252,7 @@ class MultipleCartMultipleVariationTypesTest extends CartWebDriverTestBase { 'id' => $id, 'label' => $label, 'variationType' => $variation_type->id(), 'variationTypes' => [], ]); $product_type->save(); } Loading modules/product/commerce_product.links.action.yml +2 −2 Original line number Diff line number Diff line Loading @@ -22,8 +22,8 @@ entity.commerce_product_variation_type.add_form: appears_on: - entity.commerce_product_variation_type.collection entity.commerce_product_variation.add_form: route_name: entity.commerce_product_variation.add_form entity.commerce_product_variation.add_page: route_name: entity.commerce_product_variation.add_page title: 'Add variation' appears_on: - entity.commerce_product_variation.collection modules/product/commerce_product.post_update.php +3 −1 Original line number Diff line number Diff line Loading @@ -110,7 +110,9 @@ function commerce_product_post_update_6() { $role->hasPermission("update any {$product_type->id()} commerce_product") || $role->hasPermission("update own {$product_type->id()} commerce_product") ) { $role->grantPermission("manage {$product_type->getVariationTypeId()} commerce_product_variation"); foreach ($product_type->getVariationTypeIds() as $type) { $role->grantPermission("manage {$type} commerce_product_variation"); } } } $role->save(); Loading Loading
modules/cart/src/Form/AddToCartForm.php +73 −18 Original line number Diff line number Diff line Loading @@ -5,13 +5,15 @@ namespace Drupal\commerce_cart\Form; use Drupal\commerce\Context; use Drupal\commerce_cart\CartManagerInterface; use Drupal\commerce_cart\CartProviderInterface; use Drupal\commerce_order\Entity\OrderItemInterface; use Drupal\commerce_order\Resolver\OrderTypeResolverInterface; use Drupal\commerce_price\Resolver\ChainPriceResolverInterface; use Drupal\commerce_store\CurrentStoreInterface; use Drupal\commerce_store\SelectStoreTrait; use Drupal\Component\Datetime\TimeInterface; use Drupal\Core\Entity\ContentEntityForm; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\Display\EntityFormDisplayInterface; use Drupal\Core\Entity\Entity\EntityFormDisplay; use Drupal\Core\Entity\EntityRepositoryInterface; use Drupal\Core\Entity\EntityTypeBundleInfoInterface; use Drupal\Core\Form\FormStateInterface; Loading Loading @@ -124,14 +126,6 @@ class AddToCartForm extends ContentEntityForm implements AddToCartFormInterface ); } /** * {@inheritdoc} */ public function setEntity(EntityInterface $entity) { $this->entity = $entity; return $this; } /** * {@inheritdoc} */ Loading Loading @@ -167,16 +161,68 @@ class AddToCartForm extends ContentEntityForm implements AddToCartFormInterface * {@inheritdoc} */ public function buildForm(array $form, FormStateInterface $form_state) { $original_form = $form; $form = parent::buildForm($form, $form_state); /** @var \Drupal\commerce_order\Entity\OrderItemInterface $order_item */ $order_item = $this->entity; // The widgets are allowed to signal that the form should be hidden // (because there's no purchasable entity to select, for example). if ($form_state->get('hide_form')) { $form['#access'] = FALSE; } else { $selected_variation = $form_state->get('selected_variation'); // If the order item references a different variation than the one // currently selected, and the selected variation is supposed to use // a different order item type, rebuild the form. if ($selected_variation && $order_item->getPurchasedEntityId() != $selected_variation) { /** @var \Drupal\commerce_product\Entity\ProductVariationInterface $selected_variation */ $selected_variation = $this->entityTypeManager->getStorage('commerce_product_variation')->load($selected_variation); if ($selected_variation->getOrderItemTypeId() !== $order_item->bundle()) { /** @var \Drupal\commerce_order\OrderItemStorageInterface $order_item_storage */ $order_item_storage = $this->entityTypeManager->getStorage('commerce_order_item'); $order_item = $order_item_storage->createFromPurchasableEntity($selected_variation); $this->prepareOrderItem($order_item); $this->setEntity($order_item); $form_display = EntityFormDisplay::collectRenderDisplay($order_item, $this->operation); $this->setFormDisplay($form_display, $form_state); $form = $original_form; $form = parent::buildForm($form, $form_state); } } } return $form; } /** * {@inheritdoc} */ public function setFormDisplay(EntityFormDisplayInterface $form_display, FormStateInterface $form_state) { $component = $form_display->getComponent('purchased_entity'); // If the product references variations of a different type, fallback // to using the title widget as the attributes widget cannot properly // work. if ($component['type'] === 'commerce_product_variation_attributes') { $product = $form_state->get('product'); /** @var \Drupal\commerce_product\ProductVariationStorageInterface $product_variation_storage */ $product_variation_storage = $this->entityTypeManager->getStorage('commerce_product_variation'); $variations = $product_variation_storage->loadEnabled($product); $variation_types = []; foreach ($variations as $variation) { $variation_types[$variation->bundle()] = $variation->bundle(); if (count($variation_types) > 1) { $component['type'] = 'commerce_product_variation_title'; $form_display->setComponent('purchased_entity', $component); $this->setFormDisplay($form_display, $form_state); break; } } } return parent::setFormDisplay($form_display, $form_state); } /** * {@inheritdoc} */ Loading Loading @@ -221,23 +267,32 @@ class AddToCartForm extends ContentEntityForm implements AddToCartFormInterface public function buildEntity(array $form, FormStateInterface $form_state) { /** @var \Drupal\commerce_order\Entity\OrderItemInterface $entity */ $entity = parent::buildEntity($form, $form_state); $this->prepareOrderItem($entity); return $entity; } /** * Helper function used to prepare the order item for add to cart. * * @param \Drupal\commerce_order\Entity\OrderItemInterface $order_item * The order item to prepare. */ protected function prepareOrderItem(OrderItemInterface $order_item) { // Now that the purchased entity is set, populate the title and price. $purchased_entity = $entity->getPurchasedEntity(); $entity->setTitle($purchased_entity->getOrderItemTitle()); if (!$entity->isUnitPriceOverridden()) { $purchased_entity = $order_item->getPurchasedEntity(); $order_item->setTitle($purchased_entity->getOrderItemTitle()); if (!$order_item->isUnitPriceOverridden()) { $store = $this->selectStore($purchased_entity); $context = new Context($this->currentUser, $store); $resolved_price = $this->chainPriceResolver->resolve($purchased_entity, $entity->getQuantity(), $context); $entity->setUnitPrice($resolved_price); $resolved_price = $this->chainPriceResolver->resolve($purchased_entity, $order_item->getQuantity(), $context); $order_item->setUnitPrice($resolved_price); } $order_type_id = $this->orderTypeResolver->resolve($entity); $order_type_id = $this->orderTypeResolver->resolve($order_item); $store = $this->selectStore($purchased_entity); $cart = $this->cartProvider->getCart($order_type_id, $store); if ($cart) { $entity->set('order_id', $cart->id()); $order_item->set('order_id', $cart->id()); } return $entity; } }
modules/cart/tests/src/Functional/MultipleCartMultipleVariationTypesTest.php +1 −0 Original line number Diff line number Diff line Loading @@ -191,6 +191,7 @@ class MultipleCartMultipleVariationTypesTest extends CartBrowserTestBase { 'id' => $id, 'label' => $label, 'variationType' => $variation_type->id(), 'variationTypes' => [], ]); $product_type->save(); } Loading
modules/cart/tests/src/FunctionalJavascript/MultipleCartMultipleVariationTypesTest.php +43 −0 Original line number Diff line number Diff line Loading @@ -50,6 +50,17 @@ class MultipleCartMultipleVariationTypesTest extends CartWebDriverTestBase { $this->createProductAndVariationType('color_sizes', 'Colors and Sizes'); $this->createProductAndVariationType('colors', 'Colors'); $this->createProductAndVariationType('sizes', 'Sizes'); // Create a product type referencing multiple variation types. $product_type = ProductType::create([ 'id' => 'multiple', 'label' => 'Product type referencing multiple variation types', 'variationTypes' => [ 'colors', 'color_sizes', 'sizes', ], ]); $product_type->save(); // Create the attributes. $color_attribute = ProductAttribute::create([ Loading Loading @@ -140,6 +151,24 @@ class MultipleCartMultipleVariationTypesTest extends CartWebDriverTestBase { ['attribute_size' => $this->sizeAttributes['large']->id()], ], ], 'Multiple variation types' => [ 'type' => 'multiple', 'variations' => [ [ 'type' => 'sizes', 'attribute_size' => $this->sizeAttributes['medium']->id(), ], [ 'type' => 'color_sizes', 'attribute_color' => $this->colorAttributes['green']->id(), 'attribute_size' => $this->sizeAttributes['large']->id(), ], [ 'type' => 'colors', 'attribute_color' => $this->colorAttributes['blue']->id(), ] ], ], ]; foreach ($product_matrix as $product_title => $product_data) { /** @var \Drupal\commerce_product\Entity\ProductInterface $product */ Loading Loading @@ -180,13 +209,26 @@ class MultipleCartMultipleVariationTypesTest extends CartWebDriverTestBase { $forms[1]->pressButton('Add to cart'); $this->assertSession()->pageTextContains('My Colors and Sizes FIRST - Green, Medium added to your cart.'); $last_form = end($forms); // Assert the title widget is used when a product references variations of // a different type. $this->assertTrue($last_form->hasField('Please select')); $last_form->selectFieldOption('Please select', 'Multiple variation types - Blue'); $this->assertSession()->assertWaitOnAjaxRequest(); $last_form->pressButton('Add to cart'); $this->assertSession()->pageTextContains('Multiple variation types - Blue added to your cart.'); $this->container->get('entity_type.manager')->getStorage('commerce_order')->resetCache([$this->cart->id()]); $this->cart = Order::load($this->cart->id()); $order_items = $this->cart->getItems(); $this->assertCount(2, $order_items); /** @var \Drupal\commerce_product\Entity\ProductVariationInterface $variation */ $variation = $order_items[0]->getPurchasedEntity(); $this->assertEquals($this->colorAttributes['green']->id(), $variation->getAttributeValueId('attribute_color')); $this->assertEquals($this->sizeAttributes['medium']->id(), $variation->getAttributeValueId('attribute_size')); $variation = $order_items[1]->getPurchasedEntity(); $this->assertEquals($this->colorAttributes['blue']->id(), $variation->getAttributeValueId('attribute_color')); } /** Loading @@ -210,6 +252,7 @@ class MultipleCartMultipleVariationTypesTest extends CartWebDriverTestBase { 'id' => $id, 'label' => $label, 'variationType' => $variation_type->id(), 'variationTypes' => [], ]); $product_type->save(); } Loading
modules/product/commerce_product.links.action.yml +2 −2 Original line number Diff line number Diff line Loading @@ -22,8 +22,8 @@ entity.commerce_product_variation_type.add_form: appears_on: - entity.commerce_product_variation_type.collection entity.commerce_product_variation.add_form: route_name: entity.commerce_product_variation.add_form entity.commerce_product_variation.add_page: route_name: entity.commerce_product_variation.add_page title: 'Add variation' appears_on: - entity.commerce_product_variation.collection
modules/product/commerce_product.post_update.php +3 −1 Original line number Diff line number Diff line Loading @@ -110,7 +110,9 @@ function commerce_product_post_update_6() { $role->hasPermission("update any {$product_type->id()} commerce_product") || $role->hasPermission("update own {$product_type->id()} commerce_product") ) { $role->grantPermission("manage {$product_type->getVariationTypeId()} commerce_product_variation"); foreach ($product_type->getVariationTypeIds() as $type) { $role->grantPermission("manage {$type} commerce_product_variation"); } } } $role->save(); Loading