284 lines
12 KiB
PHP
284 lines
12 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
use Core\Http\Router;
|
|
use Core\Services\Database;
|
|
use Core\Services\Settings;
|
|
use Core\Services\Shortcodes;
|
|
use Plugins\Store\StoreController;
|
|
|
|
require_once __DIR__ . '/StoreController.php';
|
|
require_once __DIR__ . '/gateways/GatewayInterface.php';
|
|
require_once __DIR__ . '/gateways/PaypalGateway.php';
|
|
require_once __DIR__ . '/gateways/Gateways.php';
|
|
|
|
Shortcodes::register('sale-chart', static function (array $attrs = []): string {
|
|
$defaultLimit = max(1, min(50, (int)Settings::get('store_sales_chart_limit', '10')));
|
|
$defaultScope = strtolower(trim((string)Settings::get('store_sales_chart_default_scope', 'tracks')));
|
|
if (!in_array($defaultScope, ['tracks', 'releases'], true)) {
|
|
$defaultScope = 'tracks';
|
|
}
|
|
$defaultWindow = strtolower(trim((string)Settings::get('store_sales_chart_default_window', 'latest')));
|
|
if (!in_array($defaultWindow, ['latest', 'weekly', 'all_time'], true)) {
|
|
$defaultWindow = 'latest';
|
|
}
|
|
|
|
$limit = max(1, min(50, (int)($attrs['limit'] ?? $defaultLimit)));
|
|
$scope = strtolower(trim((string)($attrs['type'] ?? $attrs['scope'] ?? $defaultScope)));
|
|
if (!in_array($scope, ['tracks', 'releases'], true)) {
|
|
$scope = $defaultScope;
|
|
}
|
|
$window = strtolower(trim((string)($attrs['mode'] ?? $attrs['window'] ?? $defaultWindow)));
|
|
if (!in_array($window, ['latest', 'weekly', 'all_time'], true)) {
|
|
$window = $defaultWindow;
|
|
}
|
|
|
|
$db = Database::get();
|
|
if (!($db instanceof \PDO)) {
|
|
return '';
|
|
}
|
|
|
|
$rows = [];
|
|
try {
|
|
$latestPaid = (string)($db->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 '<div class="ac-shortcode-empty">No sales yet.</div>';
|
|
}
|
|
|
|
$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 !== ''
|
|
? '<img src="' . htmlspecialchars($coverRaw, ENT_QUOTES, 'UTF-8') . '" alt="" loading="lazy">'
|
|
: '<div class="ac-shortcode-cover-fallback">AC</div>';
|
|
|
|
$copy = '<span class="ac-shortcode-sale-copy">'
|
|
. '<span class="ac-shortcode-sale-title">' . $title . '</span>'
|
|
. ($artist !== '' ? '<span class="ac-shortcode-sale-artist">' . $artist . '</span>' : '')
|
|
. '</span>'
|
|
. '<span class="ac-shortcode-sale-rank">' . $position . '</span>';
|
|
|
|
$content = '<span class="ac-shortcode-sale-thumb">' . $thumbHtml . '</span>' . $copy;
|
|
|
|
if ($href !== '#') {
|
|
$content = '<a class="ac-shortcode-sale-link" href="' . htmlspecialchars($href, ENT_QUOTES, 'UTF-8') . '">' . $content . '</a>';
|
|
}
|
|
|
|
$list .= '<li class="ac-shortcode-sale-item">' . $content . '</li>';
|
|
$position++;
|
|
}
|
|
|
|
$heading = htmlspecialchars((string)($attrs['title'] ?? 'Top Sellers'), ENT_QUOTES, 'UTF-8');
|
|
return '<section class="ac-shortcode-sale-chart"><header class="ac-shortcode-sale-head"><h3>' . $heading . '</h3></header><ol class="ac-shortcode-sale-list">' . $list . '</ol></section>';
|
|
});
|
|
|
|
Shortcodes::register('login-link', static function (array $attrs = []): string {
|
|
$label = trim((string)($attrs['label'] ?? 'Login'));
|
|
if ($label === '') {
|
|
$label = 'Login';
|
|
}
|
|
return '<a class="ac-shortcode-link ac-shortcode-link-login" href="/account">' . htmlspecialchars($label, ENT_QUOTES, 'UTF-8') . '</a>';
|
|
});
|
|
|
|
Shortcodes::register('account-link', static function (array $attrs = []): string {
|
|
$label = trim((string)($attrs['label'] ?? 'My Account'));
|
|
if ($label === '') {
|
|
$label = 'My Account';
|
|
}
|
|
return '<a class="ac-shortcode-link ac-shortcode-link-account" href="/account">' . htmlspecialchars($label, ENT_QUOTES, 'UTF-8') . '</a>';
|
|
});
|
|
|
|
Shortcodes::register('checkout-link', static function (array $attrs = []): string {
|
|
$label = trim((string)($attrs['label'] ?? 'Checkout'));
|
|
if ($label === '') {
|
|
$label = 'Checkout';
|
|
}
|
|
return '<a class="ac-shortcode-link ac-shortcode-link-checkout" href="/checkout">' . htmlspecialchars($label, ENT_QUOTES, 'UTF-8') . '</a>';
|
|
});
|
|
|
|
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[] = '<span class="ac-shortcode-cart-count">' . $count . '</span>';
|
|
}
|
|
if ($showTotal) {
|
|
$parts[] = '<span class="ac-shortcode-cart-total">' . htmlspecialchars($currency, ENT_QUOTES, 'UTF-8') . ' ' . number_format($amount, 2) . '</span>';
|
|
}
|
|
|
|
return '<a class="ac-shortcode-link ac-shortcode-link-cart" href="/cart">' . implode(' ', $parts) . '</a>';
|
|
});
|
|
|
|
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']);
|
|
};
|