<?php
 class cart { public static $data = []; public static $items = []; public static $total = []; public static function init() { if (!isset(session::$data['cart']) || !is_array(session::$data['cart'])) { session::$data['cart'] = [ 'uid' => null, ]; } self::$data = &session::$data['cart']; if (empty(self::$data['uid'])) { if (!empty($_COOKIE['cart']['uid'])) { self::$data['uid'] = $_COOKIE['cart']['uid']; } else { self::$data['uid'] = uniqid(); } } if (!isset($_COOKIE['cart']['uid']) || $_COOKIE['cart']['uid'] != self::$data['uid']) { if (!empty($_COOKIE['cookies_accepted']) || !settings::get('cookie_policy')) { header('Set-Cookie: cart[uid]='. self::$data['uid'] .'; Path='. WS_DIR_APP .'; Expires='. gmdate('r', strtotime('+3 months')) .'; SameSite=Lax', false); } } database::query( "delete from ". DB_TABLE_PREFIX ."cart_items
        where date_created < '". date('Y-m-d H:i:s', strtotime('-3 months')) ."';" ); self::load(); if (!empty($_POST['add_cart_product'])) { $options = !empty($_POST['options']) ? $_POST['options'] : []; if (!empty($options)) { foreach (array_keys($options) as $key) { if (empty($options[$key])) { unset($options[$key]); continue; } if (is_array($options[$key])) { $options[$key] = implode(', ', $options[$key]); } } } self::add_product($_POST['product_id'], $options, (isset($_POST['quantity']) ? $_POST['quantity'] : 1)); header('Location: '. $_SERVER['REQUEST_URI']); exit; } if (!empty($_POST['remove_cart_item'])) { self::remove($_POST['remove_cart_item']); header('Location: '. $_SERVER['REQUEST_URI']); exit; } if (!empty($_POST['update_cart_item'])) { self::update($_POST['update_cart_item'], isset($_POST['item'][$_POST['update_cart_item']]['quantity']) ? $_POST['item'][$_POST['update_cart_item']]['quantity'] : 1); header('Location: '. $_SERVER['REQUEST_URI']); exit; } if (!empty($_POST['clear_cart_items'])) { self::clear(); header('Location: '. $_SERVER['REQUEST_URI']); exit; } } public static function reset() { self::$items = []; self::_calculate_total(); } public static function clear() { self::reset(); database::query( "delete from ". DB_TABLE_PREFIX ."cart_items
        where cart_uid = '". database::input(self::$data['uid']) ."';" ); } public static function load() { self::reset(); if (!empty(customer::$data['id'])) { database::query( "update ". DB_TABLE_PREFIX ."cart_items set
          cart_uid = '". database::input(self::$data['uid']) ."',
          customer_id = ". (int)customer::$data['id'] ."
          where cart_uid = '". database::input(self::$data['uid']) ."'
          or customer_id = ". (int)customer::$data['id'] .";" ); } $cart_items_query = database::query( "select * from ". DB_TABLE_PREFIX ."cart_items
        where cart_uid = '". database::input(self::$data['uid']) ."';" ); while ($item = database::fetch($cart_items_query)) { if (!empty(self::$items[$item['key']])) { database::query( "delete from ". DB_TABLE_PREFIX ."cart_items
            where cart_uid = '". database::input(self::$data['uid']) ."'
            and id = ". (int)$item['id'] ."
            limit 1;" ); } self::add_product($item['product_id'], unserialize($item['options']), $item['quantity'], true, $item['key']); if (isset(self::$items[$item['key']])) self::$items[$item['key']]['id'] = $item['id']; } } public static function add_product($product_id, $options, $quantity=1, $force=false, $item_key=null) { $product = reference::product($product_id); $quantity = round((float)$quantity, $product->quantity_unit ? (int)$product->quantity_unit['decimals'] : 0, PHP_ROUND_HALF_UP); if (empty($item_key)) { if (!empty($product->quantity_unit['separate'])) { $item_key = uniqid(); } else { $item_key = md5(json_encode([$product->id, $options])); } } $item = [ 'id' => null, 'product_id' => (int)$product->id, 'options' => $options, 'option_stock_combination' => '', 'image' => $product->image, 'name' => $product->name, 'code' => $product->code, 'sku' => $product->sku, 'mpn' => $product->mpn, 'gtin' => $product->gtin, 'taric' => $product->taric, 'price' => $product->final_price, 'extras' => 0, 'tax' => $product->tax, 'tax_class_id' => $product->tax_class_id, 'quantity' => $quantity, 'quantity_min' => $product->quantity_min ? $product->quantity_min : '0', 'quantity_max' => ($product->quantity_max > 0) ? $product->quantity_max : null, 'quantity_step' => ($product->quantity_step > 0) ? $product->quantity_step : null, 'quantity_unit' => [ 'name' => !empty($product->quantity_unit['name']) ? $product->quantity_unit['name'] : '', 'decimals' => !empty($product->quantity_unit['decimals']) ? $product->quantity_unit['decimals'] : '', 'separate' => !empty($product->quantity_unit['separate']) ? $product->quantity_unit['separate'] : '', ], 'weight' => $product->weight, 'weight_class' => $product->weight_class, 'dim_x' => $product->dim_x, 'dim_y' => $product->dim_y, 'dim_z' => $product->dim_z, 'dim_class' => $product->dim_class, 'error' => '', ]; try { if (!$product->id) { throw new Exception(language::translate('error_item_not_a_valid_product', 'The item is not a valid product')); } if (!$product->status) { throw new Exception(language::translate('error_product_currently_not_available_for_purchase', 'The product is currently not available for purchase')); } if (!empty($product->date_valid_from) && $product->date_valid_from > date('Y-m-d H:i:s')) { throw new Exception(strtr(language::translate('error_product_cannot_be_purchased_until_date', 'The product cannot be purchased until %date'), ['%date' => language::strftime(language::$selected['format_date'], strtotime($product->date_valid_from))])); } if (!empty($product->date_valid_to) && $product->date_valid_to > 1970 && $product->date_valid_to < date('Y-m-d H:i:s')) { throw new Exception(strtr(language::translate('error_product_can_no_longer_be_purchased', 'The product can no longer be purchased as of %date'), ['%date' => language::strftime(language::$selected['format_date'], strtotime($product->date_valid_to))])); } if ($quantity <= 0) { throw new Exception(language::translate('error_invalid_item_quantity', 'Invalid item quantity')); } if ($product->quantity_min > 0 && $quantity < $product->quantity_min) { throw new Exception(strtr(language::translate('error_must_purchase_min_items', 'You must purchase a minimum of %num for this item'), ['%num' => (float)$product->quantity_min])); } if ($product->quantity_max > 0 && $quantity > $product->quantity_max) { throw new Exception(strtr(language::translate('error_cannot_purchase_more_than_max_items', 'You cannot purchase more than %num of this item'), ['%num' => (float)$product->quantity_max])); } if ($product->quantity_step > 0 && (round($quantity / $product->quantity_step, 4) - floor(round($quantity / $product->quantity_step, 4))) != 0) { throw new Exception(strtr(language::translate('error_can_only_purchase_sets_for_item', 'You can only purchase sets by %num for this item'), ['%num' => (float)$product->quantity_step])); } if (empty($product->sold_out_status['orderable']) && empty($product->options_stock)) { if (($product->quantity_available - $quantity - (isset(self::$items[$item_key]) ? self::$items[$item_key]['quantity'] : 0)) < 0) { throw new Exception(strtr(language::translate('error_only_n_remaining_products_available_for_purchase', 'There are only %quantity remaining products available for purchase'), ['%quantity' => round((float)$product->quantity_available, isset($product->quantity_unit['decimals']) ? (int)$product->quantity_unit['decimals'] : 0)])); } } $array_filter_recursive = function($array) use (&$array_filter_recursive) { foreach ($array as $i => $value) { if (is_array($value)) $array[$i] = $array_filter_recursive($value); } return array_filter($array, function($v) { if (is_array($v)) return !empty($v); return strlen(trim($v)); }); }; $options = $array_filter_recursive($options); $sanitized_options = []; foreach ($product->options as $option) { $possible_groups = array_filter(array_unique(reference::attribute_group($option['group_id'])->name)); $matched_groups = array_intersect(array_keys($options), $possible_groups); $matched_group = array_shift($matched_groups); if (empty($matched_group) && empty($option['required'])) { continue; } if (empty($options[$matched_group]) && !empty($option['required'])) { throw new Exception(language::translate('error_set_product_options', 'Please set your product options')); } switch ($option['function']) { case 'checkbox': $selected_values = preg_split('#\s*,\s*#', $options[$matched_group], -1, PREG_SPLIT_NO_EMPTY); $matched_values = []; foreach ($option['values'] as $value) { $possible_values = array_unique( array_merge( [$value['name']], !empty(reference::attribute_group($option['group_id'])->values[$value['value_id']]) ? array_filter(array_values(reference::attribute_group($option['group_id'])->values[$value['value_id']]['name']), 'strlen') : [] ) ); if (empty($option['required'])) { array_unshift($possible_values, ''); } if ($matched_value = array_intersect($selected_values, $possible_values)) { $matched_values[] = $matched_value; $item['extras'] += $value['price_adjust']; $found_match = true; } } if (empty($found_match)) { throw new Exception(strtr(language::translate('error_must_select_valid_option_for_group', 'You must select a valid option for %group'), ['%group' => $matched_group])); } break; case 'radio': case 'select': foreach ($option['values'] as $value) { $possible_values = array_unique( array_merge( [$value['name']], !empty(reference::attribute_group($option['group_id'])->values[$value['value_id']]) ? array_filter(array_values(reference::attribute_group($option['group_id'])->values[$value['value_id']]['name']), 'strlen') : [] ) ); if (empty($option['required'])) { array_unshift($possible_values, ''); } if ($matched_value = array_intersect([$options[$matched_group]], $possible_values)) { $matched_value = array_shift($matched_value); $item['extras'] += $value['price_adjust']; $found_match = true; break; } } if (empty($found_match)) { throw new Exception(strtr(language::translate('error_must_select_valid_option_for_group', 'You must select a valid option for %group'), ['%group' => $matched_group])); } break; case 'text': case 'textarea': $matched_value = $options[$matched_group]; if (empty($matched_value) && !empty($option['required'])) { throw new Exception(strtr(language::translate('error_must_provide_valid_input_for_group', 'You must provide a valid input for %group'), ['%group' => $matched_group])); } break; } $sanitized_options[] = [ 'group_id' => $option['id'], 'value_id' => !empty($value['id']) ? $value['id'] : 0, 'combination' => $option['group_id'] .'-'. (!empty($value['value_id']) ? $value['value_id'] : '0') . (!empty($value['custom_value']) ? ':"'. $value['custom_value'] .'"' : ''), 'name' => $matched_group, 'value' => !empty($matched_values) ? $matched_values : $matched_value, ]; } foreach ($product->options_stock as $option_stock) { $option_match = true; foreach (explode(',', $option_stock['combination']) as $pair) { if (!in_array($pair, array_column($sanitized_options, 'combination'))) { $option_match = false; break; } } if ($option_match) { if (empty($product->sold_out_status['orderable']) && ($option_stock['quantity_available'] - $quantity - (isset(self::$items[$item_key]) ? self::$items[$item_key]['quantity'] : 0)) < 0) { throw new Exception(language::translate('error_not_enough_products_in_stock_available_for_option', 'Not enough products in stock available for the selected option') . ' ('. round((float)$option_stock['quantity_available'], isset($product->quantity_unit['decimals']) ? (int)$product->quantity_unit['decimals'] : 0) .')'); } $item['option_stock_combination'] = $option_stock['combination']; if (!empty($option_stock['sku'])) $item['sku'] = $option_stock['sku']; if (!empty($option_stock['weight'])) $item['weight'] = $option_stock['weight']; if (!empty($option_stock['weight_class'])) $item['weight_class'] = $option_stock['weight_class']; if (!empty($option_stock['dim_x'])) $item['dim_x'] = $option_stock['dim_x']; if (!empty($option_stock['dim_y'])) $item['dim_y'] = $option_stock['dim_y']; if (!empty($option_stock['dim_z'])) $item['dim_z'] = $option_stock['dim_z']; if (!empty($option_stock['dim_class'])) $item['dim_class'] = $option_stock['dim_class']; break; } } } catch(Exception $e) { $item['error'] = $e->getMessage(); if (!$force) { notices::add('errors', $e->getMessage()); return false; } } if (!empty($item['options'])) { foreach (array_keys($item['options']) as $key) { if (is_array($item['options'][$key])) $item['options'][$key] = implode(', ', $item['options'][$key]); } } $item['price'] += $item['extras']; $item['tax'] += tax::get_tax($item['extras'], $product->tax_class_id); if (settings::get('round_amounts')) { $item['price'] = currency::round($item['price'], currency::$selected['code']); $item['tax'] = currency::round($item['tax'], currency::$selected['code']); } if (isset(self::$items[$item_key])) { self::$items[$item_key]['quantity'] += $quantity; } else { self::$items[$item_key] = $item; } if (!empty(self::$items[$item_key]['id'])) { database::query( "update ". DB_TABLE_PREFIX ."cart_items
          set quantity = ". (float)self::$items[$item_key]['quantity'] .",
          date_updated = '". date('Y-m-d H:i:s') ."'
          where cart_uid = '". database::input(self::$data['uid']) ."'
          and `key` = '". database::input($item_key) ."'
          limit 1;" ); } else { if (!$force) { database::query( "insert into ". DB_TABLE_PREFIX ."cart_items
            (customer_id, cart_uid, `key`, product_id, options, quantity, date_updated, date_created)
            values (". (int)customer::$data['id'] .", '". database::input(self::$data['uid']) ."', '". database::input($item_key) ."', ". (int)$item['product_id'] .", '". database::input(serialize($item['options'])) ."', ". (float)$item['quantity'] .", '". date('Y-m-d H:i:s') ."', '". date('Y-m-d H:i:s') ."');" ); self::$items[$item_key]['id'] = database::insert_id(); } } self::_calculate_total(); if (!$force) { notices::add('success', language::translate('success_product_added_to_cart', 'Your product was successfully added to the cart.')); } return true; } public static function update($item_key, $quantity, $force=false) { if (!isset(self::$items[$item_key])) { notices::add('errors', 'The item does not exist in cart.'); return; } if (self::$items[$item_key]['quantity'] == $quantity) { return; } if ($quantity <= 0) { self::remove($item_key, true); return; } $item = self::$items[$item_key]; self::$items[$item_key]['quantity'] = 0; self::add_product($item['product_id'], $item['options'], $quantity, true, $item_key); self::_calculate_total(); if (!$force) { header('Location: '. document::link()); exit; } } public static function remove($item_key, $force=false) { if (!isset(self::$items[$item_key])) return; database::query( "delete from ". DB_TABLE_PREFIX ."cart_items
        where cart_uid = '". database::input(self::$data['uid']) ."'
        and id = ". (int)self::$items[$item_key]['id'] ."
        limit 1;" ); unset(self::$items[$item_key]); self::_calculate_total(); if (!$force) { header('Location: '. document::link()); exit; } } private static function _calculate_total() { self::$total = [ 'items' => 0, 'value' => 0, 'tax' => 0, ]; foreach (self::$items as $item) { $num_items = $item['quantity']; if (!empty($item['quantity_unit']['separate'])) { $num_items = 1; } self::$total['value'] += $item['price'] * $item['quantity']; self::$total['tax'] += $item['tax'] * $item['quantity']; self::$total['items'] += $num_items; } } } 