query("SELECT MAX(updated_at) FROM ac_store_orders WHERE status = 'paid'")->fetchColumn() ?? ''); $latestCache = (string)($db->query("SELECT MAX(updated_at) FROM ac_store_sales_chart_cache")->fetchColumn() ?? ''); if ($latestPaid !== '' && ($latestCache === '' || strcmp($latestPaid, $latestCache) > 0)) { (new StoreController())->rebuildSalesChartCache(); } } catch (\Throwable $e) { } try { $stmt = $db->prepare(" SELECT item_key, item_label AS title, units, revenue FROM ac_store_sales_chart_cache WHERE chart_scope = :scope AND chart_window = :window ORDER BY rank_no ASC LIMIT :limit "); $stmt->bindValue(':scope', $scope, \PDO::PARAM_STR); $stmt->bindValue(':window', $window, \PDO::PARAM_STR); $stmt->bindValue(':limit', $limit, \PDO::PARAM_INT); $stmt->execute(); $rows = $stmt->fetchAll(\PDO::FETCH_ASSOC) ?: []; } catch (\Throwable $e) { $rows = []; } if (!$rows && $scope === 'tracks') { try { $stmt = $db->prepare(" SELECT item_key, item_label AS title, units, revenue FROM ac_store_sales_chart_cache WHERE chart_scope = 'releases' AND chart_window = :window ORDER BY rank_no ASC LIMIT :limit "); $stmt->bindValue(':window', $window, \PDO::PARAM_STR); $stmt->bindValue(':limit', $limit, \PDO::PARAM_INT); $stmt->execute(); $rows = $stmt->fetchAll(\PDO::FETCH_ASSOC) ?: []; } catch (\Throwable $e) { $rows = []; } } if (!$rows) { return '
No sales yet.
'; } $releaseIds = []; $trackIds = []; foreach ($rows as $row) { $itemKey = trim((string)($row['item_key'] ?? '')); if (preg_match('/^release:(\d+)$/', $itemKey, $m)) { $releaseIds[] = (int)$m[1]; } elseif (preg_match('/^track:(\d+)$/', $itemKey, $m)) { $trackIds[] = (int)$m[1]; } } $releaseIds = array_values(array_unique(array_filter($releaseIds))); $trackIds = array_values(array_unique(array_filter($trackIds))); $releaseMap = []; if ($releaseIds) { try { $in = implode(',', array_fill(0, count($releaseIds), '?')); $stmt = $db->prepare(" SELECT id, title, slug, cover_url, COALESCE(artist_name, '') AS artist_name FROM ac_releases WHERE id IN ({$in}) "); foreach ($releaseIds as $i => $rid) { $stmt->bindValue($i + 1, $rid, \PDO::PARAM_INT); } $stmt->execute(); $rels = $stmt->fetchAll(\PDO::FETCH_ASSOC) ?: []; foreach ($rels as $rel) { $releaseMap['release:' . (int)$rel['id']] = $rel; } } catch (\Throwable $e) { } } if ($trackIds) { try { $in = implode(',', array_fill(0, count($trackIds), '?')); $stmt = $db->prepare(" SELECT t.id AS track_id, r.id, r.title, r.slug, r.cover_url, COALESCE(r.artist_name, '') AS artist_name FROM ac_release_tracks t JOIN ac_releases r ON r.id = t.release_id WHERE t.id IN ({$in}) "); foreach ($trackIds as $i => $tid) { $stmt->bindValue($i + 1, $tid, \PDO::PARAM_INT); } $stmt->execute(); $rels = $stmt->fetchAll(\PDO::FETCH_ASSOC) ?: []; foreach ($rels as $rel) { $releaseMap['track:' . (int)$rel['track_id']] = $rel; } } catch (\Throwable $e) { } } $list = ''; $position = 1; foreach ($rows as $row) { $itemKey = trim((string)($row['item_key'] ?? '')); $rel = $releaseMap[$itemKey] ?? null; $titleRaw = $rel ? (string)($rel['title'] ?? '') : (string)($row['title'] ?? ''); $artistRaw = $rel ? trim((string)($rel['artist_name'] ?? '')) : ''; $slugRaw = $rel ? trim((string)($rel['slug'] ?? '')) : ''; $coverRaw = $rel ? trim((string)($rel['cover_url'] ?? '')) : ''; $title = htmlspecialchars($titleRaw !== '' ? $titleRaw : ((string)($row['title'] ?? 'Release')), ENT_QUOTES, 'UTF-8'); $artist = htmlspecialchars($artistRaw, ENT_QUOTES, 'UTF-8'); $href = $slugRaw !== '' ? '/release?slug=' . rawurlencode($slugRaw) : '#'; $thumbHtml = $coverRaw !== '' ? '' : '
AC
'; $copy = '' . '' . $title . '' . ($artist !== '' ? '' . $artist . '' : '') . '' . '' . $position . ''; $content = '' . $thumbHtml . '' . $copy; if ($href !== '#') { $content = '' . $content . ''; } $list .= '
  • ' . $content . '
  • '; $position++; } $heading = htmlspecialchars((string)($attrs['title'] ?? 'Top Sellers'), ENT_QUOTES, 'UTF-8'); return '

    ' . $heading . '

      ' . $list . '
    '; }); Shortcodes::register('login-link', static function (array $attrs = []): string { $label = trim((string)($attrs['label'] ?? 'Login')); if ($label === '') { $label = 'Login'; } return '' . htmlspecialchars($label, ENT_QUOTES, 'UTF-8') . ''; }); Shortcodes::register('account-link', static function (array $attrs = []): string { $label = trim((string)($attrs['label'] ?? 'My Account')); if ($label === '') { $label = 'My Account'; } return '' . htmlspecialchars($label, ENT_QUOTES, 'UTF-8') . ''; }); Shortcodes::register('checkout-link', static function (array $attrs = []): string { $label = trim((string)($attrs['label'] ?? 'Checkout')); if ($label === '') { $label = 'Checkout'; } return '' . htmlspecialchars($label, ENT_QUOTES, 'UTF-8') . ''; }); Shortcodes::register('cart-link', static function (array $attrs = []): string { $showCount = ((string)($attrs['show_count'] ?? '1')) !== '0'; $showTotal = ((string)($attrs['show_total'] ?? '1')) !== '0'; $label = trim((string)($attrs['label'] ?? 'Cart')); if ($label === '') { $label = 'Cart'; } $count = 0; $amount = 0.0; $currency = strtoupper(trim((string)Settings::get('store_currency', 'GBP'))); if (!preg_match('/^[A-Z]{3}$/', $currency)) { $currency = 'GBP'; } if (session_status() !== PHP_SESSION_ACTIVE) { @session_start(); } $cart = is_array($_SESSION['ac_cart'] ?? null) ? $_SESSION['ac_cart'] : []; foreach ($cart as $item) { if (!is_array($item)) { continue; } $qty = max(1, (int)($item['qty'] ?? 1)); $price = (float)($item['price'] ?? 0); $count += $qty; $amount += ($price * $qty); } $parts = [htmlspecialchars($label, ENT_QUOTES, 'UTF-8')]; if ($showCount) { $parts[] = '' . $count . ''; } if ($showTotal) { $parts[] = '' . htmlspecialchars($currency, ENT_QUOTES, 'UTF-8') . ' ' . number_format($amount, 2) . ''; } return '' . implode(' ', $parts) . ''; }); return function (Router $router): void { $controller = new StoreController(); $router->get('/cart', [$controller, 'cartIndex']); $router->post('/cart/discount/apply', [$controller, 'cartApplyDiscount']); $router->post('/cart/discount/remove', [$controller, 'cartClearDiscount']); $router->get('/checkout', [$controller, 'checkoutIndex']); $router->get('/account', [$controller, 'accountIndex']); $router->post('/account/request-login', [$controller, 'accountRequestLogin']); $router->get('/account/login', [$controller, 'accountLogin']); $router->get('/account/logout', [$controller, 'accountLogout']); $router->post('/checkout/place', [$controller, 'checkoutPlace']); $router->get('/checkout/paypal/return', [$controller, 'checkoutPaypalReturn']); $router->get('/checkout/paypal/cancel', [$controller, 'checkoutPaypalCancel']); $router->post('/checkout/sandbox', [$controller, 'checkoutSandbox']); $router->get('/store/download', [$controller, 'download']); $router->post('/cart/remove', [$controller, 'cartRemove']); $router->get('/store/sales-chart/rebuild', [$controller, 'salesChartCron']); $router->get('/admin/store', [$controller, 'adminIndex']); $router->post('/admin/store/install', [$controller, 'adminInstall']); $router->get('/admin/store/settings', [$controller, 'adminSettings']); $router->post('/admin/store/settings', [$controller, 'adminSaveSettings']); $router->post('/admin/store/settings/rebuild-sales-chart', [$controller, 'adminRebuildSalesChart']); $router->post('/admin/store/discounts/create', [$controller, 'adminDiscountCreate']); $router->post('/admin/store/discounts/delete', [$controller, 'adminDiscountDelete']); $router->post('/admin/store/settings/test-email', [$controller, 'adminSendTestEmail']); $router->post('/admin/store/settings/test-paypal', [$controller, 'adminTestPaypal']); $router->get('/admin/store/customers', [$controller, 'adminCustomers']); $router->get('/admin/store/orders', [$controller, 'adminOrders']); $router->post('/admin/store/orders/create', [$controller, 'adminOrderCreate']); $router->post('/admin/store/orders/status', [$controller, 'adminOrderStatus']); $router->post('/admin/store/orders/refund', [$controller, 'adminOrderRefund']); $router->post('/admin/store/orders/delete', [$controller, 'adminOrderDelete']); $router->get('/admin/store/order', [$controller, 'adminOrderView']); $router->post('/store/cart/add', [$controller, 'cartAdd']); };