v1.0.0.1
StocketBase
- <?php
-
- declare(strict_types=1);
-
- require_once __DIR__ . '/includes/config.php';
- require_once __DIR__ . '/includes/database.php';
- require_once __DIR__ . '/includes/user-auth.php';
- require_once __DIR__ . '/includes/csrf.php';
- require_once __DIR__ . '/includes/language.php';
- require_once __DIR__ . '/includes/site-front.php';
- require_once __DIR__ . '/includes/cart.php';
- require_once __DIR__ . '/includes/currency.php';
-
- UserAuth::boot();
- Cart::boot();
-
- $settings = SiteFront::settings();
-
- function cartIsSafeReturnPath(string $path): bool
- {
- return $path !== ''
- && !str_starts_with($path, '//')
- && !str_contains($path, '://')
- && preg_match('#^[a-zA-Z0-9_.\-/]+\.php(\?[^\s]*)?$#', $path) === 1;
- }
-
- if ($_SERVER['REQUEST_METHOD'] === 'POST') {
- if (!Csrf::verify($_POST['csrf_token'] ?? null)) {
- $_SESSION['cart_flash'] = ['type' => 'error', 'message' => Language::get('form_security_failed', 'Security check failed, please try again.')];
- header('Location: cart.php');
- exit;
- }
-
- $action = (string) ($_POST['action'] ?? '');
- $redirectTo = 'cart.php';
-
- switch ($action) {
- case 'add':
- $productId = (int) ($_POST['product_id'] ?? 0);
- $variantId = isset($_POST['variant_id']) && $_POST['variant_id'] !== '' ? (int) $_POST['variant_id'] : null;
- $quantity = max(1, (int) ($_POST['quantity'] ?? 1));
-
- $countBefore = Cart::count();
- if ($productId > 0) {
- Cart::add($productId, $variantId, $quantity);
- Cart::items();
- }
- $_SESSION['cart_flash'] = Cart::count() > $countBefore
- ? ['type' => 'success', 'message' => Language::get('cart_added', 'Added to cart.')]
- : ['type' => 'error', 'message' => Language::get('cart_unavailable', 'This product is not available for purchase right now.')];
-
- $returnTo = (string) ($_POST['return_to'] ?? '');
- if (cartIsSafeReturnPath($returnTo)) {
- $redirectTo = $returnTo;
- }
- break;
-
- case 'update':
- $productId = (int) ($_POST['product_id'] ?? 0);
- $variantId = isset($_POST['variant_id']) && $_POST['variant_id'] !== '' ? (int) $_POST['variant_id'] : null;
- $quantity = (int) ($_POST['quantity'] ?? 1);
- if ($productId > 0) {
- Cart::updateQuantity($productId, $variantId, $quantity);
- $_SESSION['cart_flash'] = ['type' => 'success', 'message' => Language::get('cart_updated', 'Cart updated.')];
- }
- break;
-
- case 'remove':
- $productId = (int) ($_POST['product_id'] ?? 0);
- $variantId = isset($_POST['variant_id']) && $_POST['variant_id'] !== '' ? (int) $_POST['variant_id'] : null;
- if ($productId > 0) {
- Cart::remove($productId, $variantId);
- $_SESSION['cart_flash'] = ['type' => 'success', 'message' => Language::get('cart_item_removed', 'Item removed.')];
- }
- break;
-
- case 'apply_discount':
- $code = trim((string) ($_POST['code'] ?? ''));
- $subtotal = Cart::subtotalCents();
- $currency = Cart::currency();
- $discount = Cart::findDiscount($code, $currency);
-
- if ($code === '') {
- $_SESSION['cart_flash'] = ['type' => 'error', 'message' => Language::get('cart_discount_empty', 'Please enter a discount code.')];
- } elseif ($discount === null) {
- $_SESSION['cart_flash'] = ['type' => 'error', 'message' => Language::get('cart_discount_invalid', 'That discount code is invalid or has expired.')];
- } elseif ($discount['min_order_cents'] !== null && $subtotal < (int) $discount['min_order_cents']) {
- $_SESSION['cart_flash'] = ['type' => 'error', 'message' => Language::get('cart_discount_minimum', 'Your order does not meet the minimum for this discount code.')];
- } else {
- Cart::setDiscountCode($code);
- $_SESSION['cart_flash'] = ['type' => 'success', 'message' => Language::get('cart_discount_applied_short', 'Discount code applied.')];
- }
- break;
-
- case 'remove_discount':
- Cart::setDiscountCode(null);
- $_SESSION['cart_flash'] = ['type' => 'success', 'message' => Language::get('cart_discount_removed', 'Discount code removed.')];
- break;
-
- case 'restore_from_client':
- if (!UserAuth::check() && Cart::isEmpty()) {
- $decoded = json_decode((string) ($_POST['items'] ?? ''), true);
- if (is_array($decoded)) {
- $restored = 0;
- foreach ($decoded as $entry) {
- if (!is_array($entry) || $restored >= 100) {
- continue;
- }
- $productId = (int) ($entry['product_id'] ?? 0);
- $variantId = isset($entry['variant_id']) && $entry['variant_id'] !== null ? (int) $entry['variant_id'] : null;
- $quantity = (int) ($entry['quantity'] ?? 0);
- if ($productId > 0 && $quantity > 0) {
- Cart::add($productId, $variantId, $quantity);
- $restored++;
- }
- }
- }
- }
- break;
- }
-
- header('Location: ' . $redirectTo);
- exit;
- }
-
- $flash = $_SESSION['cart_flash'] ?? null;
- unset($_SESSION['cart_flash']);
-
- $items = Cart::items();
- $subtotalCents = Cart::subtotalCents();
- $cartCurrency = Cart::currency();
-
- $appliedCode = Cart::appliedDiscountCode();
- $appliedDiscount = $appliedCode !== null ? Cart::findDiscount($appliedCode, $cartCurrency) : null;
- if ($appliedCode !== null && $appliedDiscount === null) {
- Cart::setDiscountCode(null);
- $appliedCode = null;
- }
- $discountCents = $appliedDiscount !== null ? Cart::discountAmountCents($appliedDiscount, $subtotalCents) : 0;
- $totalCents = max(0, $subtotalCents - $discountCents);
-
- $pageTitle = Language::get('cart_title', 'Your cart') . ' - ' . $settings['site_name'];
- $pageDescription = '';
-
- require __DIR__ . '/includes/front-header.php';
-
- ?>
- <h1 class="article-title"><?= htmlspecialchars(Language::get('cart_title', 'Your cart')) ?></h1>
-
- <?php if ($flash !== null): ?>
- <p style="color: var(--<?= $flash['type'] === 'error' ? 'danger' : 'success' ?>);"><?= htmlspecialchars($flash['message']) ?></p>
- <?php endif; ?>
-
- <?php foreach (SiteFront::activeAds('checkout') as $checkoutAd): ?>
- <?= SiteFront::renderAd($checkoutAd) ?>
- <?php endforeach; ?>
-
- <?php if (empty($items)): ?>
- <p><?= htmlspecialchars(Language::get('cart_empty', 'Your cart is empty.')) ?></p>
- <p><a href="index.php" class="unlock-btn"><?= htmlspecialchars(Language::get('cart_continue_shopping', 'Continue shopping')) ?></a></p>
- <?php else: ?>
- <table class="dash-table cart-table">
- <thead>
- <tr>
- <th><?= htmlspecialchars(Language::get('cart_column_product', 'Product')) ?></th>
- <th><?= htmlspecialchars(Language::get('cart_column_price', 'Price')) ?></th>
- <th><?= htmlspecialchars(Language::get('cart_column_quantity', 'Quantity')) ?></th>
- <th><?= htmlspecialchars(Language::get('cart_column_total', 'Total')) ?></th>
- <th></th>
- </tr>
- </thead>
- <tbody>
- <?php foreach ($items as $item): ?>
- <tr>
- <td>
- <a href="<?= htmlspecialchars(SiteFront::productUrl($item['slug'])) ?>" class="cart-item-link">
- <?php if (!empty($item['cover_image_path'])): ?>
- <img src="<?= htmlspecialchars($item['cover_image_path']) ?>" alt="" class="cart-item-image">
- <?php endif; ?>
- <span><?= htmlspecialchars($item['title']) ?><?= $item['variant_name'] !== null ? ' — ' . htmlspecialchars($item['variant_name']) : '' ?></span>
- </a>
- </td>
- <td><?= htmlspecialchars(Currency::format($item['unit_price_cents'], $item['currency'])) ?></td>
- <td>
- <form method="post" class="cart-quantity-form">
- <?= Csrf::field() ?>
- <input type="hidden" name="action" value="update">
- <input type="hidden" name="product_id" value="<?= (int) $item['product_id'] ?>">
- <?php if ($item['variant_id'] !== null): ?>
- <input type="hidden" name="variant_id" value="<?= (int) $item['variant_id'] ?>">
- <?php endif; ?>
- <input type="number" name="quantity" value="<?= (int) $item['quantity'] ?>" min="0" max="999" style="width:70px;">
- <button type="submit" class="dash-btn-small"><?= htmlspecialchars(Language::get('cart_update_button', 'Update')) ?></button>
- </form>
- </td>
- <td><?= htmlspecialchars(Currency::format($item['total_cents'], $item['currency'])) ?></td>
- <td>
- <form method="post">
- <?= Csrf::field() ?>
- <input type="hidden" name="action" value="remove">
- <input type="hidden" name="product_id" value="<?= (int) $item['product_id'] ?>">
- <?php if ($item['variant_id'] !== null): ?>
- <input type="hidden" name="variant_id" value="<?= (int) $item['variant_id'] ?>">
- <?php endif; ?>
- <button type="submit" class="dash-btn-small dash-btn-danger"><?= htmlspecialchars(Language::get('cart_remove_button', 'Remove')) ?></button>
- </form>
- </td>
- </tr>
- <?php endforeach; ?>
- </tbody>
- </table>
-
- <div class="cart-discount-box">
- <?php if ($appliedCode !== null): ?>
- <p>
- <?= htmlspecialchars(sprintf(Language::get('cart_discount_applied', 'Discount code "%s" applied.'), $appliedCode)) ?>
- <form method="post" style="display:inline;">
- <?= Csrf::field() ?>
- <input type="hidden" name="action" value="remove_discount">
- <button type="submit" class="dash-btn-small"><?= htmlspecialchars(Language::get('cart_remove_discount', 'Remove')) ?></button>
- </form>
- </p>
- <?php else: ?>
- <form method="post" class="cart-discount-form">
- <?= Csrf::field() ?>
- <input type="hidden" name="action" value="apply_discount">
- <label for="discount-code"><?= htmlspecialchars(Language::get('cart_discount_label', 'Discount code')) ?></label>
- <input type="text" id="discount-code" name="code" placeholder="<?= htmlspecialchars(Language::get('cart_discount_placeholder', 'Enter code')) ?>">
- <button type="submit" class="dash-btn-small"><?= htmlspecialchars(Language::get('cart_discount_apply', 'Apply')) ?></button>
- </form>
- <?php endif; ?>
- </div>
-
- <div class="cart-summary">
- <div class="cart-summary-row"><span><?= htmlspecialchars(Language::get('cart_subtotal', 'Subtotal')) ?></span><span><?= htmlspecialchars(Currency::format($subtotalCents, $cartCurrency)) ?></span></div>
- <?php if ($discountCents > 0): ?>
- <div class="cart-summary-row"><span><?= htmlspecialchars(Language::get('cart_discount', 'Discount')) ?></span><span>-<?= htmlspecialchars(Currency::format($discountCents, $cartCurrency)) ?></span></div>
- <?php endif; ?>
- <div class="cart-summary-row cart-summary-total"><span><?= htmlspecialchars(Language::get('cart_total', 'Total')) ?></span><span><?= htmlspecialchars(Currency::format($totalCents, $cartCurrency)) ?></span></div>
- <p class="cart-tax-note"><?= htmlspecialchars(Language::get('cart_tax_note', 'Tax is calculated at checkout where required.')) ?></p>
- </div>
-
- <a href="checkout.php" class="unlock-btn"><?= htmlspecialchars(Language::get('cart_checkout_button', 'Proceed to checkout')) ?></a>
- <?php endif; ?>
-
- <?php require __DIR__ . '/includes/front-footer.php'; ?>
-