JEMBOT MAWOT Bypass Shell
<?php
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://devdocs.prestashop.com/ for more information.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
*/
declare(strict_types=1);
namespace PrestaShop\PrestaShop\Adapter\Order;
use Cart;
use Configuration;
use Context;
use Currency;
use Customer;
use Customization;
use Db;
use Order;
use OrderDetail;
use OrderInvoice;
use Pack;
use PrestaShop\PrestaShop\Adapter\Cart\Comparator\CartProductsComparator;
use PrestaShop\PrestaShop\Adapter\Cart\Comparator\CartProductUpdate;
use PrestaShop\PrestaShop\Adapter\ContextStateManager;
use PrestaShop\PrestaShop\Adapter\Order\Refund\OrderProductRemover;
use PrestaShop\PrestaShop\Adapter\StockManager;
use PrestaShop\PrestaShop\Core\Domain\Order\Exception\OrderException;
use PrestaShop\PrestaShop\Core\Domain\Product\Exception\ProductOutOfStockException;
use Product;
use Shop;
use StockAvailable;
use StockManagerFactory;
use StockMvt;
use Warehouse;
/**
* Increase or decrease quantity of an order's product.
* Recalculate cart rules, order's prices and shipping infos.
*/
class OrderProductQuantityUpdater
{
/**
* @var OrderAmountUpdater
*/
private $orderAmountUpdater;
/**
* @var ContextStateManager
*/
private $contextStateManager;
/**
* @var OrderProductRemover
*/
private $orderProductRemover;
public function __construct(
OrderAmountUpdater $orderAmountUpdater,
OrderProductRemover $orderProductRemover,
ContextStateManager $contextStateManager
) {
$this->orderAmountUpdater = $orderAmountUpdater;
$this->orderProductRemover = $orderProductRemover;
$this->contextStateManager = $contextStateManager;
}
/**
* @param Order $order
* @param OrderDetail $orderDetail
* @param int $newQuantity
* @param OrderInvoice|null $orderInvoice
* @param bool $updateCart Used when you don't want to update the cart (CartRule removal for example)
*
* @return Order
*
* @throws OrderException
* @throws \PrestaShopDatabaseException
* @throws \PrestaShopException
*/
public function update(
Order $order,
OrderDetail $orderDetail,
int $newQuantity,
?OrderInvoice $orderInvoice,
bool $updateCart = true
): Order {
$cart = new Cart($order->id_cart);
$this->contextStateManager
->saveCurrentContext()
->setCart($cart)
->setCurrency(new Currency($cart->id_currency))
->setCustomer(new Customer($cart->id_customer))
->setLanguage($cart->getAssociatedLanguage())
->setCountry($cart->getTaxCountry())
->setShop(new Shop($cart->id_shop))
;
try {
$this->updateOrderDetail($order, $cart, $orderDetail, $newQuantity, $orderInvoice, $updateCart);
// Update prices on the order after cart rules are recomputed
$this->orderAmountUpdater->update($order, $cart, null !== $orderInvoice ? (int) $orderInvoice->id : null);
} finally {
$this->contextStateManager->restorePreviousContext();
}
return $order;
}
/**
* @param Order $order
* @param Cart $cart
* @param OrderDetail $orderDetail
* @param int $newQuantity
* @param OrderInvoice|null $orderInvoice
* @param bool $updateCart
*
* @throws OrderException
* @throws ProductOutOfStockException
* @throws \PrestaShopDatabaseException
* @throws \PrestaShopException
*/
private function updateOrderDetail(
Order $order,
Cart $cart,
OrderDetail $orderDetail,
int $newQuantity,
?OrderInvoice $orderInvoice,
bool $updateCart
): void {
$oldQuantity = (int) $orderDetail->product_quantity;
// Perform deletion first, we don't want the OrderDetail to be saved with a quantity 0, this could lead to bugs
if (0 === $newQuantity) {
// Product deletion
$cartComparator = $this->orderProductRemover->deleteProductFromOrder($order, $orderDetail, $updateCart);
$this->updateCustomizationOnProductDelete($order, $orderDetail, $oldQuantity);
$this->applyOtherProductUpdates($order, $cart, $orderInvoice, $cartComparator->getUpdatedProducts());
$this->applyOtherProductCreation($order, $cart, $orderInvoice, $cartComparator->getAdditionalProducts());
} else {
$this->assertValidProductQuantity($orderDetail, $newQuantity);
// It's important to override the invoice, this is what allows to switch an OrderDetail from an invoice to another
if (null !== $orderInvoice) {
$orderDetail->id_order_invoice = $orderInvoice->id;
}
$orderDetail->product_quantity = $newQuantity;
$orderDetail->reduction_percent = 0;
$orderDetail->update();
// Update quantity on the cart and stock
if ($updateCart) {
$cartComparator = $this->updateProductQuantity($cart, $orderDetail, $oldQuantity, $newQuantity);
$this->applyOtherProductUpdates($order, $cart, $orderInvoice, $cartComparator->getUpdatedProducts());
$this->applyOtherProductCreation($order, $cart, $orderInvoice, $cartComparator->getAdditionalProducts());
} elseif ($orderDetail->id_customization > 0) {
$customization = new Customization($orderDetail->id_customization);
$customization->quantity = $newQuantity;
$customization->save();
}
}
// Update product stocks
$this->updateStocks($cart, $orderDetail, $oldQuantity, $newQuantity);
}
/**
* @param Order $order
* @param Cart $cart
* @param OrderInvoice|null $orderInvoice
* @param CartProductUpdate[] $updatedProducts
*/
private function applyOtherProductUpdates(
Order $order,
Cart $cart,
?OrderInvoice $orderInvoice,
array $updatedProducts
): void {
// Some products have been affected by the removal of the initial product (probably related to a CartRule)
// So we detect the changes that happened in the cart and apply them on the OrderDetail
$orderDetails = $order->getOrderDetailList();
foreach ($updatedProducts as $updatedProduct) {
$updatedCombinationId = $updatedProduct->getCombinationId() !== null
? $updatedProduct->getCombinationId()->getValue()
: 0;
$updatedOrderDetail = null;
foreach ($orderDetails as $orderDetailData) {
if ((int) $orderDetailData['product_id'] === $updatedProduct->getProductId()->getValue()
&& (int) $orderDetailData['product_attribute_id'] === $updatedCombinationId) {
$updatedOrderDetail = new OrderDetail($orderDetailData['id_order_detail']);
break;
}
}
if (null !== $updatedOrderDetail) {
$newUpdatedQuantity = (int) $updatedOrderDetail->product_quantity + $updatedProduct->getDeltaQuantity();
// Important: we update the OrderDetail but not the cart (it is already updated) to avoid infinite loop
$this->updateOrderDetail(
$order,
$cart,
$updatedOrderDetail,
$newUpdatedQuantity,
$orderInvoice,
false
);
}
}
}
/**
* @param Order $order
* @param Cart $cart
* @param OrderInvoice|null $orderInvoice
* @param array $createdProducts
*/
private function applyOtherProductCreation(
Order $order,
Cart $cart,
?OrderInvoice $orderInvoice,
array $createdProducts
): void {
$productsToAdd = [];
foreach ($createdProducts as $createdProduct) {
$updatedCombinationId = $createdProduct->getCombinationId() !== null
? $createdProduct->getCombinationId()->getValue()
: 0;
foreach ($cart->getProducts() as $product) {
if ((int) $product['id_product'] === $createdProduct->getProductId()->getValue()
&& (int) $product['id_product_attribute'] === $updatedCombinationId) {
$productsToAdd[] = $product;
break;
}
}
}
if (count($productsToAdd) > 0) {
$orderDetail = new OrderDetail();
$orderDetail->createList(
$order,
$cart,
$order->getCurrentState(),
$productsToAdd,
$orderInvoice ? $orderInvoice->id : 0
);
}
}
/**
* @param Cart $cart
* @param OrderDetail $orderDetail
* @param int $oldQuantity
* @param int $newQuantity
*
* @return CartProductsComparator
*/
private function updateProductQuantity(
Cart $cart,
OrderDetail $orderDetail,
int $oldQuantity,
int $newQuantity
): CartProductsComparator {
$cartComparator = new CartProductsComparator($cart);
$deltaQuantity = $newQuantity - $oldQuantity;
if (0 === $deltaQuantity) {
return $cartComparator;
}
$knownUpdates = [
new CartProductUpdate(
(int) $orderDetail->product_id,
(int) $orderDetail->product_attribute_id,
$deltaQuantity,
false,
(int) $orderDetail->id_customization
),
];
$cartComparator->setKnownUpdates($knownUpdates);
/**
* Here we update product and customization in the cart.
*
* The last argument "skip quantity check" is set to true because
* 1) the quantity has already been checked,
* 2) (main reason) when the cart checks the availability ; it substracts
* its own quantity from available stock.
*
* This is because a product in a cart is not really out of the stock, because it is not checked out yet.
*
* Here we are editing an order, not a cart, so what has been ordered
* has already been substracted from the stock.
*/
$updateQuantityResult = $cart->updateQty(
abs($deltaQuantity),
$orderDetail->product_id,
$orderDetail->product_attribute_id,
$orderDetail->id_customization,
$deltaQuantity < 0 ? 'down' : 'up',
0,
new Shop($cart->id_shop),
true,
true
);
if (-1 === $updateQuantityResult) {
throw new \LogicException('Minimum quantity is not respected');
} elseif (true !== $updateQuantityResult) {
throw new \LogicException('Something went wrong');
}
return $cartComparator;
}
/**
* @param Cart $cart
* @param OrderDetail $orderDetail
* @param int $oldQuantity
* @param int $newQuantity
*
* @throws OrderException
* @throws \PrestaShopDatabaseException
* @throws \PrestaShopException
*/
private function updateStocks(Cart $cart, OrderDetail $orderDetail, int $oldQuantity, int $newQuantity): void
{
$deltaQuantity = $oldQuantity - $newQuantity;
if (0 === $deltaQuantity) {
return;
}
if (0 === $newQuantity) {
// Product deletion. Reinject quantity in stock
$this->reinjectQuantity($orderDetail, $oldQuantity, $newQuantity, true);
} elseif ($deltaQuantity > 0) {
// Increase product quantity
StockAvailable::updateQuantity(
$orderDetail->product_id,
$orderDetail->product_attribute_id,
$deltaQuantity,
$cart->id_shop,
true,
[
'id_order' => $orderDetail->id_order,
'id_stock_mvt_reason' => Configuration::get('PS_STOCK_CUSTOMER_RETURN_REASON'),
]
);
} else {
// Decrease product quantity. Reinject quantity in stock
$this->reinjectQuantity($orderDetail, $oldQuantity, $newQuantity, false);
}
}
/**
* @param OrderDetail $orderDetail
* @param int $oldQuantity
* @param int $newQuantity
* @param bool $delete
*
* @throws OrderException
* @throws \PrestaShopDatabaseException
* @throws \PrestaShopException
*/
protected function reinjectQuantity(
OrderDetail $orderDetail,
int $oldQuantity,
int $newQuantity,
$delete = false
) {
// Reinject product
$reinjectableQuantity = $oldQuantity - $newQuantity;
$quantityToReinject = $oldQuantity > $reinjectableQuantity ? $reinjectableQuantity : $oldQuantity;
$product = new Product(
$orderDetail->product_id,
false,
(int) Context::getContext()->language->id,
(int) $orderDetail->id_shop
);
if (Configuration::get('PS_ADVANCED_STOCK_MANAGEMENT')
&& $product->advanced_stock_management
&& $orderDetail->id_warehouse != 0
) {
$manager = StockManagerFactory::getManager();
$movements = StockMvt::getNegativeStockMvts(
$orderDetail->id_order,
$orderDetail->product_id,
$orderDetail->product_attribute_id,
$quantityToReinject
);
foreach ($movements as $movement) {
if ($quantityToReinject > $movement['physical_quantity']) {
$quantityToReinject = $movement['physical_quantity'];
}
if (Pack::isPack((int) $product->id)) {
// Gets items
if ($product->pack_stock_type == Pack::STOCK_TYPE_PRODUCTS_ONLY
|| $product->pack_stock_type == Pack::STOCK_TYPE_PACK_BOTH
|| ($product->pack_stock_type == Pack::STOCK_TYPE_DEFAULT
&& Configuration::get('PS_PACK_STOCK_TYPE') > 0)
) {
$products_pack = Pack::getItems((int) $product->id, (int) Configuration::get('PS_LANG_DEFAULT'));
// Foreach item
foreach ($products_pack as $product_pack) {
if ($product_pack->advanced_stock_management == 1) {
$manager->addProduct(
$product_pack->id,
$product_pack->id_pack_product_attribute,
new Warehouse($movement['id_warehouse']),
$product_pack->pack_quantity * $quantityToReinject,
null,
$movement['price_te']
);
}
}
}
if ($product->pack_stock_type == Pack::STOCK_TYPE_PACK_ONLY
|| $product->pack_stock_type == Pack::STOCK_TYPE_PACK_BOTH
|| (
$product->pack_stock_type == Pack::STOCK_TYPE_DEFAULT
&& (Configuration::get('PS_PACK_STOCK_TYPE') == Pack::STOCK_TYPE_PACK_ONLY
|| Configuration::get('PS_PACK_STOCK_TYPE') == Pack::STOCK_TYPE_PACK_BOTH)
)
) {
$manager->addProduct(
$orderDetail->product_id,
$orderDetail->product_attribute_id,
new Warehouse($movement['id_warehouse']),
$quantityToReinject,
null,
$movement['price_te']
);
}
} else {
$manager->addProduct(
$orderDetail->product_id,
$orderDetail->product_attribute_id,
new Warehouse($movement['id_warehouse']),
$quantityToReinject,
null,
$movement['price_te']
);
}
}
$productId = $orderDetail->product_id;
if ($delete) {
$orderDetail->delete();
}
StockAvailable::synchronize($productId);
} elseif ($orderDetail->id_warehouse == 0) {
StockAvailable::updateQuantity(
$orderDetail->product_id,
$orderDetail->product_attribute_id,
$quantityToReinject,
$orderDetail->id_shop,
true,
[
'id_order' => $orderDetail->id_order,
'id_stock_mvt_reason' => Configuration::get('PS_STOCK_CUSTOMER_RETURN_REASON'),
]
);
// sync all stock
(new StockManager())->updatePhysicalProductQuantity(
(int) $orderDetail->id_shop,
(int) Configuration::get('PS_OS_ERROR'),
(int) Configuration::get('PS_OS_CANCELED'),
null,
(int) $orderDetail->id_order
);
if ($delete) {
$orderDetail->delete();
}
} else {
throw new OrderException('This product cannot be re-stocked.');
}
}
/**
* @param Order $order
* @param OrderDetail $orderDetail
* @param int $oldQuantity
*
* @throws OrderException
*/
private function updateCustomizationOnProductDelete(Order $order, OrderDetail $orderDetail, int $oldQuantity): void
{
if (!(int) $order->getCurrentState()) {
throw new OrderException('Could not get a valid Order state before deletion');
}
if ($order->hasBeenPaid()) {
Db::getInstance()->execute('UPDATE `' . _DB_PREFIX_ . 'customization` SET `quantity_refunded` = `quantity_refunded` + ' . (int) $oldQuantity . ' WHERE `id_customization` = ' . (int) $orderDetail->id_customization . ' AND `id_cart` = ' . (int) $order->id_cart . ' AND `id_product` = ' . (int) $orderDetail->product_id);
}
if (!Db::getInstance()->execute('DELETE FROM `' . _DB_PREFIX_ . 'customization` WHERE `quantity` = 0')) {
throw new OrderException('Could not delete customization from database.');
}
}
/**
* @param OrderDetail $orderDetail
* @param int $newQuantity
*
* @throws ProductOutOfStockException
*/
private function assertValidProductQuantity(OrderDetail $orderDetail, int $newQuantity)
{
//check if product is available in stock
if (!Product::isAvailableWhenOutOfStock(StockAvailable::outOfStock($orderDetail->product_id))) {
$availableQuantity = StockAvailable::getQuantityAvailableByProduct(
$orderDetail->product_id,
$orderDetail->product_attribute_id,
$orderDetail->id_shop
);
$quantityDiff = $newQuantity - (int) $orderDetail->product_quantity;
if ($quantityDiff > $availableQuantity) {
throw new ProductOutOfStockException('Not enough products in stock');
}
}
}
}
xxxxx1.0, XXX xxxx