Commit 42765dee authored by Jonathan Sacksick's avatar Jonathan Sacksick Committed by Jonathan Sacksick
Browse files

Issue #3089040 by jsacksick, dwkitchen, danmer, recidive, b_sharpe, khiminrm:...

Issue #3089040 by jsacksick, dwkitchen, danmer, recidive, b_sharpe, khiminrm: Single product with multiple variation types.
parent 1773befd
Loading
Loading
Loading
Loading
+73 −18
Original line number Diff line number Diff line
@@ -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;
@@ -124,14 +126,6 @@ class AddToCartForm extends ContentEntityForm implements AddToCartFormInterface
    );
  }

  /**
   * {@inheritdoc}
   */
  public function setEntity(EntityInterface $entity) {
    $this->entity = $entity;
    return $this;
  }

  /**
   * {@inheritdoc}
   */
@@ -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}
   */
@@ -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;
  }

}
+1 −0
Original line number Diff line number Diff line
@@ -191,6 +191,7 @@ class MultipleCartMultipleVariationTypesTest extends CartBrowserTestBase {
      'id' => $id,
      'label' => $label,
      'variationType' => $variation_type->id(),
      'variationTypes' => [],
    ]);
    $product_type->save();
  }
+43 −0
Original line number Diff line number Diff line
@@ -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([
@@ -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 */
@@ -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'));
  }

  /**
@@ -210,6 +252,7 @@ class MultipleCartMultipleVariationTypesTest extends CartWebDriverTestBase {
      'id' => $id,
      'label' => $label,
      'variationType' => $variation_type->id(),
      'variationTypes' => [],
    ]);
    $product_type->save();
  }
+2 −2
Original line number Diff line number Diff line
@@ -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
+3 −1
Original line number Diff line number Diff line
@@ -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