591 lines
15 KiB
PHP
591 lines
15 KiB
PHP
<?php
|
|
|
|
/**
|
|
* Vvveb
|
|
*
|
|
* Copyright (C) 2022 Ziadin Givan
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as
|
|
* published by the Free Software Foundation, either version 3 of the
|
|
* License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
namespace Vvveb\System\Cart;
|
|
|
|
use function \Vvveb\getCurrency;
|
|
use function \Vvveb\model;
|
|
use function \Vvveb\url;
|
|
use Vvveb\Sql\Product_Option_ValueSQL;
|
|
use Vvveb\Sql\Product_VariantSQL;
|
|
use Vvveb\Sql\ProductSQL;
|
|
use Vvveb\Sql\Subscription_PlanSQL;
|
|
use Vvveb\System\Images;
|
|
use Vvveb\System\Session;
|
|
|
|
class Cart {
|
|
protected $cart = [];
|
|
|
|
protected $session;
|
|
|
|
protected $currency;
|
|
|
|
protected $tax;
|
|
|
|
protected $weight;
|
|
|
|
protected $productModel = 'product';
|
|
|
|
protected $cart_id;
|
|
|
|
protected $encrypted_cart_id;
|
|
|
|
protected $sessionKey = 'cart';
|
|
|
|
protected $persist = 'session';
|
|
|
|
protected $options;
|
|
|
|
protected $products = [];
|
|
|
|
protected $taxes = [];
|
|
|
|
protected $totals = [];
|
|
|
|
protected $total = 0;
|
|
|
|
protected $total_tax = 0;
|
|
|
|
protected $total_items = 0;
|
|
|
|
protected $controlCache = true;
|
|
|
|
use TaxTrait, ProductOptionTrait, CouponTrait, TotalTrait, DbTrait;
|
|
|
|
public static function getInstance($options = []) {
|
|
static $inst = null;
|
|
|
|
if ($inst === null) {
|
|
$inst = new Cart($options);
|
|
}
|
|
|
|
return $inst;
|
|
}
|
|
|
|
private function __construct($options = []) {
|
|
$this->session = Session :: getInstance();
|
|
$this->currency = Currency :: getInstance($options);
|
|
$this->tax = Tax :: getInstance();
|
|
$this->weight = Weight :: getInstance();
|
|
|
|
$this->cart_id = $options['cart_id'] ?? null;
|
|
$this->options = $options;
|
|
|
|
if ($this->cart_id && ! is_numeric($this->cart_id)) {
|
|
$cart_id = urldecode($this->cart_id);
|
|
$key = \Vvveb\getConfig('app.key');
|
|
$cart_id = \Vvveb\decrypt($key, $cart_id);
|
|
|
|
if ($cart_id) {
|
|
$this->encrypted_cart_id = $this->cart_id;
|
|
$this->cart_id = $cart_id;
|
|
}
|
|
}
|
|
|
|
$this->read();
|
|
|
|
if (! isset($this->total_items)) {
|
|
$this->total_items = 0;
|
|
}
|
|
|
|
if (! isset($this->total)) {
|
|
$this->total = 0;
|
|
}
|
|
|
|
if (! isset($this->products)) {
|
|
$this->products = [];
|
|
}
|
|
|
|
if ($this->total_items) {
|
|
$this->disableCache();
|
|
} else {
|
|
$this->enableCache();
|
|
}
|
|
}
|
|
|
|
public function create() {
|
|
//calll only if you need a cart id beforehand, otherwise a cart will be created automatically when adding a product if it does not exist
|
|
$this->write();
|
|
|
|
return $this->cart_id;
|
|
}
|
|
|
|
public function getEncryptedId() {
|
|
if ($this->encrypted_cart_id) {
|
|
return $this->encrypted_cart_id;
|
|
}
|
|
|
|
$key = \Vvveb\getConfig('app.key');
|
|
$this->encrypted_cart_id = \Vvveb\encrypt($key, $this->cart_id);
|
|
|
|
return $this->encrypted_cart_id;
|
|
}
|
|
|
|
public function getCartUrl() {
|
|
$id = urlencode($this->getEncryptedId());
|
|
$url = url('cart/cart/index', ['cart_id' => $id]);
|
|
//app does not have url
|
|
if (! $url) {
|
|
$url = "/checkout/$id";
|
|
}
|
|
|
|
return $url;
|
|
}
|
|
|
|
public function getCheckoutUrl() {
|
|
$id = urlencode($this->getEncryptedId());
|
|
$url = url('checkout/checkout/index', ['cart_id' => $id]);
|
|
//app does not have url
|
|
if (! $url) {
|
|
$url = "/checkout/$id";
|
|
}
|
|
|
|
return $url;
|
|
}
|
|
|
|
public function getId() {
|
|
return $this->cart_id;
|
|
}
|
|
|
|
public function updateCart() {
|
|
$this->total = 0;
|
|
$this->total_items = 0;
|
|
|
|
$results = ['products' => [], 'count' => 0];
|
|
$currency = getCurrency();
|
|
|
|
if (! empty($this->products)) {
|
|
$productIds = [];
|
|
$productOptions = [];
|
|
$productVariants = [];
|
|
$productSubscriptions = [];
|
|
|
|
foreach ($this->products as $product) {
|
|
$productId = $product['product_id'];
|
|
$productIds[$productId] = $productId;
|
|
|
|
//get all product options to make one query to get all option values
|
|
if (isset($product['option']) && is_array($product['option'])) {
|
|
foreach ($product['option'] as $value) {
|
|
if (is_numeric($value)) {
|
|
$productOptions[$value] = $value;
|
|
} else {
|
|
$product_option_value_id = $value['product_option_value_id'];
|
|
$productOptions[$product_option_value_id] = $product_option_value_id;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (isset($product['subscription_plan_id']) && $product['subscription_plan_id']) {
|
|
$productSubscriptions[$product['subscription_plan_id']] = $product['subscription_plan_id'];
|
|
}
|
|
|
|
if (isset($product['product_variant_id']) && $product['product_variant_id']) {
|
|
$productVariants[$product['product_variant_id']] = $product['product_variant_id'];
|
|
}
|
|
}
|
|
|
|
//get product data from db for products in cart
|
|
$options = [
|
|
'product_id' => $productIds,
|
|
] + $this->options;
|
|
|
|
$productSql = model($this->productModel); //new ProductSQL();
|
|
$results = $productSql->getAll(
|
|
$options
|
|
);
|
|
|
|
// if products have options get all product options in one query
|
|
$optionResults = [];
|
|
|
|
if ($productOptions) {
|
|
$productOptionValueSql = new Product_Option_ValueSQL();
|
|
$optionResults = $productOptionValueSql->getAll(
|
|
['product_option_value_id' => array_values($productOptions)] + $this->options
|
|
)['product_option_value'] ?? [];
|
|
}
|
|
|
|
// if products have subscriptions get all subscriptions in one query
|
|
$subscriptionResults = [];
|
|
|
|
if ($productSubscriptions) {
|
|
$subscriptionPlanSql = new Subscription_PlanSQL();
|
|
$subscriptionResults = $subscriptionPlanSql->getAll(
|
|
['subscription_plan_id' => array_values($productSubscriptions)] + $this->options
|
|
)['subscription_plan'] ?? [];
|
|
}
|
|
|
|
// if products have variants get all variants in one query
|
|
$variantsResults = [];
|
|
|
|
if ($productVariants) {
|
|
$productVariantSql = new Product_VariantSQL();
|
|
$variantsResults = $productVariantSql->getAll(
|
|
['product_variant_id' => array_values($productVariants)] + $this->options
|
|
)['product_variant'] ?? [];
|
|
}
|
|
}
|
|
|
|
$products = $results['product'] ?? [];
|
|
|
|
if ($products) {
|
|
foreach ($this->products as $key => &$prod) {
|
|
if (! isset($products[$product['product_id']])) {
|
|
unset($this->products[$key]);
|
|
|
|
continue;
|
|
}
|
|
|
|
$productId = $prod['product_id'];
|
|
$product = $products[$productId];
|
|
|
|
$prod['price'] = (float)$product['price'];
|
|
|
|
//add option value data and adjust price if necessary
|
|
if (isset($prod['option']) && is_array($prod['option'])) {
|
|
foreach ($prod['option'] as $option_id => $option) {
|
|
if (is_numeric($option)) {
|
|
$product_option_value_id = $option;
|
|
} else {
|
|
$product_option_value_id = $option['product_option_value_id'];
|
|
}
|
|
|
|
$value = $optionResults[$product_option_value_id];
|
|
|
|
if ($value['price']) {
|
|
if ($value['price_operator'] == '-') {
|
|
$value['price'] -= (float)$value['price'];
|
|
}
|
|
|
|
$prod['price'] += (float) $value['price'];
|
|
|
|
$value['price_tax'] = $this->tax->addTaxes($value['price'], $product['tax_type_id'] ?? 0);
|
|
$value['price_formatted'] = $value['price_operator'] . $this->currency->format($value['price_tax']);
|
|
}
|
|
|
|
if ($value['weight']) {
|
|
if ($value['weight_operator'] == '-') {
|
|
$value['weight'] -= $value['weight'];
|
|
}
|
|
|
|
$prod['weight'] = ($prod['weight'] ?? 0) + $value['weight'];
|
|
$value['weight_formatted'] = $value['weight'];
|
|
}
|
|
|
|
if ($value['points']) {
|
|
if ($value['points_operator'] == '-') {
|
|
$value['points'] -= $value['points'];
|
|
}
|
|
|
|
$prod['points'] += $value['points'];
|
|
$value['points_formatted'] = $value['points'];
|
|
}
|
|
|
|
$prod['option_value'][$product_option_value_id] = $value;
|
|
}
|
|
}
|
|
|
|
//add subscription data
|
|
if (isset($prod['subscription_plan_id']) && $prod['subscription_plan_id']) {
|
|
$prod['subscription'] = $subscriptionResults[$prod['subscription_plan_id']] ?? [];
|
|
$prod['subscription_name'] = $prod['subscription']['name'] ?? '';
|
|
}
|
|
|
|
//if product variant override price and stock
|
|
if (isset($prod['product_variant_id']) && $prod['product_variant_id']) {
|
|
$variant = $variantsResults[$prod['options']] ?? [];
|
|
|
|
if ($variant && $variant['product_variant_id'] == $prod['product_variant_id']) {
|
|
$prod['variant'] = $variant;
|
|
$prod['price'] = $variant['price'];
|
|
$prod['stock_quantity'] = $variant['stock_quantity'];
|
|
}
|
|
}
|
|
|
|
$url = ['slug' => $product['slug'], 'product_id' => $product['product_id']];
|
|
$prod['key'] = $key;
|
|
$prod['url'] = htmlspecialchars(url('product/product/index', $url));
|
|
$prod['remove-url'] = htmlspecialchars(url('cart/cart/remove', $url + ['key' => $key]));
|
|
|
|
$prod['total'] = $prod['price'] * $prod['quantity'];
|
|
$prod['total_formatted'] = $this->currency->format($prod['total']);
|
|
|
|
$taxValue = $this->tax->addTaxes($prod['price'], $product['tax_type_id'], true);
|
|
$prod['price_tax'] = ($taxValue + $prod['price']);
|
|
$prod['tax'] = $prod['price_tax'] * $prod['quantity'];
|
|
$prod['total_tax'] = $prod['tax'];
|
|
$prod['price_currency'] = $currency;
|
|
|
|
$prod['price_tax_formatted'] = $this->currency->format($prod['price_tax']);
|
|
$prod['price_formatted'] = $this->currency->format($product['price']);
|
|
$prod['total_formatted'] = $this->currency->format($prod['total']);
|
|
$prod['total_tax_formatted'] = $this->currency->format($prod['total_tax']);
|
|
|
|
if (isset($products[$productId]['subscription_plan_id'])) {
|
|
//$prod['subscription_plan_id'] = $products[$productId]['subscription_plan_id'];
|
|
}
|
|
|
|
$this->total += $prod['total'];
|
|
$this->total_tax += $prod['total_tax'];
|
|
$this->total_items += $prod['quantity'];
|
|
|
|
$prod = array_replace($product, $prod);
|
|
|
|
if (isset($product['image'])) {
|
|
$prod['image'] = Images::image($product['image'], 'product', 'thumb');
|
|
}
|
|
|
|
unset($prod['content']);
|
|
|
|
// options add to price
|
|
}
|
|
}
|
|
|
|
$this->addTotal('sub_total', 'Sub-total', $this->total);
|
|
|
|
//set cart cookie to disable cache if products in cart
|
|
if ($this->total_items) {
|
|
$this->disableCache();
|
|
} else {
|
|
$this->enableCache();
|
|
}
|
|
//write is done by addTotal
|
|
//$this->write();
|
|
|
|
return $results;
|
|
}
|
|
|
|
function add($productId, $quantity = 1, $option = [], $productVariantId = false, $subscriptionPlanId = false) {
|
|
if (! $productId) {
|
|
return false;
|
|
}
|
|
|
|
$key = $productId;
|
|
$options = '';
|
|
|
|
if ($option && is_array($option)) {
|
|
ksort($option);
|
|
$options = trim(str_replace('"', '', json_encode($option)), '{}');
|
|
$key .= '_' . $options;
|
|
} else {
|
|
$option = [];
|
|
}
|
|
|
|
if ($subscriptionPlanId) {
|
|
$key .= "_$subscriptionPlanId";
|
|
}
|
|
|
|
$key = str_replace('"', '', $key);
|
|
|
|
if (isset($this->products[$key])) {
|
|
$this->products[$key]['quantity'] += $quantity;
|
|
} else {
|
|
$this->products[$key] = [
|
|
'product_id' => (int) $productId,
|
|
'quantity' => (int)$quantity,
|
|
'option' => $option ?? [],
|
|
'options' => $options,
|
|
'subscription_plan_id' => $subscriptionPlanId,
|
|
'product_variant_id' => $productVariantId,
|
|
];
|
|
}
|
|
|
|
$this->updateCart();
|
|
|
|
return $key;
|
|
}
|
|
|
|
function update($key, $quantity = 1, $option = [], $productVariantId = false, $subscriptionPlanId = false) {
|
|
if (isset($this->products[$key])) {
|
|
$this->products[$key]['quantity'] = max(1, $quantity);
|
|
|
|
if ($option) {
|
|
$this->products[$key]['option'] = $option;
|
|
}
|
|
|
|
if ($subscriptionPlanId) {
|
|
$this->products[$key]['subscription_plan_id'] = $subscriptionPlanId;
|
|
}
|
|
|
|
if ($productVariantId) {
|
|
$this->products[$key]['product_variant_id'] = $productVariantId;
|
|
}
|
|
|
|
$this->updateCart();
|
|
|
|
return $key;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function getAll() {
|
|
return $this->products ?? [];
|
|
}
|
|
|
|
function getNoProducts() {
|
|
//return count($this->products ?? []);
|
|
$total = 0;
|
|
|
|
foreach ($this->products as $product) {
|
|
$total += $product['quantity'];
|
|
}
|
|
|
|
return $total;
|
|
}
|
|
|
|
function remove($key) {
|
|
if ($key) {
|
|
if (is_array($key)) {
|
|
foreach ($key as $value) {
|
|
if (isset($this->products[$value])) {
|
|
unset($this->products[$value]);
|
|
}
|
|
}
|
|
} else {
|
|
if (isset($this->products[$key])) {
|
|
unset($this->products[$key]);
|
|
}
|
|
}
|
|
|
|
$this->updateCart();
|
|
|
|
return $key;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public function getSubscription() {
|
|
$product_data = [];
|
|
|
|
foreach ($this->products as $value) {
|
|
if (isset($value['subscription'])) {
|
|
$product_data[] = $value;
|
|
}
|
|
}
|
|
|
|
return $product_data;
|
|
}
|
|
|
|
public function getWeight() {
|
|
$weight = 0;
|
|
|
|
foreach ($this->products as $product) {
|
|
if (isset($product['requires_shipping']) && $product['requires_shipping']) {
|
|
$weight += $this->weight->convert($product['weight'], $product['weight_type_id'], $this->options['weight_type_id']) * $product['quantity'];
|
|
}
|
|
}
|
|
|
|
return $weight;
|
|
}
|
|
|
|
public function countProducts() {
|
|
$product_total = 0;
|
|
|
|
$products = $this->products;
|
|
|
|
foreach ($products as $product) {
|
|
$product_total += $product['quantity'];
|
|
}
|
|
|
|
return $product_total;
|
|
}
|
|
|
|
public function hasProducts() {
|
|
return count($this->products);
|
|
}
|
|
|
|
public function hasSubscription() {
|
|
return count($this->getSubscription());
|
|
}
|
|
|
|
public function hasStock() {
|
|
foreach ($this->products as $product) {
|
|
if (! $product['stock']) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public function hasShipping() {
|
|
foreach ($this->products as $product) {
|
|
if ($product['requires_shipping']) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public function hasDownload() {
|
|
foreach ($this->products as $product) {
|
|
if ($product['digital_asset']) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
protected function read() {
|
|
$data = $this->session->get($this->sessionKey);
|
|
|
|
if (is_array($data)) {
|
|
foreach ($data as $property => $value) {
|
|
$this->$property = $value;
|
|
}
|
|
}
|
|
}
|
|
|
|
protected function write() {
|
|
//$data = get_object_vars($this);
|
|
foreach (['products', 'taxes', 'totals', 'total_items', 'coupons', 'product_options'] as $property) {
|
|
$data[$property] = $this->$property;
|
|
}
|
|
|
|
$this->session->set($this->sessionKey, $data);
|
|
}
|
|
|
|
public function empty() {
|
|
$this->session->set($this->sessionKey, []);
|
|
$this->enableCache();
|
|
}
|
|
*/
|
|
public function enableCache() {
|
|
//enable cache back by clearing the cart cookie
|
|
if ($this->controlCache && isset($_COOKIE['cart'])) {
|
|
setcookie('cart', '', time() - 3600, '/');
|
|
}
|
|
}
|
|
|
|
public function disableCache() {
|
|
//disable cache back setting the cart cookie
|
|
if ($this->controlCache && ! isset($_COOKIE['cart'])) {
|
|
setcookie('cart', '1', 0, '/');
|
|
}
|
|
}
|
|
}
|