Added free product coupon.
This commit is contained in:
@@ -66,9 +66,10 @@
|
|||||||
<tag name="thelia.coupon.addCoupon"/>
|
<tag name="thelia.coupon.addCoupon"/>
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
|
<service id="thelia.coupon.type.free_product" class="Thelia\Coupon\Type\FreeProduct">
|
||||||
|
<argument type="service" id="thelia.facade" />
|
||||||
|
<tag name="thelia.coupon.addCoupon"/>
|
||||||
|
</service>
|
||||||
|
|
||||||
<!-- Condition module -->
|
<!-- Condition module -->
|
||||||
|
|
||||||
|
|||||||
347
core/lib/Thelia/Coupon/Type/FreeProduct.php
Normal file
347
core/lib/Thelia/Coupon/Type/FreeProduct.php
Normal file
@@ -0,0 +1,347 @@
|
|||||||
|
<?php
|
||||||
|
/*************************************************************************************/
|
||||||
|
/* This file is part of the Thelia package. */
|
||||||
|
/* */
|
||||||
|
/* Copyright (c) OpenStudio */
|
||||||
|
/* email : dev@thelia.net */
|
||||||
|
/* web : http://www.thelia.net */
|
||||||
|
/* */
|
||||||
|
/* For the full copyright and license information, please view the LICENSE.txt */
|
||||||
|
/* file that was distributed with this source code. */
|
||||||
|
/*************************************************************************************/
|
||||||
|
|
||||||
|
namespace Thelia\Coupon\Type;
|
||||||
|
|
||||||
|
use Thelia\Core\Event\Cart\CartEvent;
|
||||||
|
use Thelia\Core\Event\TheliaEvents;
|
||||||
|
use Thelia\Core\Translation\Translator;
|
||||||
|
use Thelia\Coupon\FacadeInterface;
|
||||||
|
use Thelia\Model\CartItem;
|
||||||
|
use Thelia\Model\CartItemQuery;
|
||||||
|
use Thelia\Model\Product;
|
||||||
|
use Thelia\Model\ProductQuery;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allow to remove an amount from the checkout total
|
||||||
|
*
|
||||||
|
* @package Coupon
|
||||||
|
* @author Franck Allimant <franck@cqfdev.fr>
|
||||||
|
*/
|
||||||
|
class FreeProduct extends AbstractRemoveOnProducts
|
||||||
|
{
|
||||||
|
const OFFERED_PRODUCT_ID = 'offered_product_id';
|
||||||
|
const OFFERED_CATEGORY_ID = 'offered_category_id';
|
||||||
|
|
||||||
|
/** @var string Service Id */
|
||||||
|
protected $serviceId = 'thelia.coupon.type.free_product';
|
||||||
|
|
||||||
|
protected $offeredProductId;
|
||||||
|
protected $offeredCategoryId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This constant is user to mark a free product as in the process of being added to the cart,
|
||||||
|
* but the CartItem ID is not yet been defined.
|
||||||
|
*/
|
||||||
|
const ADD_TO_CART_IN_PROCESS = -1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
public function setFieldsValue($effects)
|
||||||
|
{
|
||||||
|
$this->offeredProductId = $effects[self::OFFERED_PRODUCT_ID];
|
||||||
|
$this->offeredCategoryId = $effects[self::OFFERED_CATEGORY_ID];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
public function getCartItemDiscount($cartItem)
|
||||||
|
{
|
||||||
|
// This method is not used, we use our own implementation of exec();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string The session variable where the cart item IDs for the free products are stored
|
||||||
|
*/
|
||||||
|
protected function getSessionVarName() {
|
||||||
|
return "coupon.free_product.cart_items." . $this->getCode();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Return the cart item id which contains the free product related to a given product
|
||||||
|
*
|
||||||
|
* @param Product $product the product in the cart which triggered the discount
|
||||||
|
*
|
||||||
|
* @return bool|int|CartItem the cart item which contains the free product, or false if the product is no longer in the cart, or ADD_TO_CART_IN_PROCESS if the adding process is not finished
|
||||||
|
*/
|
||||||
|
protected function getRelatedCartItem($product) {
|
||||||
|
|
||||||
|
$cartItemIdList = $this->facade->getRequest()->getSession()->get(
|
||||||
|
$this->getSessionVarName(),
|
||||||
|
array()
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isset($cartItemIdList[$product->getId()])) {
|
||||||
|
|
||||||
|
$cartItemId = $cartItemIdList[$product->getId()];
|
||||||
|
|
||||||
|
if ($cartItemId == self::ADD_TO_CART_IN_PROCESS) {
|
||||||
|
return self::ADD_TO_CART_IN_PROCESS;
|
||||||
|
}
|
||||||
|
else if (null !== $cartItem = CartItemQuery::create()->findPk($cartItemId)) {
|
||||||
|
return $cartItem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Maybe the product we're offering is already in the cart ? Search it.
|
||||||
|
$cartItems = $this->facade->getCart()->getCartItems();
|
||||||
|
|
||||||
|
/** @var CartItem $cartItem */
|
||||||
|
foreach ($cartItems as $cartItem) {
|
||||||
|
if ($cartItem->getProduct()->getId() == $this->offeredProductId) {
|
||||||
|
|
||||||
|
// We found the product. Store its cart item as the free product container.
|
||||||
|
$this->setRelatedCartItem($product, $cartItem->getId());
|
||||||
|
|
||||||
|
return $cartItem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the cart item id which contains the free product related to a given product
|
||||||
|
*
|
||||||
|
* @param Product $product the product in the cart which triggered the discount
|
||||||
|
* @param bool|int $cartItemId the cart item ID which contains the free product, or just true if the free product is not yet added.
|
||||||
|
*/
|
||||||
|
protected function setRelatedCartItem($product, $cartItemId) {
|
||||||
|
|
||||||
|
$cartItemIdList = $this->facade->getRequest()->getSession()->get(
|
||||||
|
$this->getSessionVarName(),
|
||||||
|
array()
|
||||||
|
);
|
||||||
|
|
||||||
|
if (! is_array($cartItemIdList)) $cartItemIdList = array();
|
||||||
|
|
||||||
|
$cartItemIdList[$product->getId()] = $cartItemId;
|
||||||
|
|
||||||
|
$this->facade->getRequest()->getSession()->set(
|
||||||
|
$this->getSessionVarName(),
|
||||||
|
$cartItemIdList
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the product id / cart item id list.
|
||||||
|
*
|
||||||
|
* @return array an array where the free product ID is the key, and the related cart item id the value.
|
||||||
|
*/
|
||||||
|
protected function getFreeProductsCartItemIds() {
|
||||||
|
return $this->facade->getRequest()->getSession()->get(
|
||||||
|
$this->getSessionVarName(),
|
||||||
|
array()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the session variable.
|
||||||
|
*/
|
||||||
|
protected function clearFreeProductsCartItemIds() {
|
||||||
|
return $this->facade->getRequest()->getSession()->remove($this->getSessionVarName());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We overload this method here to remove the free products when the
|
||||||
|
* coupons conditions are no longer met.
|
||||||
|
*
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
public function isMatching() {
|
||||||
|
$match = parent::isMatching();
|
||||||
|
|
||||||
|
if (! $match) {
|
||||||
|
// Cancel coupon effect (but no not remove the product)
|
||||||
|
$this->clearFreeProductsCartItemIds();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $match;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
public function exec()
|
||||||
|
{
|
||||||
|
$discount = 0;
|
||||||
|
|
||||||
|
$cartItems = $this->facade->getCart()->getCartItems();
|
||||||
|
|
||||||
|
/** @var Product $eligibleProduct */
|
||||||
|
$eligibleProduct = null;
|
||||||
|
|
||||||
|
/** @var CartItem $cartItem */
|
||||||
|
foreach ($cartItems as $cartItem) {
|
||||||
|
if (in_array($cartItem->getProduct()->getId(), $this->product_list)) {
|
||||||
|
if (! $cartItem->getPromo() || $this->isAvailableOnSpecialOffers()) {
|
||||||
|
$eligibleProduct = $cartItem;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($eligibleProduct !== null) {
|
||||||
|
|
||||||
|
// Get the cart item for the eligible product
|
||||||
|
$freeProductCartItem = $this->getRelatedCartItem($eligibleProduct);
|
||||||
|
|
||||||
|
// We add the free product it only if it not yet in the cart.
|
||||||
|
if ($freeProductCartItem === false) {
|
||||||
|
|
||||||
|
if (null !== $freeProduct = ProductQuery::create()->findPk($this->offeredProductId)) {
|
||||||
|
|
||||||
|
// Store in the session that the free product is added to the cart,
|
||||||
|
// so that we don't enter the following infinite loop :
|
||||||
|
//
|
||||||
|
// 1) exec() adds a product by firing a CART_ADDITEM event,
|
||||||
|
// 2) the event is processed by Action\Coupon::updateOrderDiscount(),
|
||||||
|
// 3) Action\Coupon::updateOrderDiscount() calls CouponManager::getDiscount()
|
||||||
|
// 4) CouponManager::getDiscount() calls exec() -> Infinite loop !!
|
||||||
|
|
||||||
|
// Store a marker first, we do not have the cart item id yet.
|
||||||
|
$this->setRelatedCartItem($eligibleProduct, self::ADD_TO_CART_IN_PROCESS);
|
||||||
|
|
||||||
|
$cartEvent = new CartEvent($this->facade->getCart());
|
||||||
|
|
||||||
|
$cartEvent->setNewness(true);
|
||||||
|
$cartEvent->setAppend(false);
|
||||||
|
$cartEvent->setQuantity(1);
|
||||||
|
$cartEvent->setProductSaleElementsId($freeProduct->getDefaultSaleElements()->getId());
|
||||||
|
$cartEvent->setProduct($this->offeredProductId);
|
||||||
|
|
||||||
|
$this->facade->getDispatcher()->dispatch(TheliaEvents::CART_ADDITEM, $cartEvent);
|
||||||
|
|
||||||
|
// Store the final cart item ID.
|
||||||
|
$this->setRelatedCartItem($eligibleProduct, $cartEvent->getCartItem()->getId());
|
||||||
|
|
||||||
|
$freeProductCartItem = $cartEvent->getCartItem();
|
||||||
|
|
||||||
|
// Setting product price is dangerous, as the customer could change the ordered quantity of this product.
|
||||||
|
// We will instead add the product price to the order discount.
|
||||||
|
// $cartEvent->getCartItem()->setPrice(0)->save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($freeProductCartItem instanceof CartItem) {
|
||||||
|
|
||||||
|
$taxCountry = $this->facade->getDeliveryCountry();
|
||||||
|
|
||||||
|
// The discount is the product price.
|
||||||
|
$discount = $freeProductCartItem->getPromo() ?
|
||||||
|
$freeProductCartItem->getPromoPrice() : $freeProductCartItem->getPrice();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// No eligible product was found !
|
||||||
|
else {
|
||||||
|
// Remove all free products for this coupon, but no not remove the product from the cart.
|
||||||
|
$this->clearFreeProductsCartItemIds();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $discount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
protected function getFieldList()
|
||||||
|
{
|
||||||
|
return $this->getBaseFieldList([self::OFFERED_CATEGORY_ID, self::OFFERED_PRODUCT_ID]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
protected function checkCouponFieldValue($fieldName, $fieldValue)
|
||||||
|
{
|
||||||
|
$this->checkBaseCouponFieldValue($fieldName, $fieldValue);
|
||||||
|
|
||||||
|
if ($fieldName === self::OFFERED_PRODUCT_ID) {
|
||||||
|
|
||||||
|
if (floatval($fieldValue) < 0) {
|
||||||
|
throw new \InvalidArgumentException(
|
||||||
|
Translator::getInstance()->trans(
|
||||||
|
'Please select the offered product'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ($fieldName === self::OFFERED_CATEGORY_ID) {
|
||||||
|
if (empty($fieldValue)) {
|
||||||
|
throw new \InvalidArgumentException(
|
||||||
|
Translator::getInstance()->trans(
|
||||||
|
'Please select the category of the offred product'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $fieldValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get I18n name
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getName()
|
||||||
|
{
|
||||||
|
return $this->facade
|
||||||
|
->getTranslator()
|
||||||
|
->trans('Free product when buying one or more selected products', array(), 'coupon');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
public function getToolTip()
|
||||||
|
{
|
||||||
|
$toolTip = $this->facade
|
||||||
|
->getTranslator()
|
||||||
|
->trans(
|
||||||
|
'This coupon adds a free product to the cart if one of the selected products is in the cart.',
|
||||||
|
array(),
|
||||||
|
'coupon'
|
||||||
|
);
|
||||||
|
|
||||||
|
return $toolTip;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
public function drawBackOfficeInputs()
|
||||||
|
{
|
||||||
|
return $this->drawBaseBackOfficeInputs("coupon/type-fragments/free-product.html", [
|
||||||
|
'offered_category_field_name' => $this->makeCouponFieldName(self::OFFERED_CATEGORY_ID),
|
||||||
|
'offered_category_value' => $this->offeredCategoryId,
|
||||||
|
|
||||||
|
'offered_product_field_name' => $this->makeCouponFieldName(self::OFFERED_PRODUCT_ID),
|
||||||
|
'offered_product_value' => $this->offeredProductId
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
public function clear()
|
||||||
|
{
|
||||||
|
// Clear the session variable when the coupon is cleared.
|
||||||
|
$this->clearFreeProductsCartItemIds();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user