diff --git a/modules/cart/src/Form/AddToCartForm.php b/modules/cart/src/Form/AddToCartForm.php index 5e97a0c..d57b883 100644 --- a/modules/cart/src/Form/AddToCartForm.php +++ b/modules/cart/src/Form/AddToCartForm.php @@ -17,6 +17,7 @@ use Drupal\Core\Entity\EntityTypeBundleInfoInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Session\AccountInterface; use Symfony\Component\DependencyInjection\ContainerInterface; +use Drupal\commerce_cart\CartSessionInterface; /** * Provides the order item add to cart form. @@ -26,6 +27,13 @@ class AddToCartForm extends ContentEntityForm implements AddToCartFormInterface /** * The cart manager. * + * @var \Drupal\commerce_cart\CartSessionInterface + */ + protected $cartSession; + + /** + * The cart manager. + * * @var \Drupal\commerce_cart\CartManagerInterface */ protected $cartManager; @@ -81,6 +89,8 @@ class AddToCartForm extends ContentEntityForm implements AddToCartFormInterface * The entity type bundle info. * @param \Drupal\Component\Datetime\TimeInterface $time * The time. + * @param \Drupal\commerce_cart\CartSessionInterface $cart_session + * The cart session. * @param \Drupal\commerce_cart\CartManagerInterface $cart_manager * The cart manager. * @param \Drupal\commerce_cart\CartProviderInterface $cart_provider @@ -94,9 +104,10 @@ class AddToCartForm extends ContentEntityForm implements AddToCartFormInterface * @param \Drupal\Core\Session\AccountInterface $current_user * The current user. */ - public function __construct(EntityManagerInterface $entity_manager, EntityTypeBundleInfoInterface $entity_type_bundle_info, TimeInterface $time, CartManagerInterface $cart_manager, CartProviderInterface $cart_provider, OrderTypeResolverInterface $order_type_resolver, CurrentStoreInterface $current_store, ChainPriceResolverInterface $chain_price_resolver, AccountInterface $current_user) { + public function __construct(EntityManagerInterface $entity_manager, EntityTypeBundleInfoInterface $entity_type_bundle_info, TimeInterface $time, CartSessionInterface $cart_session, CartManagerInterface $cart_manager, CartProviderInterface $cart_provider, OrderTypeResolverInterface $order_type_resolver, CurrentStoreInterface $current_store, ChainPriceResolverInterface $chain_price_resolver, AccountInterface $current_user) { parent::__construct($entity_manager, $entity_type_bundle_info, $time); + $this->cartSession = $cart_session; $this->cartManager = $cart_manager; $this->cartProvider = $cart_provider; $this->orderTypeResolver = $order_type_resolver; @@ -113,6 +124,7 @@ class AddToCartForm extends ContentEntityForm implements AddToCartFormInterface $container->get('entity.manager'), $container->get('entity_type.bundle.info'), $container->get('datetime.time'), + $container->get('commerce_cart.cart_session'), $container->get('commerce_cart.cart_manager'), $container->get('commerce_cart.cart_provider'), $container->get('commerce_order.chain_order_type_resolver'), @@ -181,7 +193,7 @@ class AddToCartForm extends ContentEntityForm implements AddToCartFormInterface protected function actions(array $form, FormStateInterface $form_state) { $actions['submit'] = [ '#type' => 'submit', - '#value' => $this->t('Add to cart'), + '#value' => $form_state->get(['settings', 'skip_cart']) ? $this->t('Checkout') : $this->t('Add to cart'), '#submit' => ['::submitForm'], ]; @@ -202,12 +214,34 @@ class AddToCartForm extends ContentEntityForm implements AddToCartFormInterface $order_type_id = $this->orderTypeResolver->resolve($order_item); $store = $this->selectStore($purchased_entity); $cart = $this->cartProvider->getCart($order_type_id, $store); - if (!$cart) { - $cart = $this->cartProvider->createCart($order_type_id, $store); + if ($form_state->get(['settings', 'skip_cart'])) { + // Create the new order. + /** @var \Drupal\commerce_order\Entity\OrderInterface $order */ + $order = $this->entityTypeManager->getStorage('commerce_order')->create([ + 'type' => $order_type_id, + 'store_id' => $store->id(), + 'uid' => $this->currentUser()->id(), + 'cart' => FALSE, + ]); + + $order->addItem($order_item); + $order->save(); + + // Other submit handlers might need the cart ID. + $form_state->set('cart_id', $order->id()); + + // Add the order as a completed cart to allow anonymous checkout access. + // @todo Find a better way for this. + $this->cartSession->addCartId($order->id(), CartSessionInterface::COMPLETED); + $form_state->setRedirect('commerce_checkout.form', ['commerce_order' => $order->id()]); + }else{ + if (!$cart) { + $cart = $this->cartProvider->createCart($order_type_id, $store); + } + $this->cartManager->addOrderItem($cart, $order_item, $form_state->get(['settings', 'combine'])); + // Other submit handlers might need the cart ID. + $form_state->set('cart_id', $cart->id()); } - $this->cartManager->addOrderItem($cart, $order_item, $form_state->get(['settings', 'combine'])); - // Other submit handlers might need the cart ID. - $form_state->set('cart_id', $cart->id()); } /** diff --git a/modules/cart/tests/src/Functional/AddToCartFormTest.php b/modules/cart/tests/src/Functional/AddToCartFormTest.php index a99e45c..afe568f 100644 --- a/modules/cart/tests/src/Functional/AddToCartFormTest.php +++ b/modules/cart/tests/src/Functional/AddToCartFormTest.php @@ -300,4 +300,55 @@ class AddToCartFormTest extends CartBrowserTestBase { $this->assertSession()->fieldNotExists('purchased_entity[0][attributes][attribute_color]'); } + /** + * Test the skip cart formatter setting. + */ + public function testSkipCart() { + $product = $this->variation->getProduct(); + + // Enable the skip_cart setting. + $display = entity_get_display('commerce_product', $product->bundle(), 'default'); + $component = $display->getComponent('variations'); + $component['settings']['skip_cart'] = TRUE; + $display + ->setComponent('variations', $component) + ->save(); + + // Test the changes to the form when the skip_cart option is enabled. + $this->drupalGet('product/' . $product->id()); + $this->assertSession()->buttonExists('Checkout'); + $this->assertSession()->buttonNotExists('Add to cart'); + + $this->submitForm([], 'Checkout'); + $this->assertSession()->statusCodeEquals(200); + preg_match('|checkout/(\d+)$|', $this->getUrl(), $matches); + $this->assertNotEmpty($matches, 'Correctly redirected to checkout'); + + $order = Order::load($matches[1]); + $this->assertEquals(0, $order->get('cart')->value); + + $order_items = $order->getItems(); + $this->assertNotEmpty(count($order_items) == 1, 'One order item was created.'); + $this->assertOrderItemInOrder($this->variation, $order_items[0], 1); + + // Now test as an anonymous user. + $this->drupalLogout(); + + $this->drupalGet('product/' . $product->id()); + $this->assertSession()->buttonExists('Checkout'); + $this->assertSession()->buttonNotExists('Add to cart'); + + $this->submitForm([], 'Checkout'); + $this->assertSession()->statusCodeEquals(200); + preg_match('|checkout/(\d+)$|', $this->getUrl(), $matches); + $this->assertNotEmpty($matches, 'Correctly redirected to checkout'); + + $order = Order::load($matches[1]); + $this->assertEquals(0, $order->get('cart')->value); + + $order_items = $order->getItems(); + $this->assertNotEmpty(count($order_items) == 1, 'One order item was created.'); + $this->assertOrderItemInOrder($this->variation, $order_items[0], 1); + } + } diff --git a/modules/cart/tests/src/Functional/CartBrowserTestBase.php b/modules/cart/tests/src/Functional/CartBrowserTestBase.php index 644a198..0e5ff4c 100644 --- a/modules/cart/tests/src/Functional/CartBrowserTestBase.php +++ b/modules/cart/tests/src/Functional/CartBrowserTestBase.php @@ -45,6 +45,7 @@ abstract class CartBrowserTestBase extends OrderBrowserTestBase { */ public static $modules = [ 'commerce_cart', + 'commerce_checkout', 'commerce_cart_test', 'node', 'taxonomy', diff --git a/modules/product/config/schema/commerce_product.schema.yml b/modules/product/config/schema/commerce_product.schema.yml index d8d41d1..f8018d1 100644 --- a/modules/product/config/schema/commerce_product.schema.yml +++ b/modules/product/config/schema/commerce_product.schema.yml @@ -83,6 +83,9 @@ field.formatter.settings.commerce_add_to_cart: combine: type: boolean label: 'Whether to attempt to combine order items containing the same product variation' + skip_cart: + type: boolean + label: 'Whether to skip cart and go directly to checkout' field.formatter.settings.commerce_product_attributes_overview: type: mapping diff --git a/modules/product/src/Plugin/Field/FieldFormatter/AddToCartFormatter.php b/modules/product/src/Plugin/Field/FieldFormatter/AddToCartFormatter.php index deb71c0..befd1a8 100644 --- a/modules/product/src/Plugin/Field/FieldFormatter/AddToCartFormatter.php +++ b/modules/product/src/Plugin/Field/FieldFormatter/AddToCartFormatter.php @@ -26,6 +26,7 @@ class AddToCartFormatter extends FormatterBase { public static function defaultSettings() { return [ 'combine' => TRUE, + 'skip_cart' => FALSE, ] + parent::defaultSettings(); } @@ -41,6 +42,13 @@ class AddToCartFormatter extends FormatterBase { '#default_value' => $this->getSetting('combine'), ]; + $form['skip_cart'] = [ + '#type' => 'checkbox', + '#title' => t('Skip cart, create a new order and immediately start the checkout process.'), + '#description' => t('Adds the product to a new order and immediately goes to the checkout page.'), + '#default_value' => $this->getSetting('skip_cart'), + ]; + return $form; } @@ -55,6 +63,9 @@ class AddToCartFormatter extends FormatterBase { else { $summary[] = $this->t('Do not combine order items containing the same product variation.'); } + if ($this->getSetting('skip_cart')) { + $summary[] = $this->t('Skip cart'); + } return $summary; } @@ -70,6 +81,7 @@ class AddToCartFormatter extends FormatterBase { $items->getEntity()->id(), $this->viewMode, $this->getSetting('combine'), + $this->getSetting('skip_cart'), $langcode, ], ], @@ -82,7 +94,9 @@ class AddToCartFormatter extends FormatterBase { * {@inheritdoc} */ public static function isApplicable(FieldDefinitionInterface $field_definition) { - $has_cart = \Drupal::moduleHandler()->moduleExists('commerce_cart'); + // This formatter requires both commerce_checkout and commerce_cart enabled, + // commerce_checkout depends on cart. + $has_cart = \Drupal::moduleHandler()->moduleExists('commerce_checkout'); $entity_type = $field_definition->getTargetEntityTypeId(); $field_name = $field_definition->getName(); return $has_cart && $entity_type == 'commerce_product' && $field_name == 'variations'; diff --git a/modules/product/src/ProductLazyBuilders.php b/modules/product/src/ProductLazyBuilders.php index 92db20e..ef98479 100644 --- a/modules/product/src/ProductLazyBuilders.php +++ b/modules/product/src/ProductLazyBuilders.php @@ -64,7 +64,7 @@ class ProductLazyBuilders { * @return array * A renderable array containing the cart form. */ - public function addToCartForm($product_id, $view_mode, $combine, $langcode) { + public function addToCartForm($product_id, $view_mode, $combine, $skip_cart, $langcode) { /** @var \Drupal\commerce_order\OrderItemStorageInterface $order_item_storage */ $order_item_storage = $this->entityTypeManager->getStorage('commerce_order_item'); /** @var \Drupal\commerce_product\Entity\ProductInterface $product */ @@ -90,6 +90,7 @@ class ProductLazyBuilders { 'view_mode' => $view_mode, 'settings' => [ 'combine' => $combine, + 'skip_cart' => $skip_cart, ], ]);