Added free product coupon.
This commit is contained in:
@@ -66,9 +66,10 @@
|
||||
<tag name="thelia.coupon.addCoupon"/>
|
||||
</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 -->
|
||||
|
||||
|
||||
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