HEX
Server: Microsoft-IIS/8.5
System: Windows NT YDAWBH120 6.3 build 9600 (Windows Server 2012 R2 Standard Edition) AMD64
User: tentjecom_web (0)
PHP: 7.4.14
Disabled: NONE
Upload Files
File: D:/HostingSpaces/blijegasten/blijegasten.be/app/Komma/Shop/Cart/ShoppingCartService.php
<?php
namespace App\Komma\Shop\Cart;


use App\Komma\Session\HasSessionTrait;
use App\Komma\Shop\Discounts\Actions\ModifyPriceAction;
use App\Komma\Shop\Discounts\DiscountableInterface;
use App\Komma\Shop\Discounts\Discount;
use App\Komma\Shop\Discounts\DiscountServiceInterface;
use App\Komma\Shop\Discounts\DiscountTypes;
use App\Komma\Shop\Products\Product\Product;
use App\Komma\Shop\Products\ProductableInterface;
use App\Komma\Shop\Vat\VatServiceInterface;
use App\Komma\Zipcodes\ZipcodeService;
use Carbon\Carbon;
use Illuminate\Http\Request;

/**
 * Represents a shopping cart
 *
 * Class ShoppingCartService
 * @package App\Komma\Shop\Cart
 */
class ShoppingCartService implements ShoppingCartServiceInterface, HasCouponCodesInterface
{
    use HasSessionTrait;

    use HasCouponCodesTrait {
        addCouponCode as traitAddCouponCode;
        removeCouponCode as traitRemoveCouponCode;
    }

    /** @var array The names of variables in this class to store and restore. */
    protected $variablesForSession = [
        'discounts',
        'couponCodes',
        'deliveryMethod',
        'deliveryLocation',
        'items',
        'date',
    ];

    /**
     * Indicates whether or not if the ShoppingCartService restores all items in the shopping cart by looking at the session variable
     *
     * @var bool
     */
    public static $restoreFromSession = true;

    /**
     * @var ShoppingCartItemInterface[] $items
     */
    private $items = [];

    /**
     * @var Discount[] $discounts
     */
    private $discounts = [];

    private $deliveryLocation;
    private $deliveryMethod;

    /** @var Carbon $date */
    private $date;


    public function __construct()
    {
        $this->discountService = app(DiscountServiceInterface::class);
        $this->restoreFromSession();
    }

    /**
     * Add a productable in the cart
     *
     * @param ProductableInterface $productable
     * @param int $quantity
     * @return ShoppingCartItem
     */
    public function addProductable(ProductableInterface $productable, int $quantity = 1) : ?ShoppingCartItemInterface
    {
        /** @var Product $product */
        if(!$productable) return null;

        /** @var ShoppingCartItemInterface $shoppingCartItem */
        $item = $this->getShoppingCartItemByProductableIdAndType($productable);
        if($item) {
            $item->setQuantity($item->getQuantity() + $quantity);
        } else {
            /** @var ShoppingCartItem $shoppingCartItem */
            $shoppingCartItem = (app(ShoppingCartItemInterface::class));
            $item = $shoppingCartItem->setProductable($productable)->setQuantity($quantity);
            $this->items[] = $item;
        }

        $this->updateDiscounts();
        $this->saveSession();
        return $item;
    }

    /**
     * Returns an existing shoppingCartItem by checking if it has a productable with a
     * certain id or false if there was no shoppingcart item with a
     * productable with the specified productable id
     * @param $productable
     * @return ShoppingCartItemInterface|false
     */
    private function getShoppingCartItemByProductableIdAndType(ProductableInterface $productable)
    {
        foreach($this->items as $item)
        {
            $itemProductable = $item->getProductable();

            /** @var ShoppingCartItemInterface $item */
            if($itemProductable->id == $productable->id && get_class($productable) === get_class($itemProductable)) return $item;
        }

        return false;
    }

    /**
     * Set the amount of items for a product
     *
     * @param int $itemId
     * @param int $quantity
     * @return $this
     */
    public function setItemQuantity(int $itemId, int $quantity = 1): ShoppingCartServiceInterface
    {
        $item = $this->getItemById($itemId);
        if(!$item) return $this;

        $item->setQuantity($quantity);
        $this->updateDiscounts();
        $this->saveSession();

        return $this;
    }


    /**
     * Deletes the ShoppingCartItem that has a product with the specified id
     *
     * @param int $itemId
     * @return ShoppingCartServiceInterface
     */
    public function deleteItem(int $itemId) : ShoppingCartServiceInterface
    {
        foreach($this->items as $index => $item)
        {
//            if($item->getProductable()->id == $productable->id)
//            {
//                array_splice($this->items, $index, 1);
//                break;
//            }

            if ($item->getId() == $itemId) {
                array_splice($this->items, $index, 1);
                break;
            }

        }

        $this->updateDiscounts();
        $this->saveSession();

        return $this;
    }

    /**
     * Clears the shopping cart of all items
     *
     * @return ShoppingCartServiceInterface
     */
    public function clear(): ShoppingCartServiceInterface
    {
        $this->items = [];
        $this->updateDiscounts();
        $this->saveSession();

        return $this;
    }

    /**
     * Get sum of the prices of the products in the shopping cart
     *
     * @return float
     */
    public function getProductTotal(): float
    {
        $price = 0;

        foreach ($this->items as $index => $item) {
            $price += $item->getTotal();
        }

        return $price;
    }

    /**
     * Get the product total and apply the discounts on it.
     * Then add the shipping cost to the price.
     * Remember the base price is in cents. And may be a float (e.g. value in cents with decimals)
     *
     * @return float
     */
    public function getTotal(): float
    {
        $price = $this->getProductTotal();

        foreach($this->discounts as $discount)
        {
            $action = $discount->getDiscountAction();
            switch (get_class($action))
            {
                case ModifyPriceAction::class;
                    $price = $action->do($price);
            }
        }

        if($this->getShippingCost() != 0) {
            $price += $this->getShippingCost();
        }

        return $price;
    }

    /**
     * Reverse calculated the vat
     *
     * @return float
     */
    public function getTotalWithoutVat(): float
    {
        // Product vat
        $vat = $this->getProductTotal() / (100 + config('shop.vat_percentage')) * 100;

        // Shipping vat
        $shippingVat = $this->getShippingCost() / (100 + config('shop.vat_percentage')) * 100;

        // Round the total of the 2 combined
        return round($vat + $shippingVat);
    }

    public function getShippingCost()
    {
        if(empty($this->deliveryLocation)) return 0;

        /** @var ZipcodeService $zipcodeService */
        $zipcodeService = app()->make(ZipcodeService::class);

        if(empty($this->deliveryLocation->country) || empty($this->deliveryLocation->postal_code)) return null;

        $zipcodes = $zipcodeService->getZipcodeByCountryAndZipcodePart($this->deliveryLocation->country, $this->deliveryLocation->postal_code);

        if($zipcodes->count() == 0 || $zipcodes->count() > 1) return null;
        return $zipcodes->first()->price;

    }

    /**
     * Get the deposit amount of the shopping cart
     *
     * @return float
     */
    public function getDeposit(): float
    {
        return round($this->getProductTotal() * (config('shop.deposit_rate_percentage') / 100));
    }

    /**
     * Get the total of the shopping cart with the deposit rate.
     *
     * @return float
     */
    public function getTotalIncludingDeposit(): float
    {
        return $this->getTotal() + $this->getDeposit();
    }

    /**
     * Get the amount of vat from the shopping cart
     *
     * @return float
     * @throws \Illuminate\Contracts\Container\BindingResolutionException
     */
    public function getVat(): float
    {
        // Product vat
        $vat = $this->getProductTotal() / (100 + config('shop.vat_percentage')) * config('shop.vat_percentage');

        // Shipping vat
        $shippingVat = $this->getShippingCost() / (100 + config('shop.vat_percentage')) * config('shop.vat_percentage');

        // Round the total of the 2 combined
        return round($vat + $shippingVat);
    }

    /**
     * Returns the amount of items in the cart.
     * If you specify the $unique argument as true it returns the number of ShoppingCartItems.
     * If it is false it will return the sum of all products in all Shopping cart items
     * If you specify the $includeDiscounts argument as true it takes the Discount into account
     * @param bool $unique
     * @param bool $includeDiscounts
     * @return int
     */
    public function getItemsCount(bool $unique = false, bool $includeDiscounts = false): int
    {
        if($includeDiscounts === false) {
            $itemCount = 0;

            if($unique == true) {
                $products = [];
                foreach($this->items as $shoppingCartItem)
                {
                    /** @var $shoppingCartItem ShoppingCartItem */
                    $productClass = get_class($shoppingCartItem->getProductable());
                    if(!in_array($productClass, $products))
                    {
                        $itemCount++;
                        $products[] = $productClass;
                    }
                }
            } else {
                foreach($this->items as $item) $itemCount = $itemCount + $item->getQuantity();
            }
            return $itemCount;
        } else {
            //TODO Implement me
            return 0;
        }
    }

    /**
     * Get all shopping cart items
     *
     * @return array
     */
    public function getItems(): array
    {
        return !empty($this->items) ? $this->items : [];
    }

    /**
     * Applies the discount to itself
     *
     * @param Discount $discount
     * @return DiscountableInterface
     */
    public function applyDiscount(Discount $discount): DiscountableInterface
    {
        $this->discounts[] = $discount;
        $this->saveSession();

        return $this;
    }

    /**
     * Returns all discounts applied
     *
     * @return Discount[]
     */
    public function getDiscounts(): array
    {
        return $this->discounts;
    }

    /**
     * Returns all discounts applied, from a certain type
     *
     * @param null $type
     * @return Discount[]
     */
    public function getDiscountsByType($type = null): array
    {
        if($type == null) return $this->discounts;

        if(!DiscountTypes::isValidType($type)) throw new \InvalidArgumentException('The given type is not a valid DiscountType.');
        return array_filter($this->discounts, function(Discount $discount) use ($type) {
            return $discount->type == $type;
        });
    }

    /**
     * Get a shoppingCartItem by ID
     *
     * @param $itemId int
     * @return ShoppingCartItemInterface
     */
    private function getItemById(int $itemId): ?ShoppingCartItemInterface
    {
        $returnItem = null;
        foreach($this->items as $index => $item) {
            if ($item->getId() == $itemId) {
                $returnItem = $item;
            }
        }
        return $returnItem;
    }

    /**
     * Validates a coupon code
     *
     * @param string $code
     * @return bool
     */
    public function validateCouponCode(string $code): bool
    {
        return $this->discountService->checkIfCouponCodeCanBeApplied($code, $this);
    }

    /**
     * Removes all discounts
     */
    public function clearDiscounts(): void
    {
        $this->discounts = [];
        foreach($this->items as $shoppingCartItem) $shoppingCartItem->clearDiscounts();
    }

    /**
     * Updates the discounts on the shopping cart and all shoppingCartItems after removing all discounts from them.
     */
    public function updateDiscounts(): void
    {
        $this->clearDiscounts();
        $this->discountService->updateDiscounts($this);
        foreach($this->items as $shoppingCartItem) $this->discountService->updateDiscounts($shoppingCartItem);
    }

    /**
     * Adds a coupon code. Notice that this does not mean that it is valid.
     * Validation is done via the DiscountServiceInterface. Returns true if
     * it was added. false if not
     *
     * @param string $code
     * @return mixed
     */
    public function addCouponCode(string $code): bool
    {
        $result = $this->traitAddCouponCode($code);
        $this->updateDiscounts();
        $this->saveSession();
        return $result;
    }

    /**
     * Removes a coupon code and returns true if it was removed and false if not.
     *
     * @param string $code
     * @return bool
     */
    public function removeCouponCode(string $code): bool
    {
        $result = $this->traitRemoveCouponCode($code);
        $this->updateDiscounts();
        $this->saveSession();
        return $result;
    }

    /**
     * Append the delivery location to session.
     */
    public function setDeliveryLocation($city, $postal_code, $country)
    {
        $this->deliveryLocation = (object)[
            'city' => $city,
            'postal_code' => $postal_code,
            'country' => $country
        ];

        $this->saveSession();
    }

    /**
     * Get the delivery location from the shopping cart
     *
     * @return mixed
     */
    public function getDeliveryLocation()
    {
        return $this->deliveryLocation;
    }

    /**
     *  Clear the delivery location
     */
    public function removeDeliveryLocation()
    {
        $this->deliveryLocation = null;
        $this->saveSession();
    }

    /**
     * Set the delivery method
     *
     * @param  string  $deliveryMethod
     */
    public function setDeliveryMethod(string $deliveryMethod)
    {
        $this->deliveryMethod = $deliveryMethod;
        $this->saveSession();
    }

    /**
     * Get the delivery method
     *
     * @return mixed
     */
    public function getDeliveryMethod()
    {
        return $this->deliveryMethod;
    }

    /**
     * Set the delivery method
     *
     * @param  string  $date
     */
    public function setDate(string $date)
    {
        $dateFormat = Carbon::createFromFormat('d-m-Y', $date);
        $dateFormat->startOfDay();

        $this->date = $dateFormat;
        $this->saveSession();
    }

    /**
     * @return Carbon
     */
    public function getDate()
    {
        return $this->date;
    }

    /**
     * Will be using shipping
     *
     * @return bool
     */
    public function useShipping(): bool
    {
        if($this->deliveryMethod == 'pickup') return false;
        return true;
    }

    /**
     * Determine if we have a product that is marked as a bouncy castle
     *
     * @return bool
     */
    public function containsBouncyCastle(): bool
    {

        foreach ($this->items as $item) {
            $product = $item->getProductable();
            if($product->categories->isEmpty()) continue;
            if($product->categories->contains('code_name', 'bouncy_castle')) return true;
        }

        return false;

    }

    /**
     * Update the delivery location from the given request
     *
     * @param  Request  $request
     */
    public function updateDeliveryLocationFromRequest(Request $request)
    {
        $this->setDeliveryMethod($request->get('pickup_or_ship'));

        // If pickup we remove the city, postal and country
        if($this->deliveryMethod == 'pickup') {
            foreach (['city', 'postal_code','country'] as $requestKey) {
                $request->request->set('invoice_'.$requestKey, null);
            }
            $this->removeDeliveryLocation();
        }
        else {
            $deliveryLocation = (object)[];

            foreach (['city', 'postal_code','country'] as $requestKey) {

                // Also populate the shipping postal, city and country from the request.
                // Because if the user check a different shipping address, it will be these.
                if($request->has('invoice_'.$requestKey)) {
                    $request->request->add(['shipping_'.$requestKey => $request->get('invoice_'.$requestKey)]);
                }

                // Store into the delivery location array
                $deliveryLocation->{$requestKey} = $request->get('invoice_'.$requestKey);
            }
            $this->setDeliveryLocation($deliveryLocation->city, $deliveryLocation->postal_code, $deliveryLocation->country);
        }
    }

}