commerce_stock_enforcement.module in Commerce Stock 8
Commerce stock enforcement module file.
File
modules/enforcement/commerce_stock_enforcement.moduleView source
<?php
/**
* @file
* Commerce stock enforcement module file.
*/
use Drupal\commerce\Context;
use Drupal\commerce\PurchasableEntityInterface;
use Drupal\commerce\Response\NeedsRedirectException;
use Drupal\commerce_order\Entity\OrderInterface;
use Drupal\commerce_product\Entity\ProductVariation;
use Drupal\Component\Utility\Xss;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
use Drupal\views\Form\ViewsForm;
use Symfony\Component\HttpFoundation\RedirectResponse;
/**
* Implements hook_form_alter().
*/
function commerce_stock_enforcement_form_alter(&$form, FormStateInterface $form_state, $form_id) {
// Add to cart form.
if (strpos($form_id, "commerce_order_item_add_to_cart_form") !== FALSE || strpos($form_id, "commerce_order_item_dc_ajax_add_cart_form") !== FALSE) {
/** @var \Drupal\commerce_product\Entity\ProductInterface $product */
$product = $form_state
->get('product');
// Get the product variation.
$selected_variation_id = $form_state
->get('selected_variation');
if (!empty($selected_variation_id)) {
$selected_variation = ProductVariation::load($selected_variation_id);
}
else {
$selected_variation = $product
->getDefaultVariation();
}
// Get the context.
$context = commerce_stock_enforcement_get_context($selected_variation);
// Add a form validate needed for the add to cart action.
$form['#validate'] = array_merge($form['#validate'], [
'commerce_stock_enforcement_add_to_cart_form_validate',
]);
// Check if in stock.
$instock = commerce_stock_enforcement_check($selected_variation, 1, $context);
if (!$instock) {
$form['actions']['submit']['#value'] = t('Out of stock');
$form['actions']['submit']['#disabled'] = TRUE;
// If quantity is visible.
if (isset($form['quantity'])) {
$form['quantity']['#disabled'] = TRUE;
}
}
}
// Cart page.
if ($form_state
->getFormObject() instanceof ViewsForm) {
/** @var \Drupal\views\ViewExecutable $view */
$view = reset($form_state
->getBuildInfo()['args']);
// Only add the Checkout button if the cart form view has order items.
if ($view->storage
->get('tag') == 'commerce_cart_form' && !empty($view->result)) {
// Get the order ID from the view argument.
$order_id = $view->args[0];
/** @var \Drupal\commerce_order\Entity\OrderInterface $order */
$order = \Drupal::entityTypeManager()
->getStorage('commerce_order')
->load($order_id);
// Force a check to display the stock state to the user.
$request_method = \Drupal::requestStack()
->getCurrentRequest()
->getMethod();
// If a GET e.g. not a submit/post.
if ($request_method == 'GET') {
// Perform a check to display the stock state to the user.
commerce_stock_enforcement_is_order_in_stock($order, TRUE);
}
// Add a form validate needed for the add to cart action.
$form['#validate'] = array_merge($form['#validate'], [
'commerce_stock_enforcement_cart_order_item_views_form_validate',
]);
}
}
// Checkout.
if (strpos($form_id, "commerce_checkout_flow") !== FALSE && $form_state
->getFormObject()
->getBaseFormId() == 'commerce_checkout_flow') {
/** @var Drupal\Core\Form\FormInterface $form_object */
$form_object = $form_state
->getFormObject();
/** @var \Drupal\commerce_order\Entity\OrderInterface $order */
$order = $form_object
->getOrder();
if ($form['#step_id'] != 'complete' && !commerce_stock_enforcement_is_order_in_stock($order, FALSE)) {
// Redirect back to cart.
$response = new RedirectResponse('/cart');
$response
->send();
}
// Add a submit validate.
$form['#validate'] = array_merge($form['#validate'], [
'commerce_stock_enforcement_checkout_form_validate',
]);
}
}
/**
* Validates the add to cart form submit.
*
* @param array $form
* Nested array of form elements that comprise the form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The form state.
*/
function commerce_stock_enforcement_add_to_cart_form_validate(array $form, FormStateInterface $form_state) {
// Get add to cart quantity.
$values = $form_state
->getValues();
if (isset($values['quantity'])) {
$quantity = $values['quantity'][0]['value'];
}
else {
$quantity = 1;
}
// Load the product variation.
if (isset($values['purchased_entity'][0]['variation'])) {
$variation_id = $values['purchased_entity'][0]['variation'];
/** @var \Drupal\commerce\PurchasableEntityInterface $purchased_entity */
$purchased_entity = ProductVariation::load($variation_id);
}
else {
$purchased_entity = ProductVariation::load($values['purchased_entity'][0]['target_id']);
}
// ** @var Drupal\Core\Form\FormInterface $form_object */
// $entity_form = $form_state->getFormObject();
//
// $order_item = $entity_form->getEntity();
// ** @var \Drupal\commerce\PurchasableEntityInterface $purchased_entity */
// $purchased_entity = $order_item->getPurchasedEntity();
$context = commerce_stock_enforcement_get_context($purchased_entity);
// Get the available stock level.
$stock_level = commerce_stock_enforcement_get_stock_level($purchased_entity, $context);
// Get the already ordered quantity.
$already_ordered = commerce_stock_enforcement_get_ordered_quantity($purchased_entity, $context);
$total_requested = $already_ordered + $quantity;
if ($total_requested <= $stock_level) {
return;
}
if ($already_ordered === 0) {
$message_text = Drupal::config('commerce_stock_enforcement.settings')
->get('insufficient_stock_add_to_cart_zero_in_cart');
$message_text = Xss::filter($message_text);
$message = t($message_text, [
'%qty' => $stock_level,
'%qty_asked' => $quantity,
]);
}
else {
$message_text = Drupal::config('commerce_stock_enforcement.settings')
->get('insufficient_stock_add_to_cart_quantity_in_cart');
$message_text = Xss::filter($message_text);
$message = t($message_text, [
'%qty' => $stock_level,
'%qty_o' => $already_ordered,
]);
}
$form_state
->setError($form, $message);
}
/**
* Validate the cart page submit.
*
* @param array $form
* Nested array of form elements that comprise the form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The form state.
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
*/
function commerce_stock_enforcement_cart_order_item_views_form_validate(array $form, FormStateInterface $form_state) {
$triggering_element = $form_state
->getTriggeringElement();
// If triggered by a line item delete.
if (isset($triggering_element['#remove_order_item']) && $triggering_element['#remove_order_item']) {
// No need to validate.
return;
}
$values = $form_state
->getValues();
if (isset($values['edit_quantity'])) {
$quantities = $values['edit_quantity'];
}
else {
$quantities = [];
}
/** @var \Drupal\views\ViewExecutable $view */
$view = reset($form_state
->getBuildInfo()['args']);
// Get the order ID from the view argument.
$order_id = $view->argument['order_id']->value[0];
/** @var \Drupal\commerce_order\Entity\OrderInterface $order */
$order = \Drupal::entityTypeManager()
->getStorage('commerce_order')
->load($order_id);
foreach ($order
->getItems() as $id => $order_item) {
$purchased_entity = $order_item
->getPurchasedEntity();
if (!$purchased_entity) {
// Not every order item has a purchased entity.
continue;
}
$name = $purchased_entity
->getTitle();
if (isset($quantities) && isset($quantities[$id])) {
$qty = $quantities[$id];
}
else {
$qty = 1;
}
$context = commerce_stock_enforcement_get_context($purchased_entity);
$stock_level = commerce_stock_enforcement_get_stock_level($purchased_entity, $context);
// Get the already ordered quantity.
$already_ordered = commerce_stock_enforcement_get_ordered_quantity($purchased_entity, $context);
if ($qty > $stock_level) {
$message_text = Drupal::config('commerce_stock_enforcement.settings')
->get('insufficient_stock_cart');
$message_text = Xss::filter($message_text);
$form_state
->setError($form['edit_quantity'][$id], t($message_text, [
'%name' => $name,
'%qty' => $stock_level,
]));
}
}
}
/**
* Validate the checkout form submit.
*
* @param array $form
* Nested array of form elements that comprise the form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The form state.
*
* @throws \Drupal\commerce\Response\NeedsRedirectException
*/
function commerce_stock_enforcement_checkout_form_validate(array $form, FormStateInterface $form_state) {
$triggering_element = $form_state
->getTriggeringElement();
/** @var Drupal\Core\Form\FormInterface $form_object */
$form_object = $form_state
->getFormObject();
/** @var \Drupal\commerce_order\Entity\OrderInterface $order */
$order = $form_object
->getOrder();
if ($form['#step_id'] != 'complete' && !commerce_stock_enforcement_is_order_in_stock($order, FALSE)) {
$cart_page = Url::fromRoute('commerce_cart.page', [], [
'absolute' => TRUE,
]);
\Drupal::messenger()
->addError('One or more Items are out of stock. Checkout canceled!');
throw new NeedsRedirectException($cart_page
->toString());
}
}
/**
* Get the context for the provided Purchasable Entity.
*
* @param \Drupal\commerce\PurchasableEntityInterface $entity
* The purchasable entity.
*
* @return \Drupal\commerce\Context
* The context.
*
* @see \Drupal\commerce_stock\ContextCreatorTrait::getContextDetails()
* @see \Drupal\commerce_cart\Form\AddToCartForm::selectStore()
*/
function commerce_stock_enforcement_get_context(PurchasableEntityInterface $entity) {
// @todo - think about using selectStore() in commerce_cart.module.
$store_to_use = \Drupal::service('commerce_store.current_store')
->getStore();
$current_user = \Drupal::currentUser();
// Make sure the current store is in the entity stores.
$stores = $entity
->getStores();
$found = FALSE;
// If we have a current store.
if ($store_to_use) {
// Make sure it is associated with the curent product.
foreach ($stores as $store) {
if ($store
->id() == $store_to_use
->id()) {
$found = TRUE;
break;
}
}
}
// If not found and we have stores associated with the product.
if (!$found) {
if (!empty($stores)) {
// Get the first store the product is assigned to.
$store_to_use = array_shift($stores);
}
}
return new Context($current_user, $store_to_use);
}
/**
* Check if the PurchasableEntity is in stock.
*
* @param \Drupal\commerce\PurchasableEntityInterface $entity
* The purchasable entity.
* @param int $quantity
* The quantity.
* @param \Drupal\commerce\Context $context
* The context.
*
* @return bool
* True if entity is in stock, FALSE otherwise.
*/
function commerce_stock_enforcement_check(PurchasableEntityInterface $entity, $quantity, Context $context) {
if (empty($quantity)) {
$quantity = 1;
}
$stock_level = commerce_stock_enforcement_get_stock_level($entity, $context);
return $stock_level >= $quantity;
}
/**
* Check if order is in stock.
*
* If order contains products that are out of stock, then error messages will be
* generated and the user redirected to the cart page.
*
* @param \Drupal\commerce_order\Entity\OrderInterface $order
* The order.
* @param bool $show_warnings
* Whether to show warning or not.
*
* @return bool
* True if order is in stock, False if not.
*
* @ToDo Needs refactoring. This function does to much. Job is here to check
* if all purchasable entities are in stock. Factor out the warnings part.
*/
function commerce_stock_enforcement_is_order_in_stock(OrderInterface $order, $show_warnings = TRUE) {
/** @var Drupal\commerce_store\Entity\StoreInterface $order_store */
$order_store = $order
->getStore();
/** @var Drupal\user\UserInterface $order_user */
$order_user = $order
->getCustomer();
$order_context = new Context($order_user, $order_store);
$order_in_stock = TRUE;
foreach ($order
->getItems() as $id => $order_item) {
$purchased_entity = $order_item
->getPurchasedEntity();
if (!$purchased_entity) {
// Not every order item has a purchased entity.
continue;
}
$name = $purchased_entity
->getTitle();
$qty = $order_item
->getQuantity();
$stock_level = commerce_stock_enforcement_get_stock_level($purchased_entity, $order_context);
if ($qty > $stock_level) {
if ($show_warnings) {
$message_text = Drupal::config('commerce_stock_enforcement.settings')
->get('insufficient_stock_cart');
$message_text = Xss::filter($message_text);
\Drupal::messenger()
->addError(t($message_text, [
'%name' => $name,
'%qty' => $stock_level,
]));
}
$order_in_stock = FALSE;
}
}
return $order_in_stock;
}
/**
* Get the available stock level for the PurchasableEntity.
*
* @param \Drupal\commerce\PurchasableEntityInterface $entity
* The purchasable entity.
* @param \Drupal\commerce\Context $context
* The context object.
*
* @return int
* The stock level.
*/
function commerce_stock_enforcement_get_stock_level(PurchasableEntityInterface $entity, Context $context) {
/** @var \Drupal\commerce_stock\StockServiceManagerInterface $stockManager */
$stockManager = \Drupal::service('commerce_stock.service_manager');
/** @var \Drupal\commerce_stock\StockServiceInterface $stock_service */
$stock_service = $stockManager
->getService($entity);
/** @var \Drupal\commerce_stock\StockCheckInterface $stock_checker */
$stock_checker = $stock_service
->getStockChecker();
if ($stock_checker
->getIsAlwaysInStock($entity)) {
return PHP_INT_MAX;
}
$stock_config = $stock_service
->getConfiguration();
$stock_level = $stock_checker
->getTotalStockLevel($entity, $stock_config
->getAvailabilityLocations($context, $entity));
return $stock_level;
}
/**
* Get the quantity already ordered for the specified PurchasableEntity.
*
* @param \Drupal\commerce\PurchasableEntityInterface $entity
* The purchasable entity.
* @param \Drupal\commerce\Context $context
* The context object.
*
* @return int
* The ordered quantity.
*/
function commerce_stock_enforcement_get_ordered_quantity(PurchasableEntityInterface $entity, Context $context) {
// Get the already ordered quantity.
$already_ordered = 0;
// Get all the carts.
$all_carts = \Drupal::service('commerce_cart.cart_provider')
->getCarts();
// Cycle all the carts to get the total stock already ordered.
// It is unlikely that a product will be in more then one cart, but it is
// probably safer to check.
foreach ($all_carts as $cart) {
foreach ($cart
->getItems() as $order_item) {
$purchased_entity = $order_item
->getPurchasedEntity();
if ($purchased_entity && $purchased_entity
->id() == $entity
->id()) {
$already_ordered += $order_item
->getQuantity();
}
}
}
return $already_ordered;
}
Functions
Name | Description |
---|---|
commerce_stock_enforcement_add_to_cart_form_validate | Validates the add to cart form submit. |
commerce_stock_enforcement_cart_order_item_views_form_validate | Validate the cart page submit. |
commerce_stock_enforcement_check | Check if the PurchasableEntity is in stock. |
commerce_stock_enforcement_checkout_form_validate | Validate the checkout form submit. |
commerce_stock_enforcement_form_alter | Implements hook_form_alter(). |
commerce_stock_enforcement_get_context | Get the context for the provided Purchasable Entity. |
commerce_stock_enforcement_get_ordered_quantity | Get the quantity already ordered for the specified PurchasableEntity. |
commerce_stock_enforcement_get_stock_level | Get the available stock level for the PurchasableEntity. |
commerce_stock_enforcement_is_order_in_stock | Check if order is in stock. |